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}