authorgravatar for me@aarvay.comaarvay <me@aarvay.com> 2025-10-14 15:30:44+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-14 12:00:44+02:00
log2f3234c76ac8465719935bc6ae0e10e0fe59fccd
tree58da2925234966c98b4622c05c43e1796025f001
parent958faa7031c7b50a544e151fea4d486e1c4926c1
signaturebadge-check Signed by PGP key B5690EEEBB952194

std.crypto: add AES-CCM and CBC-MAC (#25526)

* std.crypto: add AES-CCM and CBC-MAC Add AES-CCM (Counter with CBC-MAC) authenticated encryption and CBC-MAC message authentication code implementations to the standard library. AES-CCM combines CTR mode encryption with CBC-MAC authentication as specified in NIST SP 800-38C and RFC 3610. It provides authenticated encryption with support for additional authenticated data (AAD). CBC-MAC is a simple MAC construction used internally by CCM, specified in FIPS 113 and ISO/IEC 9797-1. Includes comprehensive test vectors from RFC 3610 and NIST SP 800-38C. * std.crypto: add CCM* (encryption-only) support to AES-CCM Implements CCM* mode per IEEE 802.15.4 specification, extending AES-CCM to support encryption-only mode when tag_len=0. This is required by protocols like ZigBee, Thread, and WirelessHART. Changes: - Allow tag_len=0 for encryption-only mode (no authentication) - Skip CBC-MAC computation when tag_len=0 in encrypt/decrypt - Correctly encode M'=0 in B0 block for CCM* mode - Add Aes128Ccm0 and Aes256Ccm0 convenience instances - Add IEEE 802.15.4 test vectors and CCM* tests * std.crypto: add doc comments for AES-CCM variants

3 files changed, 1031 insertions(+), 0 deletions(-)

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