master
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}