55// AEAD_CHACHA20_POLY1305 is an authenticated encryption with additional data algorithm.
66// The inputs to AEAD_CHACHA20_POLY1305 are:
77// A 256-bit key
8- // A 96 -bit nonce (or bigger 192 bit nonce) -- different for each invocation with the same key
8+ // A 64 -bit nonce, 96-bit (or bigger 192 bit nonce) -- different for each invocation with the same key
99// An arbitrary length plaintext
1010// Arbitrary length additional authenticated data (AAD)
1111module chacha20poly1305
@@ -41,55 +41,61 @@ pub interface AEAD {
4141
4242// key_size is the size of key (in bytes) which the Chacha20Poly1305 AEAD accepts.
4343pub const key_size = 32
44+
45+ // orig_nonce_size is the size (in bytes) of nonce of the original (DJ Bernstein) variant
46+ // which the Chacha20Poly1305 AEAD accepts.
47+ pub const orig_nonce_size = 8
4448// nonce_size is the size of the standard nonce (in bytes) which the Chacha20Poly1305 AEAD accepts.
4549pub const nonce_size = 12
4650// nonce_size is the size of the extended nonce (in bytes) which the Chacha20Poly1305 AEAD accepts.
4751pub const x_nonce_size = 24
52+
4853// tag_size is the size of the message authenticated code (in bytes) produced by Chacha20Poly1305 AEAD.
4954pub const tag_size = 16
5055
5156// encrypt does one-shot encryption of given plaintext with associated key, nonce and additional data.
5257// It return ciphertext output and authenticated tag appended into it.
53- pub fn encrypt (plaintext []u8 , key []u8 , nonce []u8 , ad []u8 ) ! []u8 {
54- mut c := new (key, nonce.len)!
55- out := c.encrypt (plaintext, nonce, ad)!
56- return out
58+ pub fn encrypt (plaintext []u8 , key []u8 , nonce []u8 , ad []u8 , opt chacha20 .Options) ! []u8 {
59+ mut c := new (key, nonce.len, opt)!
60+ return c.encrypt (plaintext, nonce, ad)!
5761}
5862
5963// decrypt does one-shot decryption of given ciphertext with associated key, nonce and additional data.
6064// It return plaintext output and verify if resulting tag is a valid message authenticated code (mac)
6165// for given message, key and additional data.
62- pub fn decrypt (ciphertext []u8 , key []u8 , nonce []u8 , ad []u8 ) ! []u8 {
63- mut c := new (key, nonce.len)!
64- out := c.decrypt (ciphertext, nonce, ad)!
65- return out
66+ pub fn decrypt (ciphertext []u8 , key []u8 , nonce []u8 , ad []u8 , opt chacha20 .Options) ! []u8 {
67+ mut c := new (key, nonce.len, opt)!
68+ return c.decrypt (ciphertext, nonce, ad)!
6669}
6770
6871// Chacha20Poly1305 represents AEAD algorithm backed by `x.crypto.chacha20` and `x.crypto.poly1305`.
72+ @[noinit]
6973struct Chacha20Poly1305 {
70- key []u8 = []u8 {len: key_size}
71- ncsize int = nonce_size
74+ mut :
75+ key []u8 = []u8 {len: key_size}
76+ nsize int
77+ opt chacha20 .Options // for XChaCha20 construct
7278}
7379
7480// new creates a new Chacha20Poly1305 AEAD instance with given 32 bytes of key
75- // and the nonce size in ncsize . The ncsize should be 12 or 24 length, otherwise it would return error.
76- pub fn new (key []u8 , ncsize int ) ! & AEAD {
81+ // and the nonce size in nsize . The nsize should be 8, 12 or 24 length, otherwise it would return error.
82+ pub fn new (key []u8 , nsize int , opt chacha 20 .Options ) ! & AEAD {
7783 if key.len != key_size {
7884 return error ('chacha20poly1305: bad key size' )
7985 }
80- if ncsize != nonce_size && ncsize != x_nonce_size {
81- return error ('chacha20poly1305: bad nonce size supplied, its should 12 or 24' )
86+ if nsize != orig_nonce_size && nsize != nonce_size && nsize != x_nonce_size {
87+ return error ('chacha20poly1305: bad nonce size supplied, its should 8, 12 or 24' )
8288 }
83- c := & Chacha20 Poly1305 {
84- key: key
85- ncsize: ncsize
89+ return & Chacha20 Poly1305 {
90+ key: key
91+ nsize: nsize
92+ opt: opt
8693 }
87- return c
8894}
8995
9096// nonce_size returns the size of underlying nonce (in bytes) of AEAD algorithm.
9197pub fn (c Chacha20Poly1305) nonce_size () int {
92- return c.ncsize
98+ return c.nsize
9399}
94100
95101// overhead returns maximum difference between the lengths of a plaintext to be encrypted and
@@ -116,42 +122,7 @@ pub fn (c Chacha20Poly1305) encrypt(plaintext []u8, nonce []u8, ad []u8) ![]u8 {
116122 if ad.len > max_u64 {
117123 return error ('chacha20poly1305: something bad in your additional data' )
118124 }
119- return c.encrypt_generic (plaintext, nonce, ad)
120- }
121-
122- // encrypt_generic encrypts plaintext along with nonce and additional data
123- fn (c Chacha20Poly1305) encrypt_generic (plaintext []u8 , nonce []u8 , ad []u8 ) ! []u8 {
124- // First, generates a Poly1305 one-time key from the 256-bit key
125- // and given nonce. Actually its generates by performing ChaCha20 key stream function,
126- // and take the first 32 bytes as a one-time key for Poly1305 from 64 bytes results.
127- // see https://datatracker.ietf.org/doc/html/rfc8439#section-2.6
128- mut polykey := []u8 {len: key_size}
129- mut s := chacha20 .new_cipher (c.key, nonce)!
130- s.encrypt (mut polykey, polykey)!
131-
132- // Next, the ChaCha20 encryption function is called to encrypt the plaintext,
133- // using the same key and nonce, and with the initial ChaCha20 counter set to 1.
134- mut ciphertext := []u8 {len: plaintext.len}
135- s.set_counter (1 )
136- s.encrypt (mut ciphertext, plaintext)!
137-
138- // Finally, the Poly1305 function is called with the generated Poly1305 one-time key
139- // calculated above, and a message constructed as described in
140- // https://datatracker.ietf.org/doc/html/rfc8439#section-2.8
141- mut constructed_msg := []u8 {}
142- poly1305_construct_msg (mut constructed_msg, ad, ciphertext)
143-
144- // Lets creates Poly1305 instance with one-time key generates in above step,
145- // updates Poly1305 state with this constructed_msg and finally generates tag.
146- mut tag := []u8 {len: tag_size}
147- mut po := poly1305 .new (polykey)!
148- po.update (constructed_msg)
149- po.finish (mut tag)
150-
151- // add this tag to ciphertext output
152- ciphertext << tag
153-
154- return ciphertext
125+ return c.generic_crypt (plaintext, nonce, ad, .encrypt)!
155126}
156127
157128// decrypt decrypts ciphertext along with provided nonce and additional data.
@@ -162,6 +133,10 @@ fn (c Chacha20Poly1305) encrypt_generic(plaintext []u8, nonce []u8, ad []u8) ![]
162133// The calculated mac is bitwise compared to the received mac.
163134// The message is authenticated if and only if the tags match, return error if failed to verify.
164135pub fn (c Chacha20Poly1305) decrypt (ciphertext []u8 , nonce []u8 , ad []u8 ) ! []u8 {
136+ // Preliminary check
137+ if ciphertext.len < tag_size {
138+ return error ('chacha20poly1305: ciphertext length does not meet minimum required length' )
139+ }
165140 if nonce.len != c.nonce_size () {
166141 return error ('chacha20poly1305: unmatching nonce size' )
167142 }
@@ -170,74 +145,128 @@ pub fn (c Chacha20Poly1305) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8
170145 if u64 (ciphertext.len) > (u64 (1 ) << 38 ) - 48 {
171146 return error ('chacha20poly1305: ciphertext too large' )
172147 }
173- return c.decrypt_generic (ciphertext, nonce, ad)
148+ return c.generic_crypt (ciphertext, nonce, ad, .decrypt) !
174149}
175150
176- fn (c Chacha20Poly1305) decrypt_generic (ciphertext []u8 , nonce []u8 , ad []u8 ) ! []u8 {
177- // generates poly1305 one-time key for later calculation
178- mut polykey := []u8 {len: key_size}
179- mut s := chacha20 .new_cipher (c.key, nonce)!
180- s.encrypt (mut polykey, polykey)!
151+ // Helpers
181152
182- // Remember, ciphertext is concatenation of associated cipher output plus tag (mac) bytes
183- encrypted := ciphertext[0 ..ciphertext.len - c.overhead ()]
184- mac := ciphertext[ciphertext.len - c.overhead ()..]
153+ // generic_crypt direction
154+ enum Mode {
155+ encrypt = 0
156+ decrypt = 1
157+ }
185158
186- mut plaintext := []u8 {len: encrypted.len}
187- s.set_counter (1 )
188- // doing reverse encrypt on cipher output part produces plaintext
189- s.encrypt (mut plaintext, encrypted)!
159+ // generic_crypt does generic encryption or decryption based on the mode flag was passed.
160+ // See AEAD Construction at https://datatracker.ietf.org/doc/html/rfc8439#section-2.8
161+ @[direct_array_access; inline]
162+ fn (c Chacha20Poly1305) generic_crypt (msg []u8 , nonce []u8 , ad []u8 , mode Mode) ! []u8 {
163+ // Setup some values
164+ mut src := unsafe { msg }
165+ mut mac := []u8 {} // used in decryption
166+ if mode == .decrypt {
167+ src = unsafe { msg[0 ..msg.len - c.overhead ()] }
168+ mac = unsafe { msg[msg.len - c.overhead ()..] }
169+ }
170+
171+ // generates 32-bytes of one-time key for later poly1305 operation
172+ mut otkey := []u8 {len: key_size}
173+ mut s := chacha20 .new_cipher (c.key, nonce, c.opt)!
174+ s.encrypt (mut otkey, otkey)!
175+
176+ // destination buffer, with overhead spaces for generated tag without reallocating
177+ mut dst := []u8 {len: src.len, cap: src.len + c.overhead ()}
190178
191- // authenticated messages part
192- mut constructed_msg := []u8 {}
193- poly1305_construct_msg (mut constructed_msg, ad, encrypted)
179+ // Next, the ChaCha20 encryption function is called to encrypt (decrypt) message input,
180+ // using the same key and nonce, and with the initial counter set to 1.
181+ s.set_counter (1 )
182+ s.encrypt (mut dst, src)!
194183
184+ // Finally, the Poly1305 function is called with the Poly1305 key calculated above
185+ // to build message authentication code (tag).
186+ // length of constructed message
187+ cm_length := if mode == .encrypt {
188+ length_constructed_msg (ad, dst)
189+ } else {
190+ length_constructed_msg (ad, src)
191+ }
192+ mut constructed_msg := []u8 {len: cm_length}
193+ if mode == .encrypt {
194+ construct_msg (mut constructed_msg, ad, dst)
195+ } else {
196+ construct_msg (mut constructed_msg, ad, src)
197+ }
195198 mut tag := []u8 {len: tag_size}
196- mut po := poly1305 .new (polykey )!
199+ mut po := poly1305 .new (otkey )!
197200 po.update (constructed_msg)
198201 po.finish (mut tag)
199202
200- // lets verify if received mac is matching with calculated tag,
201- // return error on fail and free allocated resource.
202- if subtle.constant_time_compare (mac, tag) != 1 {
203- // free allocated resource
204- unsafe {
205- s.free ()
206- tag.free ()
207- polykey.free ()
208- plaintext.free ()
203+ // If this a decryption mode, lets verify whether this calculated tag was matching
204+ // with the supplied mac, otherwise return error on fails and free allocated resources.
205+ if mode == .decrypt {
206+ if subtle.constant_time_compare (mac, tag) != 1 {
207+ // free allocated resource
208+ unsafe {
209+ s.free ()
210+ tag.free ()
211+ otkey.free ()
212+ dst.free ()
213+ constructed_msg.free ()
214+ }
215+ return error ('chacha20poly1305: unmatching tag' )
216+ } else {
217+ // return the decrypted message (plaintext) when the tag was matching
218+ return dst
209219 }
210- return error ('chacha20poly1305: unmatching tag' )
211220 }
212-
213- return plaintext
221+ // In the encryption mode, appends the tag into end of destination buffer
222+ dst << tag
223+ return dst
214224}
215225
216- // Helper function
217-
218226// pad x to 16 bytes block
227+ @[direct_array_access; inline]
219228fn pad_to_16 (x []u8 ) []u8 {
220- mut buf := x.clone ()
221- if buf.len % 16 == 0 {
222- return buf
229+ if x.len % 16 == 0 {
230+ return x
223231 }
224- pad_bytes := []u8 {len: 16 - buf.len % 16 , init: 0 }
225- buf << pad_bytes
226- return buf
232+ mut out := []u8 {len: x.len + (16 - x.len % 16 )}
233+ _ := copy (mut out, x)
234+ return out
235+ }
236+
237+ // The length of padded x
238+ @[inline]
239+ fn length_pad_to_16 (x []u8 ) int {
240+ if x.len % 16 == 0 {
241+ return x.len
242+ }
243+ //
244+ return x.len + (16 - x.len % 16 )
245+ }
246+
247+ // The length of constructed message
248+ @[inline]
249+ fn length_constructed_msg (ad []u8 , bytes []u8 ) int {
250+ mut n := 0
251+ n + = length_pad_to_16 (ad)
252+ n + = length_pad_to_16 (bytes)
253+ n + = 16 // 2 * 8 bytes
254+ return n
227255}
228256
229- // poly1305_construct_msg constructs poly1305 message for later usage and stores to out.
257+ // construct_msg builds a message for later usage and stored into out.
258+ // The last step on the AEAD Construction on the how the message was constructed.
259+ // See the details on the [2.8](https://datatracker.ietf.org/doc/html/rfc8439#section-2.8)
230260// The message constructed as a concatenation of the following:
231261// * padded to multiple of 16 bytes block of the additional data bytes
232262// * padded to multiple of 16 bytes block of the ciphertext (or plaintext) bytes
233263// * The length of the additional data in octets (as a 64-bit little-endian integer).
234264// * The length of the ciphertext (or plaintext) in octets (as a 64-bit little-endian integer).
235- fn poly1305_construct_msg (mut out []u8 , ad []u8 , bytes []u8 ) {
236- mut b8 := []u8 {len: 8 }
237- out << pad_to_16 (ad)
238- out << pad_to_16 (bytes)
239- binary.little_endian_put_u64 (mut b8 , u64 (ad.len))
240- out << b8
241- binary.little_endian_put_u64 (mut b8 , u64 (bytes.len))
242- out << b8
265+ // Assumed the output buffer length was correctly initialized.
266+ @[direct_array_access; inline]
267+ fn construct_msg (mut out []u8 , ad []u8 , bytes []u8 ) {
268+ n0 := copy (mut out, pad_to_16 (ad))
269+ n1 := copy (mut out[n0 ..], pad_to_16 (bytes))
270+ binary.little_endian_put_u64 (mut out[n0 + n1 ..], u64 (ad.len))
271+ binary.little_endian_put_u64 (mut out[n0 + n1 + 8 ..], u64 (bytes.len))
243272}
0 commit comments