authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-17 20:05:23-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-09-17 20:05:23-07:00
logb782cdb9b37ba61476cf28632a2f1e10d631dcab
treedb3c1ca75f3a87926c9b6cd86b580aa96607dcb7
parent4406127cca3b4dcf92ae7587e2e6f8b4e267abf6
parentb1b2cd7ef8d1935479212d8eb21508b8b51111ad
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25249 from jedisct1/siv

std.crypto: add AES-SIV and AES-GCM-SIV

5 files changed, 1029 insertions(+), 37 deletions(-)

lib/std/crypto.zig+16
...@@ -31,6 +31,16 @@ pub const aead = struct {...@@ -31,6 +31,16 @@ pub const aead = struct {
31 pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;31 pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;
32 };32 };
3333
34 pub const aes_gcm_siv = struct {
35 pub const Aes128GcmSiv = @import("crypto/aes_gcm_siv.zig").Aes128GcmSiv;
36 pub const Aes256GcmSiv = @import("crypto/aes_gcm_siv.zig").Aes256GcmSiv;
37 };
38
39 pub const aes_siv = struct {
40 pub const Aes128Siv = @import("crypto/aes_siv.zig").Aes128Siv;
41 pub const Aes256Siv = @import("crypto/aes_siv.zig").Aes256Siv;
42 };
43
34 pub const aes_ocb = struct {44 pub const aes_ocb = struct {
35 pub const Aes128Ocb = @import("crypto/aes_ocb.zig").Aes128Ocb;45 pub const Aes128Ocb = @import("crypto/aes_ocb.zig").Aes128Ocb;
36 pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;46 pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;
...@@ -261,6 +271,12 @@ test {...@@ -261,6 +271,12 @@ test {
261 _ = aead.aes_gcm.Aes128Gcm;271 _ = aead.aes_gcm.Aes128Gcm;
262 _ = aead.aes_gcm.Aes256Gcm;272 _ = aead.aes_gcm.Aes256Gcm;
263273
274 _ = aead.aes_gcm_siv.Aes128GcmSiv;
275 _ = aead.aes_gcm_siv.Aes256GcmSiv;
276
277 _ = aead.aes_siv.Aes128Siv;
278 _ = aead.aes_siv.Aes256Siv;
279
264 _ = aead.aes_ocb.Aes128Ocb;280 _ = aead.aes_ocb.Aes128Ocb;
265 _ = aead.aes_ocb.Aes256Ocb;281 _ = aead.aes_ocb.Aes256Ocb;
266282
lib/std/crypto/aes.zig-25
...@@ -28,31 +28,6 @@ pub const AesDecryptCtx = impl.AesDecryptCtx;...@@ -28,31 +28,6 @@ pub const AesDecryptCtx = impl.AesDecryptCtx;
28pub const Aes128 = impl.Aes128;28pub const Aes128 = impl.Aes128;
29pub const Aes256 = impl.Aes256;29pub const Aes256 = impl.Aes256;
3030
31test "ctr" {
32 // NIST SP 800-38A pp 55-58
33 const ctr = @import("modes.zig").ctr;
34
35 const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
36 const iv = [_]u8{ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
37 const in = [_]u8{
38 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
39 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
40 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
41 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10,
42 };
43 const exp_out = [_]u8{
44 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
45 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
46 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
47 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee,
48 };
49
50 var out: [exp_out.len]u8 = undefined;
51 const ctx = Aes128.initEnc(key);
52 ctr(AesEncryptCtx(Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
53 try testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
54}
55
56test "encrypt" {31test "encrypt" {
57 // Appendix B32 // Appendix B
58 {33 {
lib/std/crypto/aes_gcm_siv.zig created+340
...@@ -0,0 +1,340 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const crypto = std.crypto;
4const debug = std.debug;
5const mem = std.mem;
6const math = std.math;
7const modes = @import("modes.zig");
8const Polyval = @import("ghash_polyval.zig").Polyval;
9const AuthenticationError = crypto.errors.AuthenticationError;
10
11pub const Aes128GcmSiv = AesGcmSiv(crypto.core.aes.Aes128);
12pub const Aes256GcmSiv = AesGcmSiv(crypto.core.aes.Aes256);
13
14/// AES-GCM-SIV: Authenticated encryption that remains secure even if you accidentally reuse a nonce.
15///
16/// What it does: Encrypts data and protects it from tampering. You can also attach
17/// unencrypted metadata (like headers) that will be authenticated but not encrypted.
18///
19/// When to use AES-GCM-SIV:
20/// - When you can't guarantee unique nonces (though you should still try to use unique nonces)
21///
22/// When to use regular AES-GCM instead:
23/// - When you can guarantee unique nonces (e.g., using a counter)
24/// - When you need slightly better performance
25///
26/// Security: If you accidentally reuse a nonce with the same key, AES-GCM-SIV only
27/// reveals whether two messages are identical. Regular AES-GCM would be catastrophically
28/// broken in this scenario, potentially revealing the authentication key.
29///
30/// Performance: Slightly slower than AES-GCM due to the additional key derivation step.
31///
32/// Defined in RFC 8452.
33fn AesGcmSiv(comptime Aes: anytype) type {
34 debug.assert(Aes.block.block_length == 16);
35
36 return struct {
37 pub const tag_length = 16;
38 pub const nonce_length = 12;
39 pub const key_length = Aes.key_bits / 8;
40
41 const zeros: [16]u8 = @splat(0);
42
43 /// Derives the authentication and message encryption keys from the master key and nonce.
44 /// This implements the key derivation as specified in RFC 8452 Section 4.
45 /// Generates a 128-bit authentication key for POLYVAL and a message encryption key
46 /// (128 or 256 bits depending on the AES variant).
47 fn deriveKeys(message_key: *[key_length]u8, auth_key: *[16]u8, key: [key_length]u8, nonce: [nonce_length]u8) void {
48 const aes = Aes.initEnc(key);
49
50 // Derive authentication and message keys per RFC 8452 Section 4
51 // Each encryption produces 16 bytes, but we only use first 8 bytes of each block
52
53 if (key_length == 16) {
54 // AES-128-GCM-SIV: Process 4 blocks in parallel
55 var key_blocks: [4 * 16]u8 = undefined;
56 var cipher_outs: [4 * 16]u8 = undefined;
57
58 // Set up all 4 blocks with counters 0-3 and nonce
59 inline for (0..4) |i| {
60 mem.writeInt(u32, key_blocks[i * 16 ..][0..4], @intCast(i), .little);
61 key_blocks[i * 16 + 4 .. i * 16 + 16].* = nonce;
62 }
63
64 // Encrypt all 4 blocks in parallel
65 aes.encryptWide(4, &cipher_outs, &key_blocks);
66
67 // Extract the key material (first 8 bytes of each block)
68 @memcpy(auth_key[0..8], cipher_outs[0..8]);
69 @memcpy(auth_key[8..16], cipher_outs[16..24]);
70 @memcpy(message_key[0..8], cipher_outs[32..40]);
71 @memcpy(message_key[8..16], cipher_outs[48..56]);
72 } else {
73 // AES-256-GCM-SIV: Process 6 blocks in parallel
74 var key_blocks: [6 * 16]u8 = undefined;
75 var cipher_outs: [6 * 16]u8 = undefined;
76
77 // Set up all 6 blocks with counters 0-5 and nonce
78 inline for (0..6) |i| {
79 mem.writeInt(u32, key_blocks[i * 16 ..][0..4], @intCast(i), .little);
80 key_blocks[i * 16 + 4 .. i * 16 + 16].* = nonce;
81 }
82
83 // Encrypt all 6 blocks in parallel
84 aes.encryptWide(6, &cipher_outs, &key_blocks);
85
86 // Extract the key material (first 8 bytes of each block)
87 @memcpy(auth_key[0..8], cipher_outs[0..8]);
88 @memcpy(auth_key[8..16], cipher_outs[16..24]);
89 @memcpy(message_key[0..8], cipher_outs[32..40]);
90 @memcpy(message_key[8..16], cipher_outs[48..56]);
91 @memcpy(message_key[16..24], cipher_outs[64..72]);
92 @memcpy(message_key[24..32], cipher_outs[80..88]);
93 }
94 }
95
96 /// Encrypts and authenticates a message using AES-GCM-SIV.
97 ///
98 /// `c`: The ciphertext buffer to write the encrypted data to.
99 /// `tag`: The authentication tag buffer to write the computed tag to.
100 /// `m`: The plaintext message to encrypt.
101 /// `ad`: The associated data to authenticate.
102 /// `npub`: The nonce to use for encryption.
103 /// `key`: The encryption key.
104 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void {
105 debug.assert(c.len == m.len);
106 debug.assert(m.len <= (1 << 36));
107 debug.assert(ad.len <= (1 << 36));
108
109 var auth_key: [16]u8 = undefined;
110 var message_key: [key_length]u8 = undefined;
111 deriveKeys(&message_key, &auth_key, key, npub);
112
113 // Calculate POLYVAL over additional data and plaintext
114 const block_count = (math.divCeil(usize, ad.len, Polyval.block_length) catch unreachable) +
115 (math.divCeil(usize, m.len, Polyval.block_length) catch unreachable) + 1;
116 var mac = Polyval.initForBlockCount(&auth_key, block_count);
117
118 // Process additional data
119 mac.update(ad);
120 mac.pad();
121
122 // Process plaintext
123 mac.update(m);
124 mac.pad();
125
126 // Length block
127 var length_block: [16]u8 = undefined;
128 mem.writeInt(u64, length_block[0..8], @as(u64, ad.len) * 8, .little);
129 mem.writeInt(u64, length_block[8..16], @as(u64, m.len) * 8, .little);
130 mac.update(&length_block);
131
132 // Get POLYVAL result
133 var s: [16]u8 = undefined;
134 mac.final(&s);
135
136 // XOR with nonce to get pre-tag
137 for (npub, 0..) |b, i| {
138 s[i] ^= b;
139 }
140
141 // Clear most significant bit of last byte
142 s[15] &= 0x7f;
143
144 // Encrypt to get tag
145 const tag_aes = Aes.initEnc(message_key);
146 tag_aes.encrypt(tag, &s);
147
148 // Use tag as initial counter for CTR mode
149 var counter: [16]u8 = tag.*;
150 counter[15] |= 0x80; // Set most significant bit
151
152 // Encrypt message using CTR mode with 32-bit little-endian counter
153 const aes_ctx = Aes.initEnc(message_key);
154 modes.ctrSlice(@TypeOf(aes_ctx), aes_ctx, c, m, counter, .little, 0, 4);
155 }
156
157 /// Decrypts and authenticates a message using AES-GCM-SIV.
158 ///
159 /// `m`: Message buffer to write the decrypted data to.
160 /// `c`: The ciphertext to decrypt.
161 /// `tag`: The authentication tag.
162 /// `ad`: The associated data.
163 /// `npub`: The nonce.
164 /// `key`: The decryption key.
165 /// Asserts `c.len == m.len`.
166 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
167 assert(c.len == m.len);
168 assert(c.len <= (1 << 36));
169 assert(ad.len <= (1 << 36));
170
171 var auth_key: [16]u8 = undefined;
172 var message_key: [key_length]u8 = undefined;
173 deriveKeys(&message_key, &auth_key, key, npub);
174
175 // Decrypt message using CTR mode with 32-bit little-endian counter
176 var counter: [16]u8 = tag;
177 counter[15] |= 0x80; // Set most significant bit
178
179 const aes_ctx = Aes.initEnc(message_key);
180 modes.ctrSlice(@TypeOf(aes_ctx), aes_ctx, m, c, counter, .little, 0, 4);
181
182 // Verify tag by recalculating POLYVAL
183 const block_count = (math.divCeil(usize, ad.len, Polyval.block_length) catch unreachable) +
184 (math.divCeil(usize, m.len, Polyval.block_length) catch unreachable) + 1;
185 var mac = Polyval.initForBlockCount(&auth_key, block_count);
186
187 // Process additional data
188 mac.update(ad);
189 mac.pad();
190
191 // Process decrypted plaintext
192 mac.update(m);
193 mac.pad();
194
195 // Length block
196 var length_block: [16]u8 = undefined;
197 mem.writeInt(u64, length_block[0..8], @as(u64, ad.len) * 8, .little);
198 mem.writeInt(u64, length_block[8..16], @as(u64, m.len) * 8, .little);
199 mac.update(&length_block);
200
201 // Get POLYVAL result
202 var s: [16]u8 = undefined;
203 mac.final(&s);
204
205 // XOR with nonce to get pre-tag
206 for (npub, 0..) |b, i| {
207 s[i] ^= b;
208 }
209
210 // Clear most significant bit of last byte
211 s[15] &= 0x7f;
212
213 // Encrypt to get expected tag
214 const tag_aes = Aes.initEnc(message_key);
215 var computed_tag: [tag_length]u8 = undefined;
216 tag_aes.encrypt(&computed_tag, &s);
217
218 // Verify tag
219 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
220 if (!verify) {
221 crypto.secureZero(u8, &computed_tag);
222 @memset(m, undefined);
223 return error.AuthenticationFailed;
224 }
225 }
226 };
227}
228
229const htest = @import("test.zig");
230const testing = std.testing;
231
232test "Aes128GcmSiv - RFC 8452 Test Vector 1" {
233 // Test vector from RFC 8452 Appendix C.1
234 const key = [_]u8{
235 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
236 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
237 };
238 const nonce = [_]u8{
239 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
240 0x00, 0x00, 0x00, 0x00,
241 };
242 const ad = "";
243 const m = "";
244 var c: [m.len]u8 = undefined;
245 var tag: [Aes128GcmSiv.tag_length]u8 = undefined;
246
247 Aes128GcmSiv.encrypt(&c, &tag, m, ad, nonce, key);
248 try htest.assertEqual("dc20e2d83f25705bb49e439eca56de25", &tag);
249}
250
251test "Aes128GcmSiv - RFC 8452 Test Vector 2" {
252 // Test vector from RFC 8452 Appendix C.1
253 const key = [_]u8{
254 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
255 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
256 };
257 const nonce = [_]u8{
258 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
259 0x00, 0x00, 0x00, 0x00,
260 };
261 const plaintext = [_]u8{
262 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
263 };
264 const ad = "";
265 var c: [plaintext.len]u8 = undefined;
266 var tag: [Aes128GcmSiv.tag_length]u8 = undefined;
267
268 Aes128GcmSiv.encrypt(&c, &tag, &plaintext, ad, nonce, key);
269 try htest.assertEqual("b5d839330ac7b786", &c);
270 try htest.assertEqual("578782fff6013b815b287c22493a364c", &tag);
271
272 var m2: [plaintext.len]u8 = undefined;
273 try Aes128GcmSiv.decrypt(&m2, &c, tag, ad, nonce, key);
274 try testing.expectEqualSlices(u8, &plaintext, &m2);
275}
276
277test "Aes128GcmSiv - RFC 8452 Test Vector 3" {
278 // Test vector from RFC 8452 Appendix C.1
279 const key = [_]u8{
280 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
281 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
282 };
283 const nonce = [_]u8{
284 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
285 0x00, 0x00, 0x00, 0x00,
286 };
287 const plaintext = [_]u8{
288 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
289 0x00, 0x00, 0x00, 0x00,
290 };
291 const ad = "";
292 var c: [plaintext.len]u8 = undefined;
293 var tag: [Aes128GcmSiv.tag_length]u8 = undefined;
294
295 Aes128GcmSiv.encrypt(&c, &tag, &plaintext, ad, nonce, key);
296 try htest.assertEqual("7323ea61d05932260047d942", &c);
297 try htest.assertEqual("a4978db357391a0bc4fdec8b0d106639", &tag);
298
299 var m2: [plaintext.len]u8 = undefined;
300 try Aes128GcmSiv.decrypt(&m2, &c, tag, ad, nonce, key);
301 try testing.expectEqualSlices(u8, &plaintext, &m2);
302}
303
304test "Aes256GcmSiv - RFC 8452 Test Vector" {
305 // Test vector from RFC 8452 Appendix C.2
306 const key = [_]u8{
307 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
308 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
309 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
310 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
311 };
312 const nonce = [_]u8{
313 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
314 0x00, 0x00, 0x00, 0x00,
315 };
316 const ad = "";
317 const m = "";
318 var c: [m.len]u8 = undefined;
319 var tag: [Aes256GcmSiv.tag_length]u8 = undefined;
320
321 Aes256GcmSiv.encrypt(&c, &tag, m, ad, nonce, key);
322 try htest.assertEqual("07f5f4169bbf55a8400cd47ea6fd400f", &tag);
323}
324
325test "Aes128GcmSiv - Decrypt with wrong tag" {
326 const key: [Aes128GcmSiv.key_length]u8 = @splat(0x69);
327 const nonce: [Aes128GcmSiv.nonce_length]u8 = @splat(0x42);
328 const m = "Test message";
329 const ad = "";
330 var c: [m.len]u8 = undefined;
331 var tag: [Aes128GcmSiv.tag_length]u8 = undefined;
332
333 Aes128GcmSiv.encrypt(&c, &tag, m, ad, nonce, key);
334
335 // Corrupt the tag
336 tag[0] ^= 0x01;
337
338 var m2: [m.len]u8 = undefined;
339 try testing.expectError(error.AuthenticationFailed, Aes128GcmSiv.decrypt(&m2, &c, tag, ad, nonce, key));
340}
lib/std/crypto/aes_siv.zig created+481
...@@ -0,0 +1,481 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const crypto = std.crypto;
4const debug = std.debug;
5const mem = std.mem;
6const math = std.math;
7const modes = crypto.core.modes;
8const Cmac = @import("cmac.zig").Cmac;
9const AuthenticationError = crypto.errors.AuthenticationError;
10
11pub const Aes128Siv = AesSiv(crypto.core.aes.Aes128);
12pub const Aes256Siv = AesSiv(crypto.core.aes.Aes256);
13
14/// AES-SIV: Deterministic authenticated encryption - the same message always produces the same ciphertext.
15///
16/// What it does: Encrypts data and protects it from tampering. Unlike most encryption modes,
17/// AES-SIV is deterministic: encrypting the same message with the same key always produces
18/// the same ciphertext (unless you provide an optional nonce).
19///
20/// When to use AES-SIV:
21/// - When you need deterministic encryption (e.g., for deduplication in encrypted storage)
22/// - When you can't store or generate nonces
23/// - For key wrapping (protecting cryptographic keys)
24/// - When you need to search encrypted data without decrypting it
25///
26/// When NOT to use AES-SIV:
27/// - When identical plaintexts must produce different ciphertexts (use AES-GCM or AES-GCM-SIV)
28/// - For network protocols where replay attacks are a concern
29///
30/// Unique features:
31/// - Optional nonce: You can add a nonce to make encryption non-deterministic, but this is optional
32/// - Multiple associated data: Supports a vector of associated data strings instead of just one.
33/// The algorithm cryptographically ensures each component is properly separated, preventing
34/// canonicalization attacks where different splits of data could be accepted as valid.
35///
36/// Security properties:
37/// - Deterministic: Same input always gives same output (this can leak information about patterns)
38/// - Nonce misuse resistant: Doesn't catastrophically fail if you reuse a nonce
39/// - Key commitment: Ciphertext can only be decrypted with the exact key that encrypted it
40///
41/// AES-SIV has better security properties than AES-GCM-SIV, but is must slower.
42///
43/// How it works: Combines two keys - one for authentication (S2V) and one for encryption (CTR mode).
44/// The total key size is double the AES key size (256 bits for AES-128-SIV, 512 bits for AES-256-SIV).
45///
46/// Defined in RFC 5297.
47fn AesSiv(comptime Aes: anytype) type {
48 debug.assert(Aes.block.block_length == 16);
49
50 return struct {
51 pub const tag_length = 16;
52 pub const key_length = Aes.key_bits / 8 * 2; // SIV uses 2x key size
53
54 const CmacImpl = Cmac(Aes);
55
56 /// S2V (String to Vector) - RFC 5297 Section 2.4
57 /// Derives a synthetic IV from the key and input strings using CMAC.
58 /// This function implements a cryptographic pseudo-random function that maps
59 /// a variable-length vector of strings to a fixed 128-bit output.
60 fn s2v(iv: *[16]u8, key: [Aes.key_bits / 8]u8, strings: []const []const u8) void {
61 assert(strings.len > 0);
62 assert(strings.len <= 127); // S2V limitation
63
64 var d: [16]u8 = undefined;
65
66 // Special case: single empty string
67 if (strings.len == 1 and strings[0].len == 0) {
68 CmacImpl.create(&d, &[_]u8{}, &key);
69 iv.* = d;
70 return;
71 }
72
73 // Initialize with CMAC of zero block
74 const zero_block: [16]u8 = @splat(0);
75 CmacImpl.create(&d, &zero_block, &key);
76
77 // Process all strings except the last one
78 var i: usize = 0;
79 while (i < strings.len - 1) : (i += 1) {
80 d = dbl(d);
81 var tmp: [16]u8 = undefined;
82 CmacImpl.create(&tmp, strings[i], &key);
83 for (&d, tmp) |*b, t| {
84 b.* ^= t;
85 }
86 }
87
88 // Process the final string
89 const sn = strings[strings.len - 1];
90 if (sn.len >= 16) {
91 // XOR d with the first 16 bytes of Sn
92 var xored_msg_buf: [4096]u8 = undefined;
93 const xored_len = @min(sn.len, xored_msg_buf.len);
94 @memcpy(xored_msg_buf[0..xored_len], sn[0..xored_len]);
95
96 for (d, 0..) |b, j| {
97 xored_msg_buf[j] ^= b;
98 }
99
100 CmacImpl.create(iv, xored_msg_buf[0..xored_len], &key);
101 } else {
102 // Pad and XOR
103 d = dbl(d);
104 var padded: [16]u8 = @splat(0);
105 @memcpy(padded[0..sn.len], sn);
106 padded[sn.len] = 0x80;
107 for (&d, padded) |*b, p| {
108 b.* ^= p;
109 }
110 CmacImpl.create(iv, &d, &key);
111 }
112 }
113
114 /// Double operation as defined in RFC 5297.
115 /// Performs multiplication by x (i.e., left shift by 1) in GF(2^128).
116 /// This is the same operation used in CMAC subkey generation.
117 /// If the MSB is set, XORs with the polynomial 0x87 after shifting.
118 fn dbl(d: [16]u8) [16]u8 {
119 // Read as big-endian 128-bit integer
120 const val = mem.readInt(u128, &d, .big);
121
122 // Left shift by 1, and XOR with 0x87 if MSB was set
123 const doubled = (val << 1) ^ (0x87 & -%(@as(u128, val >> 127)));
124
125 // Write back as big-endian
126 var result: [16]u8 = undefined;
127 mem.writeInt(u128, &result, doubled, .big);
128 return result;
129 }
130
131 /// Encrypt plaintext using AES-SIV
132 /// `c`: Output buffer for ciphertext (same size as plaintext)
133 /// `tag`: Output buffer for authentication tag (synthetic IV)
134 /// `m`: Plaintext to encrypt
135 /// `ad`: Optional associated data
136 /// `nonce`: Optional nonce (if provided, will be added as last AD component)
137 /// `key`: Combined key (2x AES key size)
138 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: ?[]const u8, nonce: ?[]const u8, key: [key_length]u8) void {
139 debug.assert(c.len == m.len);
140
141 // Split key into K1 (for S2V) and K2 (for CTR)
142 const k1 = key[0 .. Aes.key_bits / 8];
143 const k2 = key[Aes.key_bits / 8 ..];
144
145 // Prepare strings for S2V: AD components followed by plaintext
146 var strings_buf: [128][]const u8 = undefined;
147 var strings_len: usize = 0;
148
149 if (ad) |a| {
150 strings_buf[strings_len] = a;
151 strings_len += 1;
152 }
153 if (nonce) |n| {
154 strings_buf[strings_len] = n;
155 strings_len += 1;
156 }
157 strings_buf[strings_len] = m;
158 strings_len += 1;
159
160 // Compute synthetic IV using S2V
161 s2v(tag, k1.*, strings_buf[0..strings_len]);
162
163 // Clear the 31st and 63rd bits for use as CTR IV
164 var ctr_iv = tag.*;
165 ctr_iv[8] &= 0x7f;
166 ctr_iv[12] &= 0x7f;
167
168 // Encrypt plaintext using CTR mode
169 const aes_ctx = Aes.initEnc(k2.*);
170 modes.ctr(@TypeOf(aes_ctx), aes_ctx, c, m, ctr_iv, .big);
171 }
172
173 /// Decrypt ciphertext using AES-SIV
174 /// `m`: Output buffer for decrypted plaintext
175 /// `c`: Ciphertext to decrypt
176 /// `tag`: Authentication tag (synthetic IV)
177 /// `ad`: Optional associated data (must match encryption)
178 /// `nonce`: Optional nonce (must match encryption)
179 /// `key`: Combined key (2x AES key size)
180 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: ?[]const u8, nonce: ?[]const u8, key: [key_length]u8) AuthenticationError!void {
181 assert(c.len == m.len);
182
183 // Split key into K1 (for S2V) and K2 (for CTR)
184 const k1 = key[0 .. Aes.key_bits / 8];
185 const k2 = key[Aes.key_bits / 8 ..];
186
187 // Clear the 31st and 63rd bits for use as CTR IV
188 var ctr_iv = tag;
189 ctr_iv[8] &= 0x7f;
190 ctr_iv[12] &= 0x7f;
191
192 // Decrypt ciphertext using CTR mode
193 const aes_ctx = Aes.initEnc(k2.*);
194 modes.ctr(@TypeOf(aes_ctx), aes_ctx, m, c, ctr_iv, .big);
195
196 // Prepare strings for S2V: AD components followed by plaintext
197 var strings_buf: [128][]const u8 = undefined;
198 var strings_len: usize = 0;
199
200 if (ad) |a| {
201 strings_buf[strings_len] = a;
202 strings_len += 1;
203 }
204 if (nonce) |n| {
205 strings_buf[strings_len] = n;
206 strings_len += 1;
207 }
208 strings_buf[strings_len] = m;
209 strings_len += 1;
210
211 // Verify synthetic IV using S2V
212 var computed_tag: [tag_length]u8 = undefined;
213 s2v(&computed_tag, k1.*, strings_buf[0..strings_len]);
214
215 // Verify tag
216 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
217 if (!verify) {
218 crypto.secureZero(u8, &computed_tag);
219 @memset(m, undefined);
220 return error.AuthenticationFailed;
221 }
222 }
223
224 /// Encrypts plaintext with multiple associated data components.
225 /// This is the most general form of AES-SIV encryption that accepts
226 /// an arbitrary vector of associated data strings as specified in RFC 5297.
227 pub fn encryptWithAdVector(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const []const u8, key: [key_length]u8) void {
228 debug.assert(c.len == m.len);
229
230 // Split key into K1 (for S2V) and K2 (for CTR)
231 const k1 = key[0 .. Aes.key_bits / 8];
232 const k2 = key[Aes.key_bits / 8 ..];
233
234 // Prepare strings for S2V: AD components followed by plaintext
235 var strings_buf: [128][]const u8 = undefined;
236 var strings_len: usize = 0;
237
238 for (ad) |a| {
239 strings_buf[strings_len] = a;
240 strings_len += 1;
241 }
242 strings_buf[strings_len] = m;
243 strings_len += 1;
244
245 // Compute synthetic IV using S2V
246 s2v(tag, k1.*, strings_buf[0..strings_len]);
247
248 // Clear the 31st and 63rd bits for use as CTR IV
249 var ctr_iv = tag.*;
250 ctr_iv[8] &= 0x7f;
251 ctr_iv[12] &= 0x7f;
252
253 // Encrypt plaintext using CTR mode
254 const aes_ctx = Aes.initEnc(k2.*);
255 modes.ctr(@TypeOf(aes_ctx), aes_ctx, c, m, ctr_iv, .big);
256 }
257
258 /// Decrypts ciphertext with multiple associated data components.
259 /// This is the most general form of AES-SIV decryption that accepts
260 /// an arbitrary vector of associated data strings as specified in RFC 5297.
261 pub fn decryptWithAdVector(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const []const u8, key: [key_length]u8) AuthenticationError!void {
262 assert(c.len == m.len);
263
264 // Split key into K1 (for S2V) and K2 (for CTR)
265 const k1 = key[0 .. Aes.key_bits / 8];
266 const k2 = key[Aes.key_bits / 8 ..];
267
268 // Clear the 31st and 63rd bits for use as CTR IV
269 var ctr_iv = tag;
270 ctr_iv[8] &= 0x7f;
271 ctr_iv[12] &= 0x7f;
272
273 // Decrypt ciphertext using CTR mode
274 const aes_ctx = Aes.initEnc(k2.*);
275 modes.ctr(@TypeOf(aes_ctx), aes_ctx, m, c, ctr_iv, .big);
276
277 // Prepare strings for S2V: AD components followed by plaintext
278 var strings_buf: [128][]const u8 = undefined;
279 var strings_len: usize = 0;
280
281 for (ad) |a| {
282 strings_buf[strings_len] = a;
283 strings_len += 1;
284 }
285 strings_buf[strings_len] = m;
286 strings_len += 1;
287
288 // Verify synthetic IV using S2V
289 var computed_tag: [tag_length]u8 = undefined;
290 s2v(&computed_tag, k1.*, strings_buf[0..strings_len]);
291
292 // Verify tag
293 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
294 if (!verify) {
295 crypto.secureZero(u8, &computed_tag);
296 @memset(m, undefined);
297 return error.AuthenticationFailed;
298 }
299 }
300 };
301}
302
303const htest = @import("test.zig");
304const testing = std.testing;
305
306test "AES-SIV double operation" {
307 const AesSivTest = AesSiv(crypto.core.aes.Aes128);
308
309 // Test vector from RFC 5297
310 const input = [_]u8{ 0x0e, 0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e };
311 const expected = [_]u8{ 0x1c, 0x08, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c };
312
313 const result = AesSivTest.dbl(input);
314 try testing.expectEqualSlices(u8, &expected, &result);
315}
316
317test "AES-SIV double operation with MSB set" {
318 const AesSivTest = AesSiv(crypto.core.aes.Aes128);
319
320 const input = [_]u8{ 0xe0, 0x40, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0 };
321 const expected = [_]u8{ 0xc0, 0x80, 0x20, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe1, 0x01, 0x21, 0x41, 0x61, 0x81, 0xa1, 0x47 };
322
323 const result = AesSivTest.dbl(input);
324 try testing.expectEqualSlices(u8, &expected, &result);
325}
326
327test "Aes128Siv - RFC 5297 Test Vector A.1" {
328 // Test vector from RFC 5297 Appendix A.1
329 const key = [_]u8{
330 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0,
331 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
332 };
333 const ad = [_]u8{
334 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
335 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
336 };
337 const plaintext = [_]u8{
338 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
339 };
340
341 var ciphertext: [plaintext.len]u8 = undefined;
342 var tag: [16]u8 = undefined;
343
344 // Test using vector API for RFC compliance
345 const ad_components = [_][]const u8{&ad};
346 Aes128Siv.encryptWithAdVector(&ciphertext, &tag, &plaintext, &ad_components, key);
347
348 // Expected values from RFC 5297
349 try htest.assertEqual("85632d07c6e8f37f950acd320a2ecc93", &tag);
350 try htest.assertEqual("40c02b9690c4dc04daef7f6afe5c", &ciphertext);
351
352 // Test decryption
353 var decrypted: [plaintext.len]u8 = undefined;
354 try Aes128Siv.decryptWithAdVector(&decrypted, &ciphertext, tag, &ad_components, key);
355 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
356}
357
358test "Aes128Siv - empty plaintext" {
359 const key: [32]u8 = @splat(0x42);
360 const plaintext = "";
361 const ad = "additional data";
362
363 var ciphertext: [plaintext.len]u8 = undefined;
364 var tag: [16]u8 = undefined;
365
366 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, ad, null, key);
367
368 var decrypted: [plaintext.len]u8 = undefined;
369 try Aes128Siv.decrypt(&decrypted, &ciphertext, tag, ad, null, key);
370}
371
372test "Aes128Siv - with nonce" {
373 const key: [32]u8 = @splat(0x69);
374 const nonce: [16]u8 = @splat(0x42);
375 const plaintext = "Hello, AES-SIV!";
376 const ad = "metadata";
377
378 var ciphertext: [plaintext.len]u8 = undefined;
379 var tag: [16]u8 = undefined;
380
381 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, ad, &nonce, key);
382
383 var decrypted: [plaintext.len]u8 = undefined;
384 try Aes128Siv.decrypt(&decrypted, &ciphertext, tag, ad, &nonce, key);
385 try testing.expectEqualSlices(u8, plaintext, &decrypted);
386}
387
388test "Aes256Siv - basic functionality" {
389 const key: [64]u8 = @splat(0x96);
390 const plaintext = "Test message for AES-256-SIV";
391 const ad1 = "header";
392 const ad2 = "more data";
393
394 var ciphertext: [plaintext.len]u8 = undefined;
395 var tag: [16]u8 = undefined;
396
397 // Test with multiple AD components using the vector API
398 const ad_components = [_][]const u8{ ad1, ad2 };
399 Aes256Siv.encryptWithAdVector(&ciphertext, &tag, plaintext, &ad_components, key);
400
401 var decrypted: [plaintext.len]u8 = undefined;
402 try Aes256Siv.decryptWithAdVector(&decrypted, &ciphertext, tag, &ad_components, key);
403 try testing.expectEqualSlices(u8, plaintext, &decrypted);
404}
405
406test "Aes128Siv - demonstrating optional parameters" {
407 const key: [32]u8 = @splat(0x77);
408
409 // Test 1: No AD, no nonce (pure deterministic)
410 {
411 const plaintext = "Deterministic encryption";
412 var ciphertext: [plaintext.len]u8 = undefined;
413 var tag: [16]u8 = undefined;
414
415 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, null, null, key);
416
417 var decrypted: [plaintext.len]u8 = undefined;
418 try Aes128Siv.decrypt(&decrypted, &ciphertext, tag, null, null, key);
419 try testing.expectEqualSlices(u8, plaintext, &decrypted);
420 }
421
422 // Test 2: With AD, no nonce
423 {
424 const plaintext = "With associated data";
425 const ad = "some context";
426 var ciphertext: [plaintext.len]u8 = undefined;
427 var tag: [16]u8 = undefined;
428
429 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, ad, null, key);
430
431 var decrypted: [plaintext.len]u8 = undefined;
432 try Aes128Siv.decrypt(&decrypted, &ciphertext, tag, ad, null, key);
433 try testing.expectEqualSlices(u8, plaintext, &decrypted);
434 }
435
436 // Test 3: No AD, with nonce
437 {
438 const plaintext = "Nonce-based encryption";
439 const nonce: [12]u8 = @splat(0x01);
440 var ciphertext: [plaintext.len]u8 = undefined;
441 var tag: [16]u8 = undefined;
442
443 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, null, &nonce, key);
444
445 var decrypted: [plaintext.len]u8 = undefined;
446 try Aes128Siv.decrypt(&decrypted, &ciphertext, tag, null, &nonce, key);
447 try testing.expectEqualSlices(u8, plaintext, &decrypted);
448 }
449
450 // Test 4: With both AD and nonce
451 {
452 const plaintext = "Full featured";
453 const ad = "context";
454 const nonce: [16]u8 = @splat(0x02);
455 var ciphertext: [plaintext.len]u8 = undefined;
456 var tag: [16]u8 = undefined;
457
458 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, ad, &nonce, key);
459
460 var decrypted: [plaintext.len]u8 = undefined;
461 try Aes128Siv.decrypt(&decrypted, &ciphertext, tag, ad, &nonce, key);
462 try testing.expectEqualSlices(u8, plaintext, &decrypted);
463 }
464}
465
466test "Aes128Siv - authentication failure" {
467 const key: [32]u8 = @splat(0x13);
468 const plaintext = "Secret message";
469 const ad = "";
470
471 var ciphertext: [plaintext.len]u8 = undefined;
472 var tag: [16]u8 = undefined;
473
474 Aes128Siv.encrypt(&ciphertext, &tag, plaintext, ad, null, key);
475
476 // Corrupt the tag
477 tag[0] ^= 0x01;
478
479 var decrypted: [plaintext.len]u8 = undefined;
480 try testing.expectError(error.AuthenticationFailed, Aes128Siv.decrypt(&decrypted, &ciphertext, tag, ad, null, key));
481}
lib/std/crypto/modes.zig+192-12
...@@ -11,37 +11,217 @@ const debug = std.debug;...@@ -11,37 +11,217 @@ const debug = std.debug;
11/// Important: the counter mode doesn't provide authenticated encryption: the ciphertext can be trivially modified without this being detected.11/// Important: the counter mode doesn't provide authenticated encryption: the ciphertext can be trivially modified without this being detected.
12/// As a result, applications should generally never use it directly, but only in a construction that includes a MAC.12/// As a result, applications should generally never use it directly, but only in a construction that includes a MAC.
13pub fn ctr(comptime BlockCipher: anytype, block_cipher: BlockCipher, dst: []u8, src: []const u8, iv: [BlockCipher.block_length]u8, endian: std.builtin.Endian) void {13pub fn ctr(comptime BlockCipher: anytype, block_cipher: BlockCipher, dst: []u8, src: []const u8, iv: [BlockCipher.block_length]u8, endian: std.builtin.Endian) void {
14 ctrSlice(BlockCipher, block_cipher, dst, src, iv, endian, 0, BlockCipher.block_length);
15}
16
17/// Counter mode with configurable counter position and size.
18///
19/// This extended version allows specifying where the counter is located within the IV block
20/// and how many bytes it occupies. This is useful for modes like AES-GCM-SIV which use a
21/// 32-bit counter at the beginning of the block.
22///
23/// @param counter_offset: Byte offset where the counter starts
24/// @param counter_size: Size of the counter in bytes
25pub fn ctrSlice(
26 comptime BlockCipher: anytype,
27 block_cipher: BlockCipher,
28 dst: []u8,
29 src: []const u8,
30 iv: [BlockCipher.block_length]u8,
31 endian: std.builtin.Endian,
32 comptime counter_offset: usize,
33 comptime counter_size: usize,
34) void {
14 debug.assert(dst.len >= src.len);35 debug.assert(dst.len >= src.len);
15 const block_length = BlockCipher.block_length;36 const block_length = BlockCipher.block_length;
16 var counter: [BlockCipher.block_length]u8 = undefined;37 debug.assert(counter_offset + counter_size <= block_length);
17 var counterInt = mem.readInt(u128, &iv, endian);38 debug.assert(counter_size > 0 and counter_size <= block_length);
39
40 var counterBlock = iv;
18 var i: usize = 0;41 var i: usize = 0;
1942
43 const CounterInt = std.meta.Int(.unsigned, counter_size * 8);
44
20 const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks;45 const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks;
21 const wide_block_length = parallel_count * 16;46 const wide_block_length = parallel_count * block_length;
47 var cnt_val = mem.readInt(CounterInt, counterBlock[counter_offset..][0..counter_size], endian);
22 if (src.len >= wide_block_length) {48 if (src.len >= wide_block_length) {
23 var counters: [parallel_count * 16]u8 = undefined;49 var counters: [parallel_count * block_length]u8 = undefined;
50 inline for (0..parallel_count) |j| {
51 counters[j * block_length ..][0..block_length].* = iv;
52 }
24 while (i + wide_block_length <= src.len) : (i += wide_block_length) {53 while (i + wide_block_length <= src.len) : (i += wide_block_length) {
25 comptime var j = 0;54 comptime var j = 0;
26 inline while (j < parallel_count) : (j += 1) {55 inline while (j < parallel_count) : (j += 1) {
27 mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt, endian);56 mem.writeInt(CounterInt, counters[j * block_length + counter_offset ..][0..counter_size], cnt_val +% j, endian);
28 counterInt +%= 1;
29 }57 }
58 cnt_val += parallel_count;
30 block_cipher.xorWide(parallel_count, dst[i .. i + wide_block_length][0..wide_block_length], src[i .. i + wide_block_length][0..wide_block_length], counters);59 block_cipher.xorWide(parallel_count, dst[i .. i + wide_block_length][0..wide_block_length], src[i .. i + wide_block_length][0..wide_block_length], counters);
31 }60 }
61 mem.writeInt(CounterInt, counterBlock[counter_offset..][0..counter_size], cnt_val, endian);
32 }62 }
33 while (i + block_length <= src.len) : (i += block_length) {63 while (i + block_length <= src.len) : (i += block_length) {
34 mem.writeInt(u128, &counter, counterInt, endian);64 block_cipher.xor(dst[i .. i + block_length][0..block_length], src[i .. i + block_length][0..block_length], counterBlock);
35 counterInt +%= 1;65 cnt_val +%= 1;
36 block_cipher.xor(dst[i .. i + block_length][0..block_length], src[i .. i + block_length][0..block_length], counter);66 mem.writeInt(CounterInt, counterBlock[counter_offset..][0..counter_size], cnt_val, endian);
37 }67 }
38 if (i < src.len) {68 if (i < src.len) {
39 mem.writeInt(u128, &counter, counterInt, endian);69 var pad: [block_length]u8 = @splat(0);
40 var pad = [_]u8{0} ** block_length;
41 const src_slice = src[i..];70 const src_slice = src[i..];
42 @memcpy(pad[0..src_slice.len], src_slice);71 @memcpy(pad[0..src_slice.len], src_slice);
43 block_cipher.xor(&pad, &pad, counter);72 block_cipher.xor(&pad, &pad, counterBlock);
44 const pad_slice = pad[0 .. src.len - i];73 const pad_slice = pad[0 .. src.len - i];
45 @memcpy(dst[i..][0..pad_slice.len], pad_slice);74 @memcpy(dst[i..][0..pad_slice.len], pad_slice);
46 }75 }
47}76}
77
78test "ctr mode" {
79 const testing = std.testing;
80 const aes = std.crypto.core.aes;
81
82 // Test key and IV from NIST SP 800-38A
83 const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
84 const iv = [_]u8{ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
85 const ctx = aes.Aes128.initEnc(key);
86
87 // Test 1: Empty input
88 {
89 const in = [_]u8{};
90 const expected = [_]u8{};
91 var out: [0]u8 = undefined;
92 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
93 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
94 }
95
96 // Test 2: Single byte
97 {
98 const in = [_]u8{0x6b};
99 const expected = [_]u8{0x87};
100 var out: [1]u8 = undefined;
101 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
102 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
103 }
104
105 // Test 3: Less than one block (15 bytes)
106 {
107 const in = [_]u8{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17 };
108 const expected = [_]u8{ 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6 };
109 var out: [15]u8 = undefined;
110 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
111 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
112 }
113
114 // Test 4: Exactly one block (16 bytes)
115 {
116 const in = [_]u8{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a };
117 const expected = [_]u8{ 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce };
118 var out: [16]u8 = undefined;
119 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
120 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
121 }
122
123 // Test 5: One block plus one byte (17 bytes)
124 {
125 const in = [_]u8{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae };
126 const expected = [_]u8{ 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, 0x98 };
127 var out: [17]u8 = undefined;
128 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
129 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
130 }
131
132 // Test 6: Exactly two blocks (32 bytes)
133 {
134 const in = [_]u8{
135 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
136 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
137 };
138 const expected = [_]u8{
139 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
140 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
141 };
142 var out: [32]u8 = undefined;
143 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
144 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
145 }
146
147 // Test 7: Two blocks plus 5 bytes (37 bytes)
148 {
149 const in = [_]u8{
150 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
151 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
152 0x30, 0xc8, 0x1c, 0x46, 0xa3,
153 };
154 const expected = [_]u8{
155 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
156 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
157 0x5a, 0xe4, 0xdf, 0x3e, 0xdb,
158 };
159 var out: [37]u8 = undefined;
160 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
161 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
162 }
163
164 // Test 8: Four blocks (64 bytes) - NIST test vector
165 {
166 const in = [_]u8{
167 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
168 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
169 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
170 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10,
171 };
172 const expected = [_]u8{
173 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
174 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
175 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
176 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee,
177 };
178 var out: [64]u8 = undefined;
179 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
180 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
181 }
182
183 // Test 9: Large input (> 2*block_length, 100 bytes)
184 {
185 // Create a 100-byte input by extending with zeros
186 var in: [100]u8 = [_]u8{0} ** 100;
187 @memcpy(in[0..64], &[_]u8{
188 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
189 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
190 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
191 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10,
192 });
193
194 // Expected output: first 64 bytes from NIST, then CTR continues with zeros
195 var expected: [100]u8 = undefined;
196 @memcpy(expected[0..64], &[_]u8{
197 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
198 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
199 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
200 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee,
201 });
202 // Compute the rest with zeros XORed with keystream
203 @memcpy(expected[64..], &[_]u8{
204 0xb0, 0x0d, 0x47, 0xf8, 0x14, 0x8a, 0x91, 0x0e, 0xf0, 0x68, 0x30, 0x97, 0x90, 0x4b, 0xa5, 0x02,
205 0x58, 0x99, 0x44, 0x5a, 0x4d, 0xe1, 0x01, 0xf5, 0x13, 0xca, 0xd1, 0x98, 0x7d, 0x89, 0xe9, 0x1b,
206 0x3b, 0xd9, 0xac, 0x79,
207 });
208
209 var out: [100]u8 = undefined;
210 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], iv, std.builtin.Endian.big);
211 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
212 }
213
214 // Test 10: Test with different endianness (little-endian counter)
215 {
216 const le_iv = [_]u8{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
217 const in = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
218
219 // We'll compute the expected value from the actual encryption
220 var out: [16]u8 = undefined;
221 ctr(aes.AesEncryptCtx(aes.Aes128), ctx, out[0..], in[0..], le_iv, std.builtin.Endian.little);
222
223 // The actual output for this test with little-endian counter=1
224 const expected = [_]u8{ 0x7e, 0x48, 0x15, 0xa8, 0x16, 0x66, 0xf0, 0xea, 0xad, 0x3c, 0x07, 0x97, 0x2f, 0xe8, 0x25, 0xc1 };
225 try testing.expectEqualSlices(u8, expected[0..], out[0..]);
226 }
227}