@@ -4,9 +4,11 @@ use rand_core::CryptoRngCore;
44use std:: vec:: Vec ;
55use zeroize:: Zeroizing ;
66
7- /// The amount of overhead in a ciphertext, compared to the plain message.
8- /// ChaCha20-Poly1305 uses a 128-bit (16 byte) authentication tag.
9- pub const CIPHERTEXT_OVERHEAD : usize = 16 ;
7+ /// Size of the ChaCha20-Poly1305 authentication tag.
8+ ///
9+ /// This tag is the overhead added to each ciphertext and must be transmitted
10+ /// alongside it for the receiver to verify integrity and authenticity.
11+ pub const TAG_SIZE : usize = 16 ;
1012
1113/// How many bytes are in a nonce.
1214/// ChaCha20-Poly1305 uses a 96-bit (12 byte) nonce.
@@ -55,36 +57,33 @@ cfg_if::cfg_if! {
5557 Self ( LessSafeKey :: new( unbound_key) )
5658 }
5759
58- fn encrypt (
60+ fn encrypt_in_place (
5961 & self ,
6062 nonce: & [ u8 ; NONCE_SIZE_BYTES ] ,
61- data: & [ u8 ] ,
62- ) -> Result <Vec < u8 > , Error > {
63+ data: & mut [ u8 ] ,
64+ ) -> Result <[ u8 ; TAG_SIZE ] , Error > {
6365 let nonce = aead:: Nonce :: assume_unique_for_key( * nonce) ;
64- let mut scratch = Vec :: with_capacity( data. len( ) + CIPHERTEXT_OVERHEAD ) ;
65- scratch. extend_from_slice( data) ;
66- self . 0
67- . seal_in_place_append_tag( nonce, aead:: Aad :: empty( ) , & mut scratch)
66+ let tag = self
67+ . 0
68+ . seal_in_place_separate_tag( nonce, aead:: Aad :: empty( ) , data)
6869 . map_err( |_| Error :: EncryptionFailed ) ?;
69- Ok ( scratch )
70+ Ok ( tag . as_ref ( ) . try_into ( ) . expect ( "tag size mismatch" ) )
7071 }
7172
72- fn decrypt (
73+ fn decrypt_in_place (
7374 & self ,
7475 nonce: & [ u8 ; NONCE_SIZE_BYTES ] ,
75- data: & [ u8 ] ,
76- ) -> Result <Vec < u8 > , Error > {
76+ data: & mut [ u8 ] ,
77+ ) -> Result <usize , Error > {
7778 let nonce = aead:: Nonce :: assume_unique_for_key( * nonce) ;
78- let mut scratch = data. to_vec( ) ;
7979 self . 0
80- . open_in_place( nonce, aead:: Aad :: empty( ) , & mut scratch )
80+ . open_in_place( nonce, aead:: Aad :: empty( ) , data )
8181 . map_err( |_| Error :: DecryptionFailed ) ?;
82- scratch. truncate( data. len( ) - CIPHERTEXT_OVERHEAD ) ;
83- Ok ( scratch)
82+ Ok ( data. len( ) - TAG_SIZE )
8483 }
8584 }
8685 } else {
87- use chacha20poly1305:: { aead:: Aead , ChaCha20Poly1305 , KeyInit as _} ;
86+ use chacha20poly1305:: { aead:: AeadInPlace , ChaCha20Poly1305 , KeyInit as _} ;
8887
8988 struct Cipher ( ChaCha20Poly1305 ) ;
9089
@@ -93,24 +92,36 @@ cfg_if::cfg_if! {
9392 Self ( ChaCha20Poly1305 :: new( key. into( ) ) )
9493 }
9594
96- fn encrypt (
95+ fn encrypt_in_place (
9796 & self ,
9897 nonce: & [ u8 ; NONCE_SIZE_BYTES ] ,
99- data: & [ u8 ] ,
100- ) -> Result <Vec <u8 >, Error > {
101- self . 0
102- . encrypt( nonce. into( ) , data)
103- . map_err( |_| Error :: EncryptionFailed )
98+ data: & mut [ u8 ] ,
99+ ) -> Result <[ u8 ; TAG_SIZE ] , Error > {
100+ let tag = self
101+ . 0
102+ . encrypt_in_place_detached( nonce. into( ) , & [ ] , data)
103+ . map_err( |_| Error :: EncryptionFailed ) ?;
104+ Ok ( tag. into( ) )
104105 }
105106
106- fn decrypt (
107+ fn decrypt_in_place (
107108 & self ,
108109 nonce: & [ u8 ; NONCE_SIZE_BYTES ] ,
109- data: & [ u8 ] ,
110- ) -> Result <Vec <u8 >, Error > {
110+ data: & mut [ u8 ] ,
111+ ) -> Result <usize , Error > {
112+ let plaintext_len = data. len( ) - TAG_SIZE ;
113+ let tag: [ u8 ; TAG_SIZE ] = data[ plaintext_len..]
114+ . try_into( )
115+ . map_err( |_| Error :: DecryptionFailed ) ?;
111116 self . 0
112- . decrypt( nonce. into( ) , data)
113- . map_err( |_| Error :: DecryptionFailed )
117+ . decrypt_in_place_detached(
118+ nonce. into( ) ,
119+ & [ ] ,
120+ & mut data[ ..plaintext_len] ,
121+ & tag. into( ) ,
122+ )
123+ . map_err( |_| Error :: DecryptionFailed ) ?;
124+ Ok ( plaintext_len)
114125 }
115126 }
116127 }
@@ -133,10 +144,23 @@ impl SendCipher {
133144 }
134145 }
135146
147+ /// Encrypts `data` in-place and returns the authentication tag.
148+ ///
149+ /// The caller is responsible for appending the returned tag to the buffer.
150+ #[ inline]
151+ pub fn send_in_place ( & mut self , data : & mut [ u8 ] ) -> Result < [ u8 ; TAG_SIZE ] , Error > {
152+ let nonce = self . nonce . inc ( ) ?;
153+ self . inner
154+ . expose ( |cipher| cipher. encrypt_in_place ( & nonce, data) )
155+ }
156+
136157 /// Encrypts data and returns the ciphertext.
137158 pub fn send ( & mut self , data : & [ u8 ] ) -> Result < Vec < u8 > , Error > {
138- let nonce = self . nonce . inc ( ) ?;
139- self . inner . expose ( |cipher| cipher. encrypt ( & nonce, data) )
159+ let mut buf = vec ! [ 0u8 ; data. len( ) + TAG_SIZE ] ;
160+ buf[ ..data. len ( ) ] . copy_from_slice ( data) ;
161+ let tag = self . send_in_place ( & mut buf[ ..data. len ( ) ] ) ?;
162+ buf[ data. len ( ) ..] . copy_from_slice ( & tag) ;
163+ Ok ( buf)
140164 }
141165}
142166
@@ -157,6 +181,33 @@ impl RecvCipher {
157181 }
158182 }
159183
184+ /// Decrypts `encrypted_data` in-place and returns the plaintext length.
185+ ///
186+ /// The buffer must contain ciphertext with the authentication tag appended
187+ /// (last `TAG_SIZE` bytes). After decryption, the plaintext is in
188+ /// `encrypted_data[..returned_len]`.
189+ ///
190+ /// # Errors
191+ ///
192+ /// Returns an error if:
193+ /// - `encrypted_data.len() < TAG_SIZE`
194+ /// - Too many messages have been received with this cipher
195+ /// - The ciphertext was corrupted or tampered with
196+ ///
197+ /// In the last two cases, the `RecvCipher` will no longer be able to return
198+ /// valid ciphertexts, and will always return an error on subsequent calls
199+ /// to [`Self::recv`]. Terminating (and optionally reestablishing) the connection
200+ /// is a simple (and safe) way to handle this scenario.
201+ #[ inline]
202+ pub fn recv_in_place ( & mut self , encrypted_data : & mut [ u8 ] ) -> Result < usize , Error > {
203+ if encrypted_data. len ( ) < TAG_SIZE {
204+ return Err ( Error :: DecryptionFailed ) ;
205+ }
206+ let nonce = self . nonce . inc ( ) ?;
207+ self . inner
208+ . expose ( |cipher| cipher. decrypt_in_place ( & nonce, encrypted_data) )
209+ }
210+
160211 /// Decrypts ciphertext and returns the original data.
161212 ///
162213 /// # Errors
@@ -171,9 +222,10 @@ impl RecvCipher {
171222 /// to [`Self::recv`]. Terminating (and optionally reestablishing) the connection
172223 /// is a simple (and safe) way to handle this scenario.
173224 pub fn recv ( & mut self , encrypted_data : & [ u8 ] ) -> Result < Vec < u8 > , Error > {
174- let nonce = self . nonce . inc ( ) ?;
175- self . inner
176- . expose ( |cipher| cipher. decrypt ( & nonce, encrypted_data) )
225+ let mut buf = encrypted_data. to_vec ( ) ;
226+ let plaintext_len = self . recv_in_place ( & mut buf) ?;
227+ buf. truncate ( plaintext_len) ;
228+ Ok ( buf)
177229 }
178230}
179231
@@ -189,7 +241,7 @@ mod tests {
189241
190242 let plaintext = b"hello world" ;
191243 let ciphertext = send. send ( plaintext) . unwrap ( ) ;
192- assert_eq ! ( ciphertext. len( ) , plaintext. len( ) + CIPHERTEXT_OVERHEAD ) ;
244+ assert_eq ! ( ciphertext. len( ) , plaintext. len( ) + TAG_SIZE ) ;
193245
194246 let decrypted = recv. recv ( & ciphertext) . unwrap ( ) ;
195247 assert_eq ! ( decrypted, plaintext) ;
@@ -211,7 +263,7 @@ mod tests {
211263 fn test_recv_ciphertext_too_short ( ) {
212264 let mut rng = test_rng ( ) ;
213265 let mut recv = RecvCipher :: new ( & mut rng) ;
214- let short_data = vec ! [ 0u8 ; CIPHERTEXT_OVERHEAD - 1 ] ;
266+ let short_data = vec ! [ 0u8 ; TAG_SIZE - 1 ] ;
215267 assert ! ( matches!(
216268 recv. recv( & short_data) ,
217269 Err ( Error :: DecryptionFailed )
@@ -222,7 +274,70 @@ mod tests {
222274 fn test_recv_ciphertext_exactly_overhead ( ) {
223275 let mut rng = test_rng ( ) ;
224276 let mut recv = RecvCipher :: new ( & mut rng) ;
225- let tag_only = vec ! [ 0u8 ; CIPHERTEXT_OVERHEAD ] ;
277+ let tag_only = vec ! [ 0u8 ; TAG_SIZE ] ;
226278 assert ! ( matches!( recv. recv( & tag_only) , Err ( Error :: DecryptionFailed ) ) ) ;
227279 }
280+
281+ #[ test]
282+ fn test_send_recv_in_place_roundtrip ( ) {
283+ let mut send = SendCipher :: new ( & mut test_rng ( ) ) ;
284+ let mut recv = RecvCipher :: new ( & mut test_rng ( ) ) ;
285+
286+ let plaintext = b"hello world" ;
287+ let mut buf = vec ! [ 0u8 ; plaintext. len( ) + TAG_SIZE ] ;
288+ buf[ ..plaintext. len ( ) ] . copy_from_slice ( plaintext) ;
289+
290+ // Encrypt plaintext in place, get tag back
291+ let tag = send. send_in_place ( & mut buf[ ..plaintext. len ( ) ] ) . unwrap ( ) ;
292+ // Append tag to buffer
293+ buf[ plaintext. len ( ) ..] . copy_from_slice ( & tag) ;
294+
295+ // Decrypt ciphertext+tag in place, get plaintext length back
296+ let plaintext_len = recv. recv_in_place ( & mut buf) . unwrap ( ) ;
297+
298+ assert_eq ! ( plaintext_len, plaintext. len( ) ) ;
299+ assert_eq ! ( & buf[ ..plaintext_len] , plaintext) ;
300+ }
301+
302+ #[ test]
303+ fn test_recv_in_place_ciphertext_too_short ( ) {
304+ let mut recv = RecvCipher :: new ( & mut test_rng ( ) ) ;
305+
306+ // Buffer smaller than tag size
307+ let mut buf = vec ! [ 0u8 ; TAG_SIZE - 1 ] ;
308+ assert ! ( matches!(
309+ recv. recv_in_place( & mut buf) ,
310+ Err ( Error :: DecryptionFailed )
311+ ) ) ;
312+ }
313+
314+ #[ test]
315+ fn test_send_in_place_recv_compatibility ( ) {
316+ let mut send = SendCipher :: new ( & mut test_rng ( ) ) ;
317+ let mut recv = RecvCipher :: new ( & mut test_rng ( ) ) ;
318+
319+ let plaintext = b"cross-api test" ;
320+ let mut buf = vec ! [ 0u8 ; plaintext. len( ) + TAG_SIZE ] ;
321+ buf[ ..plaintext. len ( ) ] . copy_from_slice ( plaintext) ;
322+
323+ let tag = send. send_in_place ( & mut buf[ ..plaintext. len ( ) ] ) . unwrap ( ) ;
324+ buf[ plaintext. len ( ) ..] . copy_from_slice ( & tag) ;
325+
326+ // Use allocating recv on in-place encrypted data
327+ let decrypted = recv. recv ( & buf) . unwrap ( ) ;
328+ assert_eq ! ( decrypted, plaintext) ;
329+ }
330+
331+ #[ test]
332+ fn test_send_recv_in_place_compatibility ( ) {
333+ let mut send = SendCipher :: new ( & mut test_rng ( ) ) ;
334+ let mut recv = RecvCipher :: new ( & mut test_rng ( ) ) ;
335+
336+ let plaintext = b"cross-api test" ;
337+ let mut ciphertext = send. send ( plaintext) . unwrap ( ) ;
338+
339+ // Use in-place recv on allocating send data
340+ let plaintext_len = recv. recv_in_place ( & mut ciphertext) . unwrap ( ) ;
341+ assert_eq ! ( & ciphertext[ ..plaintext_len] , plaintext) ;
342+ }
228343}
0 commit comments