authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2021-02-28 08:29:38+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-02-28 20:40:49-08:00
loga5a3ad4f956bae1ca0e5a49de2e9ac7145170039
tree713106289091e9eea404dbf3267b1967d5998b06
parent58b14d01aeb919989d1750a8894350a07fd4e844

std/crypto: add AES-OCB

OCB has been around for a long time. It's simpler, faster and more secure than AES-GCM. RFC 7253 was published in 2014. OCB also won the CAESAR competition along with AEGIS. It's been implemented in OpenSSL and other libraries for years. So, why isn't everybody using it instead of GCM? And why don't we have it in Zig already? The sad reason for this was patents. GCM was invented only to work around these patents, and for all this time, OCB was that nice thing that everybody knew existed but that couldn't be freely used. That just changed. The OCB patents are now abandoned, and OCB's author just announced that OCB was officially public domain.

3 files changed, 353 insertions(+), 22 deletions(-)

lib/std/crypto.zig+8-22
...@@ -16,6 +16,11 @@ pub const aead = struct {...@@ -16,6 +16,11 @@ pub const aead = struct {
16 pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;16 pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;
17 };17 };
1818
19 pub const aes_ocb = struct {
20 pub const Aes128Ocb = @import("crypto/aes_ocb.zig").Aes128Ocb;
21 pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;
22 };
23
19 pub const Gimli = @import("crypto/gimli.zig").Aead;24 pub const Gimli = @import("crypto/gimli.zig").Aead;
2025
21 pub const chacha_poly = struct {26 pub const chacha_poly = struct {
...@@ -157,30 +162,11 @@ test "crypto" {...@@ -157,30 +162,11 @@ test "crypto" {
157 }162 }
158 }163 }
159164
160 _ = @import("crypto/aes.zig");165 _ = @import("crypto/aegis.zig");
161 _ = @import("crypto/bcrypt.zig");166 _ = @import("crypto/aes_gcm.zig");
167 _ = @import("crypto/aes_ocb.zig");
162 _ = @import("crypto/blake2.zig");168 _ = @import("crypto/blake2.zig");
163 _ = @import("crypto/blake3.zig");
164 _ = @import("crypto/chacha20.zig");169 _ = @import("crypto/chacha20.zig");
165 _ = @import("crypto/gimli.zig");
166 _ = @import("crypto/hmac.zig");
167 _ = @import("crypto/isap.zig");
168 _ = @import("crypto/md5.zig");
169 _ = @import("crypto/modes.zig");
170 _ = @import("crypto/pbkdf2.zig");
171 _ = @import("crypto/poly1305.zig");
172 _ = @import("crypto/sha1.zig");
173 _ = @import("crypto/sha2.zig");
174 _ = @import("crypto/sha3.zig");
175 _ = @import("crypto/salsa20.zig");
176 _ = @import("crypto/siphash.zig");
177 _ = @import("crypto/25519/curve25519.zig");
178 _ = @import("crypto/25519/ed25519.zig");
179 _ = @import("crypto/25519/edwards25519.zig");
180 _ = @import("crypto/25519/field.zig");
181 _ = @import("crypto/25519/scalar.zig");
182 _ = @import("crypto/25519/x25519.zig");
183 _ = @import("crypto/25519/ristretto255.zig");
184}170}
185171
186test "CSPRNG" {172test "CSPRNG" {
lib/std/crypto/aes_ocb.zig created+343
...@@ -0,0 +1,343 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std");
8const crypto = std.crypto;
9const aes = crypto.core.aes;
10const assert = std.debug.assert;
11const math = std.math;
12const mem = std.mem;
13
14pub const Aes128Ocb = AesOcb(aes.Aes128);
15pub const Aes256Ocb = AesOcb(aes.Aes256);
16
17const Block = [16]u8;
18
19/// AES-OCB (RFC 7253 - https://competitions.cr.yp.to/round3/ocbv11.pdf)
20fn AesOcb(comptime Aes: anytype) type {
21 const EncryptCtx = aes.AesEncryptCtx(Aes);
22 const DecryptCtx = aes.AesDecryptCtx(Aes);
23
24 return struct {
25 pub const key_length = Aes.key_bits / 8;
26 pub const nonce_length: usize = 12;
27 pub const tag_length: usize = 16;
28
29 const Lx = struct {
30 star: Block align(16),
31 dol: Block align(16),
32 table: [56]Block align(16) = undefined,
33 upto: usize,
34
35 fn double(l: Block) callconv(.Inline) Block {
36 const l_ = mem.readIntBig(u128, &l);
37 const l_2 = (l_ << 1) ^ (0x87 & -%(l_ >> 127));
38 var l2: Block = undefined;
39 mem.writeIntBig(u128, &l2, l_2);
40 return l2;
41 }
42
43 fn precomp(lx: *Lx, upto: usize) []const Block {
44 const table = &lx.table;
45 assert(upto < table.len);
46 var i = lx.upto;
47 while (i + 1 <= upto) : (i += 1) {
48 table[i + 1] = double(table[i]);
49 }
50 lx.upto = upto;
51 return lx.table[0 .. upto + 1];
52 }
53
54 fn init(aes_enc_ctx: EncryptCtx) Lx {
55 const zeros = [_]u8{0} ** 16;
56 var star: Block = undefined;
57 aes_enc_ctx.encrypt(&star, &zeros);
58 const dol = double(star);
59 var lx = Lx{ .star = star, .dol = dol, .upto = 0 };
60 lx.table[0] = double(dol);
61 return lx;
62 }
63 };
64
65 fn hash(aes_enc_ctx: EncryptCtx, lx: *Lx, a: []const u8) Block {
66 const full_blocks: usize = a.len / 16;
67 const x_max = if (full_blocks > 0) math.log2_int(usize, full_blocks) else 0;
68 const lt = lx.precomp(x_max);
69 var sum = [_]u8{0} ** 16;
70 var offset = [_]u8{0} ** 16;
71 var i: usize = 0;
72 while (i < full_blocks) : (i += 1) {
73 xorWith(&offset, lt[@ctz(usize, i + 1)]);
74 var e = xorBlocks(offset, a[i * 16 ..][0..16].*);
75 aes_enc_ctx.encrypt(&e, &e);
76 xorWith(&sum, e);
77 }
78 const leftover = a.len % 16;
79 if (leftover > 0) {
80 xorWith(&offset, lx.star);
81 var padded = [_]u8{0} ** 16;
82 mem.copy(u8, padded[0..leftover], a[i * 16 ..][0..leftover]);
83 padded[leftover] = 1;
84 var e = xorBlocks(offset, padded);
85 aes_enc_ctx.encrypt(&e, &e);
86 xorWith(&sum, e);
87 }
88 return sum;
89 }
90
91 fn getOffset(aes_enc_ctx: EncryptCtx, npub: [nonce_length]u8) Block {
92 var nx = [_]u8{0} ** 16;
93 nx[0] = @intCast(u8, @truncate(u7, tag_length * 8) << 1);
94 nx[16 - nonce_length - 1] = 1;
95 mem.copy(u8, nx[16 - nonce_length ..], &npub);
96
97 const bottom = @truncate(u6, nx[15]);
98 nx[15] &= 0xc0;
99 var ktop_: Block = undefined;
100 aes_enc_ctx.encrypt(&ktop_, &nx);
101 const ktop = mem.readIntBig(u128, &ktop_);
102 var stretch = (@as(u192, ktop) << 64) | @as(u192, @truncate(u64, ktop >> 64) ^ @truncate(u64, ktop >> 56));
103 var offset: Block = undefined;
104 mem.writeIntBig(u128, &offset, @truncate(u128, stretch >> (64 - @as(u7, bottom))));
105 return offset;
106 }
107
108 const has_aesni = comptime std.Target.x86.featureSetHas(std.Target.current.cpu.features, .aes);
109 const has_armaes = comptime std.Target.aarch64.featureSetHas(std.Target.current.cpu.features, .aes);
110 const wb: usize = if ((std.Target.current.cpu.arch == .x86_64 and has_aesni) or (std.Target.current.cpu.arch == .aarch64 and has_armaes)) 4 else 0;
111
112 /// c: ciphertext: output buffer should be of size m.len
113 /// tag: authentication tag: output MAC
114 /// m: message
115 /// ad: Associated Data
116 /// npub: public nonce
117 /// k: secret key
118 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void {
119 assert(c.len == m.len);
120
121 const aes_enc_ctx = Aes.initEnc(key);
122 const full_blocks: usize = m.len / 16;
123 const x_max = if (full_blocks > 0) math.log2_int(usize, full_blocks) else 0;
124 var lx = Lx.init(aes_enc_ctx);
125 const lt = lx.precomp(x_max);
126
127 var offset = getOffset(aes_enc_ctx, npub);
128 var sum = [_]u8{0} ** 16;
129 var i: usize = 0;
130
131 while (wb > 0 and i + wb <= full_blocks) : (i += wb) {
132 var offsets: [wb]Block align(16) = undefined;
133 var es: [16 * wb]u8 align(16) = undefined;
134 var j: usize = 0;
135 while (j < wb) : (j += 1) {
136 xorWith(&offset, lt[@ctz(usize, i + 1 + j)]);
137 offsets[j] = offset;
138 const p = m[(i + j) * 16 ..][0..16].*;
139 mem.copy(u8, es[j * 16 ..][0..16], &xorBlocks(p, offsets[j]));
140 xorWith(&sum, p);
141 }
142 aes_enc_ctx.encryptWide(wb, &es, &es);
143 j = 0;
144 while (j < wb) : (j += 1) {
145 const e = es[j * 16 ..][0..16].*;
146 mem.copy(u8, c[(i + j) * 16 ..][0..16], &xorBlocks(e, offsets[j]));
147 }
148 }
149 while (i < full_blocks) : (i += 1) {
150 xorWith(&offset, lt[@ctz(usize, i + 1)]);
151 const p = m[i * 16 ..][0..16].*;
152 var e = xorBlocks(p, offset);
153 aes_enc_ctx.encrypt(&e, &e);
154 mem.copy(u8, c[i * 16 ..][0..16], &xorBlocks(e, offset));
155 xorWith(&sum, p);
156 }
157 const leftover = m.len % 16;
158 if (leftover > 0) {
159 xorWith(&offset, lx.star);
160 var pad = offset;
161 aes_enc_ctx.encrypt(&pad, &pad);
162 for (m[i * 16 ..]) |x, j| {
163 c[i * 16 + j] = pad[j] ^ x;
164 }
165 var e = [_]u8{0} ** 16;
166 mem.copy(u8, e[0..leftover], m[i * 16 ..][0..leftover]);
167 e[leftover] = 0x80;
168 xorWith(&sum, e);
169 }
170 var e = xorBlocks(xorBlocks(sum, offset), lx.dol);
171 aes_enc_ctx.encrypt(&e, &e);
172 tag.* = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
173 }
174
175 /// m: message: output buffer should be of size c.len
176 /// c: ciphertext
177 /// tag: authentication tag
178 /// ad: Associated Data
179 /// npub: public nonce
180 /// k: secret key
181 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void {
182 assert(c.len == m.len);
183
184 const aes_enc_ctx = Aes.initEnc(key);
185 const aes_dec_ctx = DecryptCtx.initFromEnc(aes_enc_ctx);
186 const full_blocks: usize = m.len / 16;
187 const x_max = if (full_blocks > 0) math.log2_int(usize, full_blocks) else 0;
188 var lx = Lx.init(aes_enc_ctx);
189 const lt = lx.precomp(x_max);
190
191 var offset = getOffset(aes_enc_ctx, npub);
192 var sum = [_]u8{0} ** 16;
193 var i: usize = 0;
194
195 while (wb > 0 and i + wb <= full_blocks) : (i += wb) {
196 var offsets: [wb]Block align(16) = undefined;
197 var es: [16 * wb]u8 align(16) = undefined;
198 var j: usize = 0;
199 while (j < wb) : (j += 1) {
200 xorWith(&offset, lt[@ctz(usize, i + 1 + j)]);
201 offsets[j] = offset;
202 const q = c[(i + j) * 16 ..][0..16].*;
203 mem.copy(u8, es[j * 16 ..][0..16], &xorBlocks(q, offsets[j]));
204 }
205 aes_dec_ctx.decryptWide(wb, &es, &es);
206 j = 0;
207 while (j < wb) : (j += 1) {
208 const p = xorBlocks(es[j * 16 ..][0..16].*, offsets[j]);
209 mem.copy(u8, m[(i + j) * 16 ..][0..16], &p);
210 xorWith(&sum, p);
211 }
212 }
213 while (i < full_blocks) : (i += 1) {
214 xorWith(&offset, lt[@ctz(usize, i + 1)]);
215 const q = c[i * 16 ..][0..16].*;
216 var e = xorBlocks(q, offset);
217 aes_dec_ctx.decrypt(&e, &e);
218 const p = xorBlocks(e, offset);
219 mem.copy(u8, m[i * 16 ..][0..16], &p);
220 xorWith(&sum, p);
221 }
222 const leftover = m.len % 16;
223 if (leftover > 0) {
224 xorWith(&offset, lx.star);
225 var pad = offset;
226 aes_enc_ctx.encrypt(&pad, &pad);
227 for (c[i * 16 ..]) |x, j| {
228 m[i * 16 + j] = pad[j] ^ x;
229 }
230 var e = [_]u8{0} ** 16;
231 mem.copy(u8, e[0..leftover], m[i * 16 ..][0..leftover]);
232 e[leftover] = 0x80;
233 xorWith(&sum, e);
234 }
235 var e = xorBlocks(xorBlocks(sum, offset), lx.dol);
236 aes_enc_ctx.encrypt(&e, &e);
237 var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
238 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
239 crypto.utils.secureZero(u8, &computed_tag);
240 if (!verify) {
241 return error.AuthenticationFailed;
242 }
243 }
244 };
245}
246
247fn xorBlocks(x: Block, y: Block) callconv(.Inline) Block {
248 var z: Block = x;
249 for (z) |*v, i| {
250 v.* = x[i] ^ y[i];
251 }
252 return z;
253}
254
255fn xorWith(x: *Block, y: Block) callconv(.Inline) void {
256 for (x) |*v, i| {
257 v.* ^= y[i];
258 }
259}
260
261const hexToBytes = std.fmt.hexToBytes;
262
263test "AesOcb test vector 1" {
264 var k: [Aes128Ocb.key_length]u8 = undefined;
265 var nonce: [Aes128Ocb.nonce_length]u8 = undefined;
266 var tag: [Aes128Ocb.tag_length]u8 = undefined;
267 _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F");
268 _ = try hexToBytes(&nonce, "BBAA99887766554433221100");
269
270 var c: [0]u8 = undefined;
271 Aes128Ocb.encrypt(&c, &tag, "", "", nonce, k);
272
273 var expected_c: [c.len]u8 = undefined;
274 var expected_tag: [tag.len]u8 = undefined;
275 _ = try hexToBytes(&expected_tag, "785407BFFFC8AD9EDCC5520AC9111EE6");
276
277 var m: [0]u8 = undefined;
278 try Aes128Ocb.decrypt(&m, "", tag, "", nonce, k);
279}
280
281test "AesOcb test vector 2" {
282 var k: [Aes128Ocb.key_length]u8 = undefined;
283 var nonce: [Aes128Ocb.nonce_length]u8 = undefined;
284 var tag: [Aes128Ocb.tag_length]u8 = undefined;
285 var ad: [40]u8 = undefined;
286 _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F");
287 _ = try hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627");
288 _ = try hexToBytes(&nonce, "BBAA9988776655443322110E");
289
290 var c: [0]u8 = undefined;
291 Aes128Ocb.encrypt(&c, &tag, "", &ad, nonce, k);
292
293 var expected_tag: [tag.len]u8 = undefined;
294 _ = try hexToBytes(&expected_tag, "C5CD9D1850C141E358649994EE701B68");
295
296 var m: [0]u8 = undefined;
297 try Aes128Ocb.decrypt(&m, &c, tag, &ad, nonce, k);
298}
299
300test "AesOcb test vector 3" {
301 var k: [Aes128Ocb.key_length]u8 = undefined;
302 var nonce: [Aes128Ocb.nonce_length]u8 = undefined;
303 var tag: [Aes128Ocb.tag_length]u8 = undefined;
304 var m: [40]u8 = undefined;
305 var c: [m.len]u8 = undefined;
306 _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F");
307 _ = try hexToBytes(&m, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627");
308 _ = try hexToBytes(&nonce, "BBAA9988776655443322110F");
309
310 Aes128Ocb.encrypt(&c, &tag, &m, "", nonce, k);
311
312 var expected_c: [c.len]u8 = undefined;
313 var expected_tag: [tag.len]u8 = undefined;
314 _ = try hexToBytes(&expected_tag, "479AD363AC366B95A98CA5F3000B1479");
315 _ = try hexToBytes(&expected_c, "4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E");
316
317 var m2: [m.len]u8 = undefined;
318 try Aes128Ocb.decrypt(&m2, &c, tag, "", nonce, k);
319 assert(mem.eql(u8, &m, &m2));
320}
321
322test "AesOcb test vector 4" {
323 var k: [Aes128Ocb.key_length]u8 = undefined;
324 var nonce: [Aes128Ocb.nonce_length]u8 = undefined;
325 var tag: [Aes128Ocb.tag_length]u8 = undefined;
326 var m: [40]u8 = undefined;
327 var ad = m;
328 var c: [m.len]u8 = undefined;
329 _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F");
330 _ = try hexToBytes(&m, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627");
331 _ = try hexToBytes(&nonce, "BBAA99887766554433221104");
332
333 Aes128Ocb.encrypt(&c, &tag, &m, &ad, nonce, k);
334
335 var expected_c: [c.len]u8 = undefined;
336 var expected_tag: [tag.len]u8 = undefined;
337 _ = try hexToBytes(&expected_tag, "3AD7A4FF3835B8C5701C1CCEC8FC3358");
338 _ = try hexToBytes(&expected_c, "571D535B60B277188BE5147170A9A22C");
339
340 var m2: [m.len]u8 = undefined;
341 try Aes128Ocb.decrypt(&m2, &c, tag, &ad, nonce, k);
342 assert(mem.eql(u8, &m, &m2));
343}
lib/std/crypto/benchmark.zig+2
...@@ -208,6 +208,8 @@ const aeads = [_]Crypto{...@@ -208,6 +208,8 @@ const aeads = [_]Crypto{
208 Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },208 Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },
209 Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },209 Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },
210 Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" },210 Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" },
211 Crypto{ .ty = crypto.aead.aes_ocb.Aes128Ocb, .name = "aes128-ocb" },
212 Crypto{ .ty = crypto.aead.aes_ocb.Aes256Ocb, .name = "aes256-ocb" },
211 Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" },213 Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" },
212};214};
213215