Skip to main content

signed_crypto/
lib.rs

1// Copyright 2026, Kehan Pan, All rights reserved.
2//
3// Cryptographic scheme inspired by:
4// https://github.com/google/openrtb-doubleclick/blob/master/doubleclick-core/src/main/java/com/google/doubleclick/crypto/DoubleClickCrypto.java
5
6//! # signed-crypto
7//!
8//! A Rust library for encrypted payloads with built-in integrity verification.
9//!
10//! ## Package Format
11//!
12//! Encrypted payloads follow this structure:
13//!
14//! ```text
15//! initVector:16 || E(payload:?) || I(signature:4)
16//! ```
17//!
18//! where:
19//! - `initVector` = `timestamp:8 || serverId:8`
20//! - `E(payload)` = AES-256/CTR64 encryption with encryption key
21//! - `I(signature)` = First 4 bytes of HMAC-SHA256(integrityKey, payload || initVector)
22//!
23//! ## Example
24//!
25//! ```rust
26//! use signed_crypto::{Crypto, Keys};
27//!
28//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
29//! let keys = Keys::new(&[0u8; 32], &[0u8; 32])?;
30//! let crypto = Crypto::new(keys);
31//!
32//! // Encrypt
33//! let payload = b"Hello, world!";
34//! let mut pkg = crypto.init_plain_data(payload.len(), None)?;
35//! crypto.set_payload(&mut pkg, payload)?;
36//! let encrypted = crypto.encrypt(&pkg)?;
37//!
38//! // Decrypt
39//! let decrypted = crypto.decrypt(&encrypted)?;
40//! assert_eq!(crypto.payload(&decrypted), Some(payload.as_slice()));
41//! # Ok(())
42//! # }
43//! ```
44
45use aes::cipher::{KeyIvInit, StreamCipher};
46use base64::{engine::general_purpose::URL_SAFE, Engine as _};
47use byteorder::{BigEndian, ByteOrder};
48use hmac::{Hmac, Mac};
49use sha2::Sha256;
50use thiserror::Error;
51use time::{Duration, OffsetDateTime};
52
53type HmacSha256 = Hmac<Sha256>;
54type Aes256Ctr64BE = ctr::Ctr64BE<aes::Aes256>;
55
56const UNIX_EPOCH: OffsetDateTime = time::OffsetDateTime::UNIX_EPOCH;
57
58/// Holds the encryption and integrity keys.
59///
60/// Both keys must be exactly 32 bytes (256 bits).
61///
62/// # Fields
63///
64/// * `encryption_key` - AES-256 encryption key
65/// * `integrity_key` - HMAC-SHA256 integrity key
66#[derive(Clone, Debug)]
67pub struct Keys {
68    /// AES-256 encryption key (32 bytes)
69    pub encryption_key: [u8; 32],
70    /// HMAC-SHA256 integrity key (32 bytes)
71    pub integrity_key: [u8; 32],
72}
73
74impl Keys {
75    /// Creates a new `Keys` instance from raw byte slices.
76    ///
77    /// Both keys must be exactly 32 bytes.
78    ///
79    /// # Errors
80    ///
81    /// Returns [`CryptoError::InvalidKey`] if either key is not 32 bytes.
82    ///
83    /// # Example
84    ///
85    /// ```rust
86    /// use signed_crypto::Keys;
87    ///
88    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
89    /// let enc_key = [0u8; 32];
90    /// let int_key = [0u8; 32];
91    /// let keys = Keys::new(&enc_key, &int_key)?;
92    /// # Ok(())
93    /// # }
94    /// ```
95    pub fn new(encryption_key: &[u8], integrity_key: &[u8]) -> Result<Self, CryptoError> {
96        let encryption_key: [u8; 32] = encryption_key
97            .try_into()
98            .map_err(|_| CryptoError::InvalidKey)?;
99        let integrity_key: [u8; 32] = integrity_key
100            .try_into()
101            .map_err(|_| CryptoError::InvalidKey)?;
102
103        Ok(Self {
104            encryption_key,
105            integrity_key,
106        })
107    }
108}
109
110/// Errors that can occur during cryptographic operations.
111#[derive(Error, Debug)]
112pub enum CryptoError {
113    /// Key is not exactly 32 bytes.
114    #[error("invalid key")]
115    InvalidKey,
116    /// HMAC signature verification failed.
117    #[error("invalid signature")]
118    InvalidSign,
119    /// Initialization vector is invalid.
120    #[error("invalid init vector")]
121    InvalidInitVector,
122    /// Data is too short to be a valid package.
123    #[error("data too short")]
124    DataTooShort,
125    /// Payload size does not match expected size.
126    #[error("payload size mismatch")]
127    PayloadSizeMismatch,
128    /// Base64 decoding failed.
129    #[error("decode error: {0}")]
130    DecodeError(#[from] base64::DecodeError),
131}
132
133/// Main cryptographic operations instance.
134///
135/// Holds the keys and provides methods for encryption, decryption,
136/// and metadata extraction.
137///
138/// # Example
139///
140/// ```rust
141/// use signed_crypto::{Crypto, Keys};
142///
143/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
144/// let keys = Keys::new(&[0u8; 32], &[0u8; 32])?;
145/// let crypto = Crypto::new(keys);
146/// # Ok(())
147/// # }
148/// ```
149pub struct Crypto {
150    /// The encryption and integrity keys.
151    pub keys: Keys,
152}
153
154impl Crypto {
155    /// Creates a new `Crypto` instance.
156    ///
157    /// # Example
158    ///
159    /// ```rust
160    /// use signed_crypto::{Crypto, Keys};
161    ///
162    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
163    /// let keys = Keys::new(&[0u8; 32], &[0u8; 32])?;
164    /// let crypto = Crypto::new(keys);
165    /// # Ok(())
166    /// # }
167    /// ```
168    pub fn new(keys: Keys) -> Self {
169        Self { keys }
170    }
171
172    /// Offset of the initialization vector in a package.
173    pub const IV_BASE: usize = 0;
174    /// Size of the initialization vector in bytes.
175    pub const IV_SIZE: usize = 16;
176    /// Offset of the timestamp within the IV.
177    pub const IV_TIME_OFFSET: usize = 0;
178    /// Size of the timestamp in bytes.
179    pub const IV_TIME_SIZE: usize = 8;
180    /// Offset of the server ID within the IV.
181    pub const IV_SERVER_ID_OFFSET: usize = 8;
182    /// Size of the server ID in bytes.
183    pub const IV_SERVER_ID_SIZE: usize = 8;
184    /// Size of the HMAC signature in bytes.
185    pub const SIGNATURE_SIZE: usize = 4;
186    /// Offset where the payload begins.
187    pub const PAYLOAD_BASE: usize = Crypto::IV_BASE + Crypto::IV_SIZE;
188    /// Total overhead size (IV + signature) in bytes.
189    pub const OVERHEAD_SIZE: usize = Crypto::IV_SIZE + Crypto::SIGNATURE_SIZE;
190
191    /// Decodes a URL-safe Base64 encoded string.
192    ///
193    /// # Example
194    ///
195    /// ```rust
196    /// use signed_crypto::{Crypto, Keys};
197    ///
198    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
199    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
200    /// let encoded = "SGVsbG8=";
201    /// let decoded = crypto.decode(encoded)?;
202    /// # Ok(())
203    /// # }
204    /// ```
205    #[inline]
206    pub fn decode<T>(&self, data: T) -> Result<Vec<u8>, CryptoError>
207    where
208        T: AsRef<[u8]>,
209    {
210        URL_SAFE
211            .decode(data)
212            .map(|v| v.to_vec())
213            .map_err(|e| e.into())
214    }
215
216    /// Encodes data as a URL-safe Base64 string.
217    ///
218    /// # Example
219    ///
220    /// ```rust
221    /// use signed_crypto::{Crypto, Keys};
222    ///
223    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
224    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
225    /// let data = b"Hello";
226    /// let encoded = crypto.encode(data);
227    /// # Ok(())
228    /// # }
229    /// ```
230    #[inline]
231    pub fn encode<T>(&self, data: T) -> String
232    where
233        T: AsRef<[u8]>,
234    {
235        URL_SAFE.encode(data)
236    }
237
238    /// Decrypts a package and verifies the HMAC signature.
239    ///
240    /// # Errors
241    ///
242    /// Returns [`CryptoError::InvalidSign`] if signature verification fails.
243    ///
244    /// # Example
245    ///
246    /// ```rust
247    /// use signed_crypto::{Crypto, Keys};
248    ///
249    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
250    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
251    /// let mut pkg = crypto.init_plain_data(5, None)?;
252    /// crypto.set_payload(&mut pkg, b"Hello")?;
253    /// let encrypted = crypto.encrypt(&pkg)?;
254    /// let decrypted = crypto.decrypt(&encrypted)?;
255    /// # Ok(())
256    /// # }
257    /// ```
258    #[inline]
259    pub fn decrypt(&self, cipher_data: &[u8]) -> Result<Vec<u8>, CryptoError> {
260        if cipher_data.len() < Self::OVERHEAD_SIZE {
261            return Err(CryptoError::DataTooShort);
262        }
263
264        let mut data = cipher_data.to_vec();
265        let data_size = data.len();
266
267        self.xor_payload(&mut data)?;
268
269        let confirmation_signature = self.hmac_signature(&data)?;
270        let integrity_signature = self.read_i32(&data, data_size - Self::SIGNATURE_SIZE);
271        self.write_i32(
272            &mut data,
273            data_size - Self::SIGNATURE_SIZE,
274            confirmation_signature,
275        );
276
277        if confirmation_signature != integrity_signature {
278            return Err(CryptoError::InvalidSign);
279        }
280
281        Ok(data)
282    }
283
284    /// Encrypts a package in-place.
285    ///
286    /// # Example
287    ///
288    /// ```rust
289    /// use signed_crypto::{Crypto, Keys};
290    ///
291    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
292    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
293    /// let mut pkg = crypto.init_plain_data(5, None)?;
294    /// crypto.set_payload(&mut pkg, b"Hello")?;
295    /// let encrypted = crypto.encrypt(&pkg)?;
296    /// # Ok(())
297    /// # }
298    /// ```
299    #[inline]
300    pub fn encrypt(&self, plain_data: &[u8]) -> Result<Vec<u8>, CryptoError> {
301        if plain_data.len() < Self::OVERHEAD_SIZE {
302            return Err(CryptoError::DataTooShort);
303        }
304
305        let mut data = plain_data.to_vec();
306        let data_size = data.len();
307        let signature = self.hmac_signature(&data)?;
308        self.write_i32(&mut data, data_size - Self::SIGNATURE_SIZE, signature);
309
310        self.xor_payload(&mut data)?;
311
312        Ok(data)
313    }
314
315    /// Creates a custom initialization vector.
316    ///
317    /// # Arguments
318    ///
319    /// * `timestamp` - The timestamp to embed
320    /// * `server_id` - The server ID to embed
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// use signed_crypto::{Crypto, Keys};
326    /// use time::OffsetDateTime;
327    ///
328    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
329    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
330    /// let iv = crypto.create_init_vector(OffsetDateTime::now_utc(), 12345);
331    /// # Ok(())
332    /// # }
333    /// ```
334    #[inline]
335    pub fn create_init_vector(&self, timestamp: OffsetDateTime, server_id: i64) -> Vec<u8> {
336        let timestamp = (timestamp.unix_timestamp_nanos() / 1_000) as i64; // microseconds
337        let mut iv = vec![0; Self::IV_SIZE];
338        self.write_i64(&mut iv, Self::IV_TIME_OFFSET, timestamp);
339        self.write_i64(&mut iv, Self::IV_SERVER_ID_OFFSET, server_id);
340        iv
341    }
342
343    /// Extracts the timestamp from a package's initialization vector.
344    ///
345    /// Returns `None` if the data is too short.
346    ///
347    /// # Example
348    ///
349    /// ```rust
350    /// use signed_crypto::{Crypto, Keys};
351    /// use time::OffsetDateTime;
352    ///
353    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
354    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
355    /// let mut pkg = crypto.init_plain_data(5, None)?;
356    /// crypto.set_payload(&mut pkg, b"Hello")?;
357    /// let encrypted = crypto.encrypt(&pkg)?;
358    /// let ts = crypto.timestamp(&encrypted).unwrap();
359    /// # Ok(())
360    /// # }
361    /// ```
362    #[inline]
363    pub fn timestamp(&self, data: &[u8]) -> Option<OffsetDateTime> {
364        if data.len() < Self::IV_SIZE {
365            return None;
366        }
367        let ts = self.read_i64(data, Self::IV_BASE + Self::IV_TIME_OFFSET);
368        Some(
369            UNIX_EPOCH
370                .checked_add(Duration::microseconds(ts))
371                .unwrap_or(UNIX_EPOCH),
372        )
373    }
374
375    /// Extracts the server ID from a package's initialization vector.
376    ///
377    /// Returns `None` if the data is too short.
378    ///
379    /// # Example
380    ///
381    /// ```rust
382    /// use signed_crypto::{Crypto, Keys};
383    ///
384    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
385    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
386    /// let mut pkg = crypto.init_plain_data(5, None)?;
387    /// crypto.set_payload(&mut pkg, b"Hello")?;
388    /// let encrypted = crypto.encrypt(&pkg)?;
389    /// let server_id = crypto.server_id(&encrypted).unwrap();
390    /// # Ok(())
391    /// # }
392    /// ```
393    #[inline]
394    pub fn server_id(&self, data: &[u8]) -> Option<i64> {
395        if data.len() < Self::IV_SIZE {
396            return None;
397        }
398        Some(self.read_i64(data, Self::IV_BASE + Self::IV_SERVER_ID_OFFSET))
399    }
400
401    /// Extracts the payload from a package without decryption.
402    ///
403    /// Returns `None` if the data is too short.
404    ///
405    /// # Example
406    ///
407    /// ```rust
408    /// use signed_crypto::{Crypto, Keys};
409    ///
410    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
411    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
412    /// let mut pkg = crypto.init_plain_data(5, None)?;
413    /// crypto.set_payload(&mut pkg, b"Hello")?;
414    /// let payload = crypto.payload(&pkg).unwrap();
415    /// assert_eq!(payload, b"Hello");
416    /// # Ok(())
417    /// # }
418    /// ```
419    #[inline]
420    pub fn payload<'a>(&self, data: &'a [u8]) -> Option<&'a [u8]> {
421        if data.len() < Self::OVERHEAD_SIZE {
422            return None;
423        }
424        Some(&data[Self::PAYLOAD_BASE..data.len() - Self::SIGNATURE_SIZE])
425    }
426
427    /// Initializes a plain data package buffer.
428    ///
429    /// If `iv` is `None`, generates a random IV with current timestamp.
430    ///
431    /// # Arguments
432    ///
433    /// * `payload_size` - Size of the payload in bytes
434    /// * `iv` - Optional custom initialization vector
435    ///
436    /// # Example
437    ///
438    /// ```rust
439    /// use signed_crypto::{Crypto, Keys};
440    ///
441    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
442    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
443    /// let pkg = crypto.init_plain_data(10, None)?;
444    /// # Ok(())
445    /// # }
446    /// ```
447    #[inline]
448    pub fn init_plain_data(
449        &self,
450        payload_size: usize,
451        iv: Option<&[u8]>,
452    ) -> Result<Vec<u8>, CryptoError> {
453        let mut plain_data = vec![0; Self::OVERHEAD_SIZE + payload_size];
454        if let Some(iv) = iv {
455            plain_data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE].copy_from_slice(iv);
456        } else {
457            let now = (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000) as i64;
458            self.write_i64(&mut plain_data, Self::IV_TIME_OFFSET, now);
459            self.write_i64(
460                &mut plain_data,
461                Self::IV_SERVER_ID_OFFSET,
462                rand::random::<i64>(),
463            );
464        }
465
466        Ok(plain_data)
467    }
468
469    /// Sets the payload in a plain data package buffer.
470    ///
471    /// # Errors
472    ///
473    /// Returns [`CryptoError::PayloadSizeMismatch`] if the payload size
474    /// does not match the expected size.
475    ///
476    /// # Example
477    ///
478    /// ```rust
479    /// use signed_crypto::{Crypto, Keys};
480    ///
481    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
482    /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
483    /// let mut pkg = crypto.init_plain_data(5, None)?;
484    /// crypto.set_payload(&mut pkg, b"Hello")?;
485    /// # Ok(())
486    /// # }
487    /// ```
488    #[inline]
489    pub fn set_payload(&self, plain_data: &mut [u8], payload: &[u8]) -> Result<(), CryptoError> {
490        if payload.len() != plain_data.len() - Self::OVERHEAD_SIZE {
491            return Err(CryptoError::PayloadSizeMismatch);
492        }
493        plain_data[Self::PAYLOAD_BASE..Self::PAYLOAD_BASE + payload.len()].copy_from_slice(payload);
494        Ok(())
495    }
496
497    #[inline]
498    fn read_i32(&self, data: &[u8], offset: usize) -> i32 {
499        BigEndian::read_i32(&data[offset..offset + 4])
500    }
501
502    #[inline]
503    fn read_i64(&self, data: &[u8], offset: usize) -> i64 {
504        BigEndian::read_i64(&data[offset..offset + 8])
505    }
506
507    #[inline]
508    fn write_i32(&self, data: &mut [u8], offset: usize, value: i32) {
509        BigEndian::write_i32(&mut data[offset..offset + 4], value);
510    }
511
512    #[inline]
513    fn write_i64(&self, data: &mut [u8], offset: usize, value: i64) {
514        BigEndian::write_i64(&mut data[offset..offset + 8], value);
515    }
516
517    #[inline]
518    fn xor_payload(&self, data: &mut [u8]) -> Result<(), CryptoError> {
519        let iv: &[u8; 16] = &data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE]
520            .try_into()
521            .map_err(|_| CryptoError::InvalidInitVector)?;
522
523        let mut cipher = Aes256Ctr64BE::new(&self.keys.encryption_key.into(), iv.into());
524        let data_size = data.len();
525        cipher.apply_keystream(&mut data[Self::PAYLOAD_BASE..data_size - Self::SIGNATURE_SIZE]);
526
527        Ok(())
528    }
529
530    #[inline]
531    fn hmac_signature(&self, data: &[u8]) -> Result<i32, CryptoError> {
532        let mut mac = HmacSha256::new_from_slice(&self.keys.integrity_key)
533            .map_err(|_| CryptoError::InvalidKey)?;
534
535        mac.update(&data[Self::PAYLOAD_BASE..data.len() - Self::SIGNATURE_SIZE]);
536        mac.update(&data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE]);
537
538        let b = mac.finalize().into_bytes();
539
540        Ok(self.read_i32(&b, 0))
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547    use base64::prelude::*;
548
549    static TEST_ENCRYPTION_KEY: &str = "sIxwz7yw62yrfoLGt12lIHKuYrK/S5kLuApI2BQe7Ac=";
550    static TEST_INTEGRITY_KEY: &str = "v3fsVcMBMMHYzRhi7SpM0sdqwzvAxM6KPTu9OtVod5I=";
551
552    fn create_keys() -> Keys {
553        Keys::new(
554            &BASE64_STANDARD.decode(TEST_ENCRYPTION_KEY).unwrap(),
555            &BASE64_STANDARD.decode(TEST_INTEGRITY_KEY).unwrap(),
556        )
557        .unwrap()
558    }
559
560    #[test]
561    fn test_decode() {
562        let crypto = Crypto::new(create_keys());
563        let encoded = "aGVsbG8sIHdvcmxk";
564        let decoded = crypto.decode(encoded).unwrap();
565        assert_eq!(decoded, b"hello, world");
566    }
567
568    #[test]
569    fn test_encode() {
570        let crypto = Crypto::new(create_keys());
571        let data = b"hello, world";
572        let encoded = crypto.encode(data);
573        assert_eq!(encoded, "aGVsbG8sIHdvcmxk");
574    }
575
576    #[test]
577    fn test_decrypt() {
578        let crypto = Crypto::new(create_keys());
579        let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
580        let iv = crypto.create_init_vector(timestamp, 123456789);
581        let payload = "https://example.com".as_bytes();
582
583        let mut plain_data = crypto.init_plain_data(payload.len(), Some(&iv)).unwrap();
584        crypto.set_payload(&mut plain_data, payload).unwrap();
585        let encrypted_data = crypto.encrypt(&plain_data).unwrap();
586
587        assert_eq!(crypto.timestamp(&iv), Some(timestamp));
588        assert_eq!(crypto.server_id(&iv), Some(123456789));
589        assert_eq!(
590            crypto.payload(&encrypted_data).unwrap().len(),
591            payload.len()
592        );
593        assert_ne!(crypto.payload(&encrypted_data), Some(payload));
594
595        let decrypted_data = crypto.decrypt(&encrypted_data).unwrap();
596        assert_eq!(crypto.timestamp(&decrypted_data), Some(timestamp));
597        assert_eq!(crypto.server_id(&decrypted_data), Some(123456789));
598        assert_eq!(crypto.payload(&decrypted_data), Some(payload));
599
600        let mut encrypted_data_invalid_sign = encrypted_data.clone();
601        crypto.write_i32(
602            &mut encrypted_data_invalid_sign,
603            encrypted_data.len() - Crypto::SIGNATURE_SIZE,
604            123456789,
605        );
606        assert!(matches!(
607            crypto.decrypt(&encrypted_data_invalid_sign),
608            Err(CryptoError::InvalidSign)
609        ));
610        assert_ne!(crypto.payload(&encrypted_data_invalid_sign), Some(payload))
611    }
612
613    #[test]
614    fn test_create_init_vector() {
615        let crypto = Crypto::new(create_keys());
616        let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
617        let iv = crypto.create_init_vector(timestamp, 123456789);
618        assert_eq!(iv.len(), Crypto::IV_SIZE);
619        assert_eq!(crypto.read_i64(&iv, Crypto::IV_TIME_OFFSET), 1_000_000);
620        assert_eq!(crypto.read_i64(&iv, Crypto::IV_SERVER_ID_OFFSET), 123456789);
621        assert_eq!(crypto.timestamp(&iv), Some(timestamp));
622        assert_eq!(crypto.server_id(&iv), Some(123456789));
623    }
624
625    #[test]
626    fn test_init_plain_data() {
627        let crypto = Crypto::new(create_keys());
628        let payload = "https://example.com".as_bytes();
629
630        let mut plain_data = crypto.init_plain_data(payload.len(), None).unwrap();
631        crypto.set_payload(&mut plain_data, payload).unwrap();
632
633        assert_eq!(plain_data.len(), Crypto::OVERHEAD_SIZE + payload.len());
634        assert_eq!(crypto.payload(&plain_data), Some(payload));
635    }
636
637    #[test]
638    fn test_init_plain_data_empty_payload() {
639        let crypto = Crypto::new(create_keys());
640        let payload = "".as_bytes();
641
642        let mut plain_data = crypto.init_plain_data(0, None).unwrap();
643        crypto.set_payload(&mut plain_data, payload).unwrap();
644        assert_eq!(crypto.payload(&plain_data), Some(payload));
645    }
646}