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}