master
1const std = @import("std");
2const crypto = std.crypto;
3const mem = std.mem;
4
5/// CMAC with AES-128 - RFC 4493 https://www.rfc-editor.org/rfc/rfc4493
6pub const CmacAes128 = Cmac(crypto.core.aes.Aes128);
7
8/// NIST Special Publication 800-38B - The CMAC Mode for Authentication
9/// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38b.pdf
10pub fn Cmac(comptime BlockCipher: type) type {
11 const BlockCipherCtx = @typeInfo(@TypeOf(BlockCipher.initEnc)).@"fn".return_type.?;
12 const Block = [BlockCipher.block.block_length]u8;
13
14 return struct {
15 const Self = @This();
16 pub const key_length = BlockCipher.key_bits / 8;
17 pub const block_length = BlockCipher.block.block_length;
18 pub const mac_length = block_length;
19
20 cipher_ctx: BlockCipherCtx,
21 k1: Block,
22 k2: Block,
23 buf: Block = [_]u8{0} ** block_length,
24 pos: usize = 0,
25
26 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {
27 var ctx = Self.init(key);
28 ctx.update(msg);
29 ctx.final(out);
30 }
31
32 pub fn init(key: *const [key_length]u8) Self {
33 const cipher_ctx = BlockCipher.initEnc(key.*);
34 const zeros = [_]u8{0} ** block_length;
35 var k1: Block = undefined;
36 cipher_ctx.encrypt(&k1, &zeros);
37 k1 = double(k1);
38 return Self{
39 .cipher_ctx = cipher_ctx,
40 .k1 = k1,
41 .k2 = double(k1),
42 };
43 }
44
45 pub fn update(self: *Self, msg: []const u8) void {
46 const left = block_length - self.pos;
47 var m = msg;
48 if (m.len > left) {
49 for (self.buf[self.pos..], 0..) |*b, i| b.* ^= m[i];
50 m = m[left..];
51 self.cipher_ctx.encrypt(&self.buf, &self.buf);
52 self.pos = 0;
53 }
54 while (m.len > block_length) {
55 for (self.buf[0..block_length], 0..) |*b, i| b.* ^= m[i];
56 m = m[block_length..];
57 self.cipher_ctx.encrypt(&self.buf, &self.buf);
58 self.pos = 0;
59 }
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 var mac = self.k1;
68 if (self.pos < block_length) {
69 mac = self.k2;
70 mac[self.pos] ^= 0x80;
71 }
72 for (&mac, 0..) |*b, i| b.* ^= self.buf[i];
73 self.cipher_ctx.encrypt(out, &mac);
74 }
75
76 fn double(l: Block) Block {
77 const Int = std.meta.Int(.unsigned, block_length * 8);
78 const l_ = mem.readInt(Int, &l, .big);
79 const l_2 = switch (block_length) {
80 8 => (l_ << 1) ^ (0x1b & -%(l_ >> 63)), // mod x^64 + x^4 + x^3 + x + 1
81 16 => (l_ << 1) ^ (0x87 & -%(l_ >> 127)), // mod x^128 + x^7 + x^2 + x + 1
82 32 => (l_ << 1) ^ (0x0425 & -%(l_ >> 255)), // mod x^256 + x^10 + x^5 + x^2 + 1
83 64 => (l_ << 1) ^ (0x0125 & -%(l_ >> 511)), // mod x^512 + x^8 + x^5 + x^2 + 1
84 else => @compileError("unsupported block length"),
85 };
86 var l2: Block = undefined;
87 mem.writeInt(Int, &l2, l_2, .big);
88 return l2;
89 }
90 };
91}
92
93const testing = std.testing;
94
95test "CmacAes128 - Example 1: len = 0" {
96 const key = [_]u8{
97 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
98 };
99 var msg: [0]u8 = undefined;
100 const exp = [_]u8{
101 0xbb, 0x1d, 0x69, 0x29, 0xe9, 0x59, 0x37, 0x28, 0x7f, 0xa3, 0x7d, 0x12, 0x9b, 0x75, 0x67, 0x46,
102 };
103 var out: [CmacAes128.mac_length]u8 = undefined;
104 CmacAes128.create(&out, &msg, &key);
105 try testing.expectEqualSlices(u8, &out, &exp);
106}
107
108test "CmacAes128 - Example 2: len = 16" {
109 const key = [_]u8{
110 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
111 };
112 const msg = [_]u8{
113 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
114 };
115 const exp = [_]u8{
116 0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c,
117 };
118 var out: [CmacAes128.mac_length]u8 = undefined;
119 CmacAes128.create(&out, &msg, &key);
120 try testing.expectEqualSlices(u8, &out, &exp);
121}
122
123test "CmacAes128 - Example 3: len = 40" {
124 const key = [_]u8{
125 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
126 };
127 const msg = [_]u8{
128 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
129 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
130 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
131 };
132 const exp = [_]u8{
133 0xdf, 0xa6, 0x67, 0x47, 0xde, 0x9a, 0xe6, 0x30, 0x30, 0xca, 0x32, 0x61, 0x14, 0x97, 0xc8, 0x27,
134 };
135 var out: [CmacAes128.mac_length]u8 = undefined;
136 CmacAes128.create(&out, &msg, &key);
137 try testing.expectEqualSlices(u8, &out, &exp);
138}
139
140test "CmacAes128 - Example 4: len = 64" {
141 const key = [_]u8{
142 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
143 };
144 const msg = [_]u8{
145 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
146 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
147 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
148 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10,
149 };
150 const exp = [_]u8{
151 0x51, 0xf0, 0xbe, 0xbf, 0x7e, 0x3b, 0x9d, 0x92, 0xfc, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3c, 0xfe,
152 };
153 var out: [CmacAes128.mac_length]u8 = undefined;
154 CmacAes128.create(&out, &msg, &key);
155 try testing.expectEqualSlices(u8, &out, &exp);
156}