1//! AES-CCM (Counter with CBC-MAC) authenticated encryption.
2//! AES-CCM* extends CCM to support encryption-only mode (tag_len=0).
3//!
4//! References:
5//! - NIST SP 800-38C: https://csrc.nist.gov/publications/detail/sp/800-38c/final
6//! - RFC 3610: https://datatracker.ietf.org/doc/html/rfc3610
7
8const std = @import("std");
9const assert = std.debug.assert;
10const crypto = std.crypto;
11const mem = std.mem;
12const modes = crypto.core.modes;
13const AuthenticationError = crypto.errors.AuthenticationError;
14const cbc_mac = @import("cbc_mac.zig");
15
16/// AES-128-CCM* with no authentication (encryption-only, 13-byte nonce).
17pub const Aes128Ccm0 = AesCcm(crypto.core.aes.Aes128, 0, 13);
18/// AES-128-CCM with 8-byte authentication tag and 13-byte nonce.
19pub const Aes128Ccm8 = AesCcm(crypto.core.aes.Aes128, 8, 13);
20/// AES-128-CCM with 16-byte authentication tag and 13-byte nonce.
21pub const Aes128Ccm16 = AesCcm(crypto.core.aes.Aes128, 16, 13);
22/// AES-256-CCM* with no authentication (encryption-only, 13-byte nonce).
23pub const Aes256Ccm0 = AesCcm(crypto.core.aes.Aes256, 0, 13);
24/// AES-256-CCM with 8-byte authentication tag and 13-byte nonce.
25pub const Aes256Ccm8 = AesCcm(crypto.core.aes.Aes256, 8, 13);
26/// AES-256-CCM with 16-byte authentication tag and 13-byte nonce.
27pub const Aes256Ccm16 = AesCcm(crypto.core.aes.Aes256, 16, 13);
28
29/// AES-CCM authenticated encryption (NIST SP 800-38C, RFC 3610).
30/// CCM* mode extends CCM to support encryption-only mode when tag_len=0.
31///
32/// `BlockCipher`: Block cipher type (must have 16-byte blocks).
33/// `tag_len`: Authentication tag length in bytes (0, 4, 6, 8, 10, 12, 14, or 16).
34/// When tag_len=0, CCM* provides encryption-only (no authentication).
35/// `nonce_len`: Nonce length in bytes (7 to 13).
36fn AesCcm(comptime BlockCipher: type, comptime tag_len: usize, comptime nonce_len: usize) type {
37 const block_length = BlockCipher.block.block_length;
38
39 comptime {
40 assert(block_length == 16); // CCM requires 16-byte blocks
41 if (tag_len != 0 and (tag_len < 4 or tag_len > 16 or tag_len % 2 != 0)) {
42 @compileError("CCM tag_length must be 0, 4, 6, 8, 10, 12, 14, or 16 bytes");
43 }
44 if (nonce_len < 7 or nonce_len > 13) {
45 @compileError("CCM nonce_length must be between 7 and 13 bytes");
46 }
47 }
48
49 const L = 15 - nonce_len; // Counter size in bytes (2 to 8)
50
51 return struct {
52 pub const key_length = BlockCipher.key_bits / 8;
53 pub const tag_length = tag_len;
54 pub const nonce_length = nonce_len;
55
56 /// `c`: Ciphertext output buffer (must be same length as m).
57 /// `tag`: Authentication tag output.
58 /// `m`: Plaintext message to encrypt.
59 /// `ad`: Associated data to authenticate.
60 /// `npub`: Public nonce (must be unique for each message with same key).
61 /// `key`: Encryption key.
62 pub fn encrypt(
63 c: []u8,
64 tag: *[tag_length]u8,
65 m: []const u8,
66 ad: []const u8,
67 npub: [nonce_length]u8,
68 key: [key_length]u8,
69 ) void {
70 assert(c.len == m.len);
71
72 // Validate message length fits in L bytes
73 const max_msg_len: u64 = if (L >= 8) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(L * 8))) - 1;
74 assert(m.len <= max_msg_len);
75
76 const cipher_ctx = BlockCipher.initEnc(key);
77
78 // CCM*: Skip authentication if tag_length is 0 (encryption-only mode)
79 if (tag_length > 0) {
80 // Compute CBC-MAC using the reusable CBC-MAC module
81 var mac_result: [block_length]u8 = undefined;
82 computeCbcMac(&mac_result, &key, m, ad, npub);
83
84 // Construct counter block for tag encryption (counter = 0)
85 var ctr_block: [block_length]u8 = undefined;
86 formatCtrBlock(&ctr_block, npub, 0);
87
88 // Encrypt the MAC tag
89 var s0: [block_length]u8 = undefined;
90 cipher_ctx.encrypt(&s0, &ctr_block);
91 for (tag, mac_result[0..tag_length], s0[0..tag_length]) |*t, mac_byte, s_byte| {
92 t.* = mac_byte ^ s_byte;
93 }
94
95 crypto.secureZero(u8, &mac_result);
96 crypto.secureZero(u8, &s0);
97 }
98
99 // Encrypt the plaintext using CTR mode (starting from counter = 1)
100 var ctr_block: [block_length]u8 = undefined;
101 formatCtrBlock(&ctr_block, npub, 1);
102 // CCM counter is in the last L bytes of the block
103 modes.ctrSlice(@TypeOf(cipher_ctx), cipher_ctx, c, m, ctr_block, .big, 1 + nonce_len, L);
104 }
105
106 /// `m`: Plaintext output buffer (must be same length as c).
107 /// `c`: Ciphertext to decrypt.
108 /// `tag`: Authentication tag to verify.
109 /// `ad`: Associated data (must match encryption).
110 /// `npub`: Public nonce (must match encryption).
111 /// `key`: Private key.
112 ///
113 /// Asserts `c.len == m.len`.
114 /// Contents of `m` are undefined if an error is returned.
115 pub fn decrypt(
116 m: []u8,
117 c: []const u8,
118 tag: [tag_length]u8,
119 ad: []const u8,
120 npub: [nonce_length]u8,
121 key: [key_length]u8,
122 ) AuthenticationError!void {
123 assert(m.len == c.len);
124
125 const max_msg_len: u64 = if (L >= 8) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(L * 8))) - 1;
126 if (c.len > max_msg_len) return error.AuthenticationFailed;
127
128 const cipher_ctx = BlockCipher.initEnc(key);
129
130 // Decrypt the ciphertext using CTR mode (starting from counter = 1)
131 var ctr_block: [block_length]u8 = undefined;
132 formatCtrBlock(&ctr_block, npub, 1);
133 // CCM counter is in the last L bytes of the block
134 modes.ctrSlice(@TypeOf(cipher_ctx), cipher_ctx, m, c, ctr_block, .big, 1 + nonce_len, L);
135
136 // CCM*: Skip authentication if tag_length is 0 (encryption-only mode)
137 if (tag_length > 0) {
138 // Compute CBC-MAC over decrypted plaintext
139 var mac_result: [block_length]u8 = undefined;
140 computeCbcMac(&mac_result, &key, m, ad, npub);
141
142 // Decrypt the received tag
143 formatCtrBlock(&ctr_block, npub, 0);
144 var s0: [block_length]u8 = undefined;
145 cipher_ctx.encrypt(&s0, &ctr_block);
146
147 // Reconstruct the expected MAC
148 var expected_mac: [tag_length]u8 = undefined;
149 for (&expected_mac, mac_result[0..tag_length], s0[0..tag_length]) |*e, mac_byte, s_byte| {
150 e.* = mac_byte ^ s_byte;
151 }
152
153 // Constant-time tag comparison
154 const valid = crypto.timing_safe.eql([tag_length]u8, expected_mac, tag);
155 if (!valid) {
156 crypto.secureZero(u8, &expected_mac);
157 crypto.secureZero(u8, &mac_result);
158 crypto.secureZero(u8, &s0);
159 crypto.secureZero(u8, m);
160 return error.AuthenticationFailed;
161 }
162
163 crypto.secureZero(u8, &expected_mac);
164 crypto.secureZero(u8, &mac_result);
165 crypto.secureZero(u8, &s0);
166 }
167 }
168
169 /// Format the counter block for CTR mode
170 /// Counter block format: [flags | nonce | counter]
171 /// flags = L - 1
172 fn formatCtrBlock(block: *[block_length]u8, npub: [nonce_length]u8, counter: u64) void {
173 @memset(block, 0);
174 block[0] = L - 1; // flags
175 @memcpy(block[1..][0..nonce_length], &npub);
176 // Counter goes in the last L bytes
177 const CounterInt = @Int(.unsigned, L * 8);
178 mem.writeInt(CounterInt, block[1 + nonce_length ..][0..L], @as(CounterInt, @intCast(counter)), .big);
179 }
180
181 /// Compute CBC-MAC over the message and associated data.
182 /// CCM uses plain CBC-MAC, not CMAC (RFC 3610).
183 fn computeCbcMac(mac: *[block_length]u8, key: *const [key_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8) void {
184 const CbcMac = cbc_mac.CbcMac(BlockCipher);
185 var ctx = CbcMac.init(key);
186
187 // Process B_0 block
188 var b0: [block_length]u8 = undefined;
189 formatB0Block(&b0, m.len, ad.len, npub);
190 ctx.update(&b0);
191
192 // Process associated data if present
193 // RFC 3610: AD is (encoded_length || ad) padded to block boundary
194 if (ad.len > 0) {
195 // Encode and add associated data length
196 var ad_len_encoding: [10]u8 = undefined;
197 const ad_len_size = encodeAdLength(&ad_len_encoding, ad.len);
198
199 // Process AD with padding to block boundary
200 ctx.update(ad_len_encoding[0..ad_len_size]);
201 ctx.update(ad);
202
203 // Add zero padding to reach block boundary
204 const total_ad_size = ad_len_size + ad.len;
205 const remainder = total_ad_size % block_length;
206 if (remainder > 0) {
207 const padding: [block_length]u8 = @splat(0);
208 ctx.update(padding[0 .. block_length - remainder]);
209 }
210 }
211
212 // Process plaintext message
213 ctx.update(m);
214
215 // Finalize MAC
216 ctx.final(mac);
217 }
218
219 /// Format the B_0 block for CBC-MAC
220 /// B_0 format: [flags | nonce | message_length]
221 /// flags = 64*Adata + 8*M' + L'
222 /// where: Adata = (ad.len > 0), M' = (tag_length - 2)/2 if M>0 else 0, L' = L - 1
223 /// CCM*: When tag_length=0, M' is encoded as 0
224 fn formatB0Block(block: *[block_length]u8, msg_len: usize, ad_len: usize, npub: [nonce_length]u8) void {
225 @memset(block, 0);
226
227 const Adata: u8 = if (ad_len > 0) 1 else 0;
228 const M_prime: u8 = if (tag_length > 0) @intCast((tag_length - 2) / 2) else 0;
229 const L_prime: u8 = L - 1;
230
231 block[0] = (Adata << 6) | (M_prime << 3) | L_prime;
232 @memcpy(block[1..][0..nonce_length], &npub);
233
234 // Encode message length in last L bytes
235 const LengthInt = @Int(.unsigned, L * 8);
236 mem.writeInt(LengthInt, block[1 + nonce_length ..][0..L], @as(LengthInt, @intCast(msg_len)), .big);
237 }
238
239 /// Encode associated data length according to CCM specification
240 /// Returns the number of bytes written
241 fn encodeAdLength(buf: *[10]u8, ad_len: usize) usize {
242 if (ad_len < 65280) { // 2^16 - 2^8
243 // Encode as 2 bytes
244 mem.writeInt(u16, buf[0..2], @as(u16, @intCast(ad_len)), .big);
245 return 2;
246 } else if (ad_len <= std.math.maxInt(u32)) {
247 // Encode as 0xff || 0xfe || 4 bytes
248 buf[0] = 0xff;
249 buf[1] = 0xfe;
250 mem.writeInt(u32, buf[2..6], @as(u32, @intCast(ad_len)), .big);
251 return 6;
252 } else {
253 // Encode as 0xff || 0xff || 8 bytes
254 buf[0] = 0xff;
255 buf[1] = 0xff;
256 mem.writeInt(u64, buf[2..10], @as(u64, @intCast(ad_len)), .big);
257 return 10;
258 }
259 }
260 };
261}
262
263// Tests
264
265const testing = std.testing;
266const fmt = std.fmt;
267const hexToBytes = fmt.hexToBytes;
268
269test "Aes256Ccm8 - Encrypt decrypt round-trip" {
270 const key: [32]u8 = @splat(0x42);
271 const nonce: [13]u8 = @splat(0x11);
272 const m = "Hello, World! This is a test message.";
273 var c: [m.len]u8 = undefined;
274 var m2: [m.len]u8 = undefined;
275 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
276
277 Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
278
279 try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
280
281 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
282}
283
284test "Aes256Ccm8 - Associated data" {
285 const key: [32]u8 = @splat(0x42);
286 const nonce: [13]u8 = @splat(0x11);
287 const m = "secret message";
288 const ad = "additional authenticated data";
289 var c: [m.len]u8 = undefined;
290 var m2: [m.len]u8 = undefined;
291 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
292
293 Aes256Ccm8.encrypt(&c, &tag, m, ad, nonce, key);
294
295 try Aes256Ccm8.decrypt(&m2, &c, tag, ad, nonce, key);
296 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
297
298 var m3: [m.len]u8 = undefined;
299 const wrong_adata = "wrong data";
300 const result = Aes256Ccm8.decrypt(&m3, &c, tag, wrong_adata, nonce, key);
301 try testing.expectError(error.AuthenticationFailed, result);
302}
303
304test "Aes256Ccm8 - Wrong key" {
305 const key: [32]u8 = @splat(0x42);
306 const wrong_key: [32]u8 = @splat(0x43);
307 const nonce: [13]u8 = @splat(0x11);
308 const m = "secret";
309 var c: [m.len]u8 = undefined;
310 var m2: [m.len]u8 = undefined;
311 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
312
313 Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
314
315 const result = Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, wrong_key);
316 try testing.expectError(error.AuthenticationFailed, result);
317}
318
319test "Aes256Ccm8 - Corrupted ciphertext" {
320 const key: [32]u8 = @splat(0x42);
321 const nonce: [13]u8 = @splat(0x11);
322 const m = "secret message";
323 var c: [m.len]u8 = undefined;
324 var m2: [m.len]u8 = undefined;
325 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
326
327 Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
328
329 c[5] ^= 0xFF;
330
331 const result = Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
332 try testing.expectError(error.AuthenticationFailed, result);
333}
334
335test "Aes256Ccm8 - Empty plaintext" {
336 const key: [32]u8 = @splat(0x42);
337 const nonce: [13]u8 = @splat(0x11);
338 const m = "";
339 var c: [m.len]u8 = undefined;
340 var m2: [m.len]u8 = undefined;
341 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
342
343 Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
344
345 try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
346
347 try testing.expectEqual(@as(usize, 0), m2.len);
348}
349
350test "Aes128Ccm8 - Basic functionality" {
351 const key: [16]u8 = @splat(0x42);
352 const nonce: [13]u8 = @splat(0x11);
353 const m = "Test AES-128-CCM";
354 var c: [m.len]u8 = undefined;
355 var m2: [m.len]u8 = undefined;
356 var tag: [Aes128Ccm8.tag_length]u8 = undefined;
357
358 Aes128Ccm8.encrypt(&c, &tag, m, "", nonce, key);
359
360 try Aes128Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
361
362 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
363}
364
365test "Aes256Ccm16 - 16-byte tag" {
366 const key: [32]u8 = @splat(0x42);
367 const nonce: [13]u8 = @splat(0x11);
368 const m = "Test 16-byte tag";
369 var c: [m.len]u8 = undefined;
370 var m2: [m.len]u8 = undefined;
371 var tag: [Aes256Ccm16.tag_length]u8 = undefined;
372
373 Aes256Ccm16.encrypt(&c, &tag, m, "", nonce, key);
374
375 try testing.expectEqual(@as(usize, 16), tag.len);
376
377 try Aes256Ccm16.decrypt(&m2, &c, tag, "", nonce, key);
378
379 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
380}
381
382test "Aes256Ccm8 - Edge case short nonce" {
383 const Aes256Ccm8_7 = AesCcm(crypto.core.aes.Aes256, 8, 7);
384 var key: [32]u8 = undefined;
385 _ = try hexToBytes(&key, "eda32f751456e33195f1f499cf2dc7c97ea127b6d488f211ccc5126fbb24afa6");
386 var nonce: [7]u8 = undefined;
387 _ = try hexToBytes(&nonce, "a544218dadd3c1");
388 var m: [1]u8 = undefined;
389 _ = try hexToBytes(&m, "00");
390
391 var c: [m.len]u8 = undefined;
392 var tag: [Aes256Ccm8_7.tag_length]u8 = undefined;
393
394 Aes256Ccm8_7.encrypt(&c, &tag, &m, "", nonce, key);
395
396 var m2: [c.len]u8 = undefined;
397
398 try Aes256Ccm8_7.decrypt(&m2, &c, tag, "", nonce, key);
399 try testing.expectEqualSlices(u8, &m, &m2);
400}
401
402test "Aes256Ccm8 - Edge case long nonce" {
403 var key: [32]u8 = undefined;
404 _ = try hexToBytes(&key, "e1b8a927a95efe94656677b692662000278b441c79e879dd5c0ddc758bdc9ee8");
405 var nonce: [13]u8 = undefined;
406 _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf39");
407 var m: [1]u8 = undefined;
408 _ = try hexToBytes(&m, "00");
409
410 var c: [m.len]u8 = undefined;
411 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
412
413 Aes256Ccm8.encrypt(&c, &tag, &m, "", nonce, key);
414
415 var m2: [c.len]u8 = undefined;
416
417 try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
418 try testing.expectEqualSlices(u8, &m, &m2);
419}
420
421test "Aes256Ccm8 - With AAD and wrong AAD detection" {
422 var key: [32]u8 = undefined;
423 _ = try hexToBytes(&key, "8c5cf3457ff22228c39c051c4e05ed4093657eb303f859a9d4b0f8be0127d88a");
424 var nonce: [13]u8 = undefined;
425 _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf39");
426 var m: [1]u8 = undefined;
427 _ = try hexToBytes(&m, "00");
428 var ad: [32]u8 = undefined;
429 _ = try hexToBytes(&ad, "3c0e2815d37d844f7ac240ba9d6e3a0b2a86f706e885959e09a1005e024f6907");
430
431 var c: [m.len]u8 = undefined;
432 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
433
434 Aes256Ccm8.encrypt(&c, &tag, &m, &ad, nonce, key);
435
436 var m2: [c.len]u8 = undefined;
437
438 try Aes256Ccm8.decrypt(&m2, &c, tag, &ad, nonce, key);
439 try testing.expectEqualSlices(u8, &m, &m2);
440
441 var wrong_ad: [32]u8 = undefined;
442 _ = try hexToBytes(&wrong_ad, "0000000000000000000000000000000000000000000000000000000000000000");
443 var m3: [c.len]u8 = undefined;
444 const result = Aes256Ccm8.decrypt(&m3, &c, tag, &wrong_ad, nonce, key);
445 try testing.expectError(error.AuthenticationFailed, result);
446}
447
448test "Aes256Ccm8 - Multi-block payload" {
449 const Aes256Ccm8_12 = AesCcm(crypto.core.aes.Aes256, 8, 12);
450
451 // Test with 32-byte payload (2 AES blocks)
452 var key: [32]u8 = undefined;
453 _ = try hexToBytes(&key, "af063639e66c284083c5cf72b70d8bc277f5978e80d9322d99f2fdc718cda569");
454 var nonce: [12]u8 = undefined;
455 _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf");
456 var m: [32]u8 = undefined;
457 _ = try hexToBytes(&m, "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff");
458
459 // Encrypt
460 var c: [32]u8 = undefined;
461 var tag: [Aes256Ccm8_12.tag_length]u8 = undefined;
462
463 Aes256Ccm8_12.encrypt(&c, &tag, &m, "", nonce, key);
464
465 // Decrypt and verify
466 var m2: [32]u8 = undefined;
467
468 try Aes256Ccm8_12.decrypt(&m2, &c, tag, "", nonce, key);
469 try testing.expectEqualSlices(u8, &m, &m2);
470}
471
472test "Aes256Ccm8 - Multi-block with AAD" {
473 const Aes256Ccm8_12 = AesCcm(crypto.core.aes.Aes256, 8, 12);
474
475 // Test with multi-block payload (3 AES blocks) and AAD
476 var key: [32]u8 = undefined;
477 _ = try hexToBytes(&key, "f7079dfa3b5c7b056347d7e437bcded683abd6e2c9e069d333284082cbb5d453");
478 var nonce: [12]u8 = undefined;
479 _ = try hexToBytes(&nonce, "5b8e40746f6b98e00f1d13ff");
480
481 // 48-byte payload (3 AES blocks)
482 var m: [48]u8 = undefined;
483 _ = try hexToBytes(&m, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f");
484
485 // 16-byte AAD
486 var ad: [16]u8 = undefined;
487 _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f");
488
489 // Encrypt
490 var c: [48]u8 = undefined;
491 var tag: [Aes256Ccm8_12.tag_length]u8 = undefined;
492
493 Aes256Ccm8_12.encrypt(&c, &tag, &m, &ad, nonce, key);
494
495 // Decrypt and verify
496 var m2: [48]u8 = undefined;
497
498 try Aes256Ccm8_12.decrypt(&m2, &c, tag, &ad, nonce, key);
499 try testing.expectEqualSlices(u8, &m, &m2);
500}
501
502test "Aes256Ccm8 - Minimum nonce length" {
503 const Aes256Ccm8_7 = AesCcm(crypto.core.aes.Aes256, 8, 7);
504
505 // Test with 7-byte nonce (minimum allowed by CCM spec)
506 var key: [32]u8 = undefined;
507 _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f");
508 var nonce: [7]u8 = undefined;
509 _ = try hexToBytes(&nonce, "10111213141516");
510 const m = "Test message with minimum nonce length";
511
512 // Encrypt
513 var c: [m.len]u8 = undefined;
514 var tag: [Aes256Ccm8_7.tag_length]u8 = undefined;
515
516 Aes256Ccm8_7.encrypt(&c, &tag, m, "", nonce, key);
517
518 // Decrypt and verify
519 var m2: [m.len]u8 = undefined;
520
521 try Aes256Ccm8_7.decrypt(&m2, &c, tag, "", nonce, key);
522 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
523}
524
525test "Aes256Ccm8 - Maximum nonce length" {
526 // Test with 13-byte nonce (maximum allowed by CCM spec)
527 var key: [32]u8 = undefined;
528 _ = try hexToBytes(&key, "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f");
529 var nonce: [13]u8 = undefined;
530 _ = try hexToBytes(&nonce, "101112131415161718191a1b1c");
531 const m = "Test message with maximum nonce length";
532
533 // Encrypt
534 var c: [m.len]u8 = undefined;
535 var tag: [Aes256Ccm8.tag_length]u8 = undefined;
536
537 Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key);
538
539 // Decrypt and verify
540 var m2: [m.len]u8 = undefined;
541
542 try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key);
543 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
544}
545
546// RFC 3610 test vectors
547
548test "Aes128Ccm8 - RFC 3610 Packet Vector #1" {
549 const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13);
550
551 // RFC 3610 Appendix A, Packet Vector #1
552 var key: [16]u8 = undefined;
553 _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
554 var nonce: [13]u8 = undefined;
555 _ = try hexToBytes(&nonce, "00000003020100A0A1A2A3A4A5");
556 var ad: [8]u8 = undefined;
557 _ = try hexToBytes(&ad, "0001020304050607");
558 var plaintext: [23]u8 = undefined;
559 _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E");
560
561 // Expected ciphertext and tag from RFC
562 var expected_ciphertext: [23]u8 = undefined;
563 _ = try hexToBytes(&expected_ciphertext, "588C979A61C663D2F066D0C2C0F989806D5F6B61DAC384");
564 var expected_tag: [8]u8 = undefined;
565 _ = try hexToBytes(&expected_tag, "17E8D12CFDF926E0");
566
567 // Encrypt
568 var c: [plaintext.len]u8 = undefined;
569 var tag: [Aes128Ccm8_13.tag_length]u8 = undefined;
570
571 Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
572
573 // Verify ciphertext matches RFC expected output
574 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
575
576 // Verify tag matches RFC expected output
577 try testing.expectEqualSlices(u8, &expected_tag, &tag);
578
579 // Decrypt and verify round-trip
580 var m: [plaintext.len]u8 = undefined;
581 try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key);
582 try testing.expectEqualSlices(u8, &plaintext, &m);
583}
584
585test "Aes128Ccm8 - RFC 3610 Packet Vector #2" {
586 const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13);
587
588 // RFC 3610 Appendix A, Packet Vector #2 (8-byte tag, M=8)
589 var key: [16]u8 = undefined;
590 _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
591 var nonce: [13]u8 = undefined;
592 _ = try hexToBytes(&nonce, "00000004030201A0A1A2A3A4A5");
593 var ad: [8]u8 = undefined;
594 _ = try hexToBytes(&ad, "0001020304050607");
595 var plaintext: [24]u8 = undefined;
596 _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
597
598 // Expected ciphertext and tag from RFC (from total packet: header + ciphertext + tag)
599 var expected_ciphertext: [24]u8 = undefined;
600 _ = try hexToBytes(&expected_ciphertext, "72C91A36E135F8CF291CA894085C87E3CC15C439C9E43A3B");
601 var expected_tag: [8]u8 = undefined;
602 _ = try hexToBytes(&expected_tag, "A091D56E10400916");
603
604 // Encrypt
605 var c: [plaintext.len]u8 = undefined;
606 var tag: [Aes128Ccm8_13.tag_length]u8 = undefined;
607
608 Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
609
610 // Verify ciphertext matches RFC expected output
611 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
612
613 // Verify tag matches RFC expected output
614 try testing.expectEqualSlices(u8, &expected_tag, &tag);
615
616 // Decrypt and verify round-trip
617 var m: [plaintext.len]u8 = undefined;
618 try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key);
619 try testing.expectEqualSlices(u8, &plaintext, &m);
620}
621
622test "Aes128Ccm8 - RFC 3610 Packet Vector #3" {
623 const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13);
624
625 // RFC 3610 Appendix A, Packet Vector #3 (8-byte tag, 25-byte payload)
626 var key: [16]u8 = undefined;
627 _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
628 var nonce: [13]u8 = undefined;
629 _ = try hexToBytes(&nonce, "00000005040302A0A1A2A3A4A5");
630 var ad: [8]u8 = undefined;
631 _ = try hexToBytes(&ad, "0001020304050607");
632 var plaintext: [25]u8 = undefined;
633 _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20");
634
635 // Expected ciphertext and tag from RFC
636 var expected_ciphertext: [25]u8 = undefined;
637 _ = try hexToBytes(&expected_ciphertext, "51B1E5F44A197D1DA46B0F8E2D282AE871E838BB64DA859657");
638 var expected_tag: [8]u8 = undefined;
639 _ = try hexToBytes(&expected_tag, "4ADAA76FBD9FB0C5");
640
641 // Encrypt
642 var c: [plaintext.len]u8 = undefined;
643 var tag: [Aes128Ccm8_13.tag_length]u8 = undefined;
644
645 Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
646
647 // Verify ciphertext matches RFC expected output
648 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
649
650 // Verify tag matches RFC expected output
651 try testing.expectEqualSlices(u8, &expected_tag, &tag);
652
653 // Decrypt and verify round-trip
654 var m: [plaintext.len]u8 = undefined;
655 try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key);
656 try testing.expectEqualSlices(u8, &plaintext, &m);
657}
658
659// NIST SP 800-38C test vectors
660
661test "Aes128Ccm4 - NIST SP 800-38C Example 1" {
662 const Aes128Ccm4_7 = AesCcm(crypto.core.aes.Aes128, 4, 7);
663
664 // Example 1 (C.1): Klen=128, Tlen=32, Nlen=56, Alen=64, Plen=32
665 var key: [16]u8 = undefined;
666 _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
667 var nonce: [7]u8 = undefined;
668 _ = try hexToBytes(&nonce, "10111213141516");
669 var ad: [8]u8 = undefined;
670 _ = try hexToBytes(&ad, "0001020304050607");
671 var plaintext: [4]u8 = undefined;
672 _ = try hexToBytes(&plaintext, "20212223");
673
674 // Expected ciphertext and tag from NIST
675 var expected_ciphertext: [4]u8 = undefined;
676 _ = try hexToBytes(&expected_ciphertext, "7162015b");
677 var expected_tag: [4]u8 = undefined;
678 _ = try hexToBytes(&expected_tag, "4dac255d");
679
680 // Encrypt
681 var c: [plaintext.len]u8 = undefined;
682 var tag: [Aes128Ccm4_7.tag_length]u8 = undefined;
683
684 Aes128Ccm4_7.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
685
686 // Verify ciphertext matches NIST expected output
687 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
688
689 // Verify tag matches NIST expected output
690 try testing.expectEqualSlices(u8, &expected_tag, &tag);
691
692 // Decrypt and verify round-trip
693 var m: [plaintext.len]u8 = undefined;
694 try Aes128Ccm4_7.decrypt(&m, &c, tag, &ad, nonce, key);
695 try testing.expectEqualSlices(u8, &plaintext, &m);
696}
697
698test "Aes128Ccm6 - NIST SP 800-38C Example 2" {
699 const Aes128Ccm6_8 = AesCcm(crypto.core.aes.Aes128, 6, 8);
700
701 // Example 2 (C.2): Klen=128, Tlen=48, Nlen=64, Alen=128, Plen=128
702 var key: [16]u8 = undefined;
703 _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
704 var nonce: [8]u8 = undefined;
705 _ = try hexToBytes(&nonce, "1011121314151617");
706 var ad: [16]u8 = undefined;
707 _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f");
708 var plaintext: [16]u8 = undefined;
709 _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f");
710
711 // Expected ciphertext and tag from NIST
712 var expected_ciphertext: [16]u8 = undefined;
713 _ = try hexToBytes(&expected_ciphertext, "d2a1f0e051ea5f62081a7792073d593d");
714 var expected_tag: [6]u8 = undefined;
715 _ = try hexToBytes(&expected_tag, "1fc64fbfaccd");
716
717 // Encrypt
718 var c: [plaintext.len]u8 = undefined;
719 var tag: [Aes128Ccm6_8.tag_length]u8 = undefined;
720
721 Aes128Ccm6_8.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
722
723 // Verify ciphertext matches NIST expected output
724 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
725
726 // Verify tag matches NIST expected output
727 try testing.expectEqualSlices(u8, &expected_tag, &tag);
728
729 // Decrypt and verify round-trip
730 var m: [plaintext.len]u8 = undefined;
731 try Aes128Ccm6_8.decrypt(&m, &c, tag, &ad, nonce, key);
732 try testing.expectEqualSlices(u8, &plaintext, &m);
733}
734
735test "Aes128Ccm8 - NIST SP 800-38C Example 3" {
736 const Aes128Ccm8_12 = AesCcm(crypto.core.aes.Aes128, 8, 12);
737
738 // Example 3 (C.3): Klen=128, Tlen=64, Nlen=96, Alen=160, Plen=192
739 var key: [16]u8 = undefined;
740 _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
741 var nonce: [12]u8 = undefined;
742 _ = try hexToBytes(&nonce, "101112131415161718191a1b");
743 var ad: [20]u8 = undefined;
744 _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f10111213");
745 var plaintext: [24]u8 = undefined;
746 _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f3031323334353637");
747
748 // Expected ciphertext and tag from NIST
749 var expected_ciphertext: [24]u8 = undefined;
750 _ = try hexToBytes(&expected_ciphertext, "e3b201a9f5b71a7a9b1ceaeccd97e70b6176aad9a4428aa5");
751 var expected_tag: [8]u8 = undefined;
752 _ = try hexToBytes(&expected_tag, "484392fbc1b09951");
753
754 // Encrypt
755 var c: [plaintext.len]u8 = undefined;
756 var tag: [Aes128Ccm8_12.tag_length]u8 = undefined;
757
758 Aes128Ccm8_12.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
759
760 // Verify ciphertext matches NIST expected output
761 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
762
763 // Verify tag matches NIST expected output
764 try testing.expectEqualSlices(u8, &expected_tag, &tag);
765
766 // Decrypt and verify round-trip
767 var m: [plaintext.len]u8 = undefined;
768 try Aes128Ccm8_12.decrypt(&m, &c, tag, &ad, nonce, key);
769 try testing.expectEqualSlices(u8, &plaintext, &m);
770}
771
772test "Aes128Ccm14 - NIST SP 800-38C Example 4" {
773 const Aes128Ccm14_13 = AesCcm(crypto.core.aes.Aes128, 14, 13);
774
775 // Example 4 (C.4): Klen=128, Tlen=112, Nlen=104, Alen=524288, Plen=256
776 // Note: Associated data is 65536 bytes (256-byte pattern repeated 256 times)
777 var key: [16]u8 = undefined;
778 _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f");
779 var nonce: [13]u8 = undefined;
780 _ = try hexToBytes(&nonce, "101112131415161718191a1b1c");
781 var plaintext: [32]u8 = undefined;
782 _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
783
784 // Generate 65536-byte associated data (256-byte pattern repeated 256 times)
785 var pattern: [256]u8 = undefined;
786 _ = try hexToBytes(&pattern, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
787
788 var ad: [65536]u8 = undefined;
789 for (0..256) |i| {
790 @memcpy(ad[i * 256 .. (i + 1) * 256], &pattern);
791 }
792
793 // Expected ciphertext and tag from NIST
794 var expected_ciphertext: [32]u8 = undefined;
795 _ = try hexToBytes(&expected_ciphertext, "69915dad1e84c6376a68c2967e4dab615ae0fd1faec44cc484828529463ccf72");
796 var expected_tag: [14]u8 = undefined;
797 _ = try hexToBytes(&expected_tag, "b4ac6bec93e8598e7f0dadbcea5b");
798
799 // Encrypt
800 var c: [plaintext.len]u8 = undefined;
801 var tag: [Aes128Ccm14_13.tag_length]u8 = undefined;
802
803 Aes128Ccm14_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
804
805 // Verify ciphertext matches NIST expected output
806 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
807
808 // Verify tag matches NIST expected output
809 try testing.expectEqualSlices(u8, &expected_tag, &tag);
810
811 // Decrypt and verify round-trip
812 var m: [plaintext.len]u8 = undefined;
813 try Aes128Ccm14_13.decrypt(&m, &c, tag, &ad, nonce, key);
814 try testing.expectEqualSlices(u8, &plaintext, &m);
815}
816
817// CCM* test vectors (encryption-only mode with M=0)
818
819test "Aes128Ccm0 - IEEE 802.15.4 Data Frame (Encryption-only)" {
820 // IEEE 802.15.4 test vector from section 2.7
821 // Security level 0x04 (ENC, encryption without authentication)
822 var key: [16]u8 = undefined;
823 _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF");
824 var nonce: [13]u8 = undefined;
825 _ = try hexToBytes(&nonce, "ACDE48000000000100000005" ++ "04");
826 var plaintext: [4]u8 = undefined;
827 _ = try hexToBytes(&plaintext, "61626364");
828 var ad: [26]u8 = undefined;
829 _ = try hexToBytes(&ad, "69DC842143020000000048DEAC010000000048DEAC0405000000");
830
831 // Expected ciphertext from IEEE spec
832 var expected_ciphertext: [4]u8 = undefined;
833 _ = try hexToBytes(&expected_ciphertext, "D43E022B");
834
835 // Encrypt
836 var c: [plaintext.len]u8 = undefined;
837 var tag: [Aes128Ccm0.tag_length]u8 = undefined;
838
839 Aes128Ccm0.encrypt(&c, &tag, &plaintext, &ad, nonce, key);
840
841 // Verify ciphertext matches IEEE expected output
842 try testing.expectEqualSlices(u8, &expected_ciphertext, &c);
843
844 // Decrypt and verify round-trip
845 var m: [plaintext.len]u8 = undefined;
846 try Aes128Ccm0.decrypt(&m, &c, tag, &ad, nonce, key);
847 try testing.expectEqualSlices(u8, &plaintext, &m);
848}
849
850test "Aes128Ccm0 - Zero-length plaintext with encryption-only" {
851 const key: [16]u8 = @splat(0x42);
852 const nonce: [13]u8 = @splat(0x11);
853 const m = "";
854 const ad = "some associated data";
855 var c: [m.len]u8 = undefined;
856 var m2: [m.len]u8 = undefined;
857 var tag: [Aes128Ccm0.tag_length]u8 = undefined;
858
859 Aes128Ccm0.encrypt(&c, &tag, m, ad, nonce, key);
860
861 try Aes128Ccm0.decrypt(&m2, &c, tag, ad, nonce, key);
862
863 try testing.expectEqual(@as(usize, 0), m2.len);
864}
865
866test "Aes256Ccm0 - Basic encryption-only round-trip" {
867 const key: [32]u8 = @splat(0x42);
868 const nonce: [13]u8 = @splat(0x11);
869 const m = "Hello, CCM* encryption-only mode!";
870 var c: [m.len]u8 = undefined;
871 var m2: [m.len]u8 = undefined;
872 var tag: [Aes256Ccm0.tag_length]u8 = undefined;
873
874 Aes256Ccm0.encrypt(&c, &tag, m, "", nonce, key);
875
876 try Aes256Ccm0.decrypt(&m2, &c, tag, "", nonce, key);
877
878 try testing.expectEqualSlices(u8, m[0..], m2[0..]);
879}
880
881test "Aes256Ccm decryption of oversized ciphertext" {
882 const key: [32]u8 = @splat(0);
883 const nonce: [13]u8 = @splat(0);
884 const tag: [Aes256Ccm16.tag_length]u8 = @splat(0);
885 var buf: [65536]u8 = @splat(0);
886 try testing.expectError(error.AuthenticationFailed, Aes256Ccm16.decrypt(&buf, &buf, tag, "", nonce, key));
887}