diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index da6ec2edf0..24ca549479 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -16,6 +16,11 @@ pub const aead = struct { pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm; }; + pub const aes_ocb = struct { + pub const Aes128Ocb = @import("crypto/aes_ocb.zig").Aes128Ocb; + pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb; + }; + pub const Gimli = @import("crypto/gimli.zig").Aead; pub const chacha_poly = struct { @@ -157,30 +162,11 @@ test "crypto" { } } - _ = @import("crypto/aes.zig"); - _ = @import("crypto/bcrypt.zig"); + _ = @import("crypto/aegis.zig"); + _ = @import("crypto/aes_gcm.zig"); + _ = @import("crypto/aes_ocb.zig"); _ = @import("crypto/blake2.zig"); - _ = @import("crypto/blake3.zig"); _ = @import("crypto/chacha20.zig"); - _ = @import("crypto/gimli.zig"); - _ = @import("crypto/hmac.zig"); - _ = @import("crypto/isap.zig"); - _ = @import("crypto/md5.zig"); - _ = @import("crypto/modes.zig"); - _ = @import("crypto/pbkdf2.zig"); - _ = @import("crypto/poly1305.zig"); - _ = @import("crypto/sha1.zig"); - _ = @import("crypto/sha2.zig"); - _ = @import("crypto/sha3.zig"); - _ = @import("crypto/salsa20.zig"); - _ = @import("crypto/siphash.zig"); - _ = @import("crypto/25519/curve25519.zig"); - _ = @import("crypto/25519/ed25519.zig"); - _ = @import("crypto/25519/edwards25519.zig"); - _ = @import("crypto/25519/field.zig"); - _ = @import("crypto/25519/scalar.zig"); - _ = @import("crypto/25519/x25519.zig"); - _ = @import("crypto/25519/ristretto255.zig"); } test "CSPRNG" { diff --git a/lib/std/crypto/aes_ocb.zig b/lib/std/crypto/aes_ocb.zig new file mode 100644 index 0000000000..ab0138f181 --- /dev/null +++ b/lib/std/crypto/aes_ocb.zig @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +const std = @import("std"); +const crypto = std.crypto; +const aes = crypto.core.aes; +const assert = std.debug.assert; +const math = std.math; +const mem = std.mem; + +pub const Aes128Ocb = AesOcb(aes.Aes128); +pub const Aes256Ocb = AesOcb(aes.Aes256); + +const Block = [16]u8; + +/// AES-OCB (RFC 7253 - https://competitions.cr.yp.to/round3/ocbv11.pdf) +fn AesOcb(comptime Aes: anytype) type { + const EncryptCtx = aes.AesEncryptCtx(Aes); + const DecryptCtx = aes.AesDecryptCtx(Aes); + + return struct { + pub const key_length = Aes.key_bits / 8; + pub const nonce_length: usize = 12; + pub const tag_length: usize = 16; + + const Lx = struct { + star: Block align(16), + dol: Block align(16), + table: [56]Block align(16) = undefined, + upto: usize, + + fn double(l: Block) callconv(.Inline) Block { + const l_ = mem.readIntBig(u128, &l); + const l_2 = (l_ << 1) ^ (0x87 & -%(l_ >> 127)); + var l2: Block = undefined; + mem.writeIntBig(u128, &l2, l_2); + return l2; + } + + fn precomp(lx: *Lx, upto: usize) []const Block { + const table = &lx.table; + assert(upto < table.len); + var i = lx.upto; + while (i + 1 <= upto) : (i += 1) { + table[i + 1] = double(table[i]); + } + lx.upto = upto; + return lx.table[0 .. upto + 1]; + } + + fn init(aes_enc_ctx: EncryptCtx) Lx { + const zeros = [_]u8{0} ** 16; + var star: Block = undefined; + aes_enc_ctx.encrypt(&star, &zeros); + const dol = double(star); + var lx = Lx{ .star = star, .dol = dol, .upto = 0 }; + lx.table[0] = double(dol); + return lx; + } + }; + + fn hash(aes_enc_ctx: EncryptCtx, lx: *Lx, a: []const u8) Block { + const full_blocks: usize = a.len / 16; + const x_max = if (full_blocks > 0) math.log2_int(usize, full_blocks) else 0; + const lt = lx.precomp(x_max); + var sum = [_]u8{0} ** 16; + var offset = [_]u8{0} ** 16; + var i: usize = 0; + while (i < full_blocks) : (i += 1) { + xorWith(&offset, lt[@ctz(usize, i + 1)]); + var e = xorBlocks(offset, a[i * 16 ..][0..16].*); + aes_enc_ctx.encrypt(&e, &e); + xorWith(&sum, e); + } + const leftover = a.len % 16; + if (leftover > 0) { + xorWith(&offset, lx.star); + var padded = [_]u8{0} ** 16; + mem.copy(u8, padded[0..leftover], a[i * 16 ..][0..leftover]); + padded[leftover] = 1; + var e = xorBlocks(offset, padded); + aes_enc_ctx.encrypt(&e, &e); + xorWith(&sum, e); + } + return sum; + } + + fn getOffset(aes_enc_ctx: EncryptCtx, npub: [nonce_length]u8) Block { + var nx = [_]u8{0} ** 16; + nx[0] = @intCast(u8, @truncate(u7, tag_length * 8) << 1); + nx[16 - nonce_length - 1] = 1; + mem.copy(u8, nx[16 - nonce_length ..], &npub); + + const bottom = @truncate(u6, nx[15]); + nx[15] &= 0xc0; + var ktop_: Block = undefined; + aes_enc_ctx.encrypt(&ktop_, &nx); + const ktop = mem.readIntBig(u128, &ktop_); + var stretch = (@as(u192, ktop) << 64) | @as(u192, @truncate(u64, ktop >> 64) ^ @truncate(u64, ktop >> 56)); + var offset: Block = undefined; + mem.writeIntBig(u128, &offset, @truncate(u128, stretch >> (64 - @as(u7, bottom)))); + return offset; + } + + const has_aesni = comptime std.Target.x86.featureSetHas(std.Target.current.cpu.features, .aes); + const has_armaes = comptime std.Target.aarch64.featureSetHas(std.Target.current.cpu.features, .aes); + 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; + + /// c: ciphertext: output buffer should be of size m.len + /// tag: authentication tag: output MAC + /// m: message + /// ad: Associated Data + /// npub: public nonce + /// k: secret key + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { + assert(c.len == m.len); + + const aes_enc_ctx = Aes.initEnc(key); + const full_blocks: usize = m.len / 16; + const x_max = if (full_blocks > 0) math.log2_int(usize, full_blocks) else 0; + var lx = Lx.init(aes_enc_ctx); + const lt = lx.precomp(x_max); + + var offset = getOffset(aes_enc_ctx, npub); + var sum = [_]u8{0} ** 16; + var i: usize = 0; + + while (wb > 0 and i + wb <= full_blocks) : (i += wb) { + var offsets: [wb]Block align(16) = undefined; + var es: [16 * wb]u8 align(16) = undefined; + var j: usize = 0; + while (j < wb) : (j += 1) { + xorWith(&offset, lt[@ctz(usize, i + 1 + j)]); + offsets[j] = offset; + const p = m[(i + j) * 16 ..][0..16].*; + mem.copy(u8, es[j * 16 ..][0..16], &xorBlocks(p, offsets[j])); + xorWith(&sum, p); + } + aes_enc_ctx.encryptWide(wb, &es, &es); + j = 0; + while (j < wb) : (j += 1) { + const e = es[j * 16 ..][0..16].*; + mem.copy(u8, c[(i + j) * 16 ..][0..16], &xorBlocks(e, offsets[j])); + } + } + while (i < full_blocks) : (i += 1) { + xorWith(&offset, lt[@ctz(usize, i + 1)]); + const p = m[i * 16 ..][0..16].*; + var e = xorBlocks(p, offset); + aes_enc_ctx.encrypt(&e, &e); + mem.copy(u8, c[i * 16 ..][0..16], &xorBlocks(e, offset)); + xorWith(&sum, p); + } + const leftover = m.len % 16; + if (leftover > 0) { + xorWith(&offset, lx.star); + var pad = offset; + aes_enc_ctx.encrypt(&pad, &pad); + for (m[i * 16 ..]) |x, j| { + c[i * 16 + j] = pad[j] ^ x; + } + var e = [_]u8{0} ** 16; + mem.copy(u8, e[0..leftover], m[i * 16 ..][0..leftover]); + e[leftover] = 0x80; + xorWith(&sum, e); + } + var e = xorBlocks(xorBlocks(sum, offset), lx.dol); + aes_enc_ctx.encrypt(&e, &e); + tag.* = xorBlocks(e, hash(aes_enc_ctx, &lx, ad)); + } + + /// m: message: output buffer should be of size c.len + /// c: ciphertext + /// tag: authentication tag + /// ad: Associated Data + /// npub: public nonce + /// k: secret key + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { + assert(c.len == m.len); + + const aes_enc_ctx = Aes.initEnc(key); + const aes_dec_ctx = DecryptCtx.initFromEnc(aes_enc_ctx); + const full_blocks: usize = m.len / 16; + const x_max = if (full_blocks > 0) math.log2_int(usize, full_blocks) else 0; + var lx = Lx.init(aes_enc_ctx); + const lt = lx.precomp(x_max); + + var offset = getOffset(aes_enc_ctx, npub); + var sum = [_]u8{0} ** 16; + var i: usize = 0; + + while (wb > 0 and i + wb <= full_blocks) : (i += wb) { + var offsets: [wb]Block align(16) = undefined; + var es: [16 * wb]u8 align(16) = undefined; + var j: usize = 0; + while (j < wb) : (j += 1) { + xorWith(&offset, lt[@ctz(usize, i + 1 + j)]); + offsets[j] = offset; + const q = c[(i + j) * 16 ..][0..16].*; + mem.copy(u8, es[j * 16 ..][0..16], &xorBlocks(q, offsets[j])); + } + aes_dec_ctx.decryptWide(wb, &es, &es); + j = 0; + while (j < wb) : (j += 1) { + const p = xorBlocks(es[j * 16 ..][0..16].*, offsets[j]); + mem.copy(u8, m[(i + j) * 16 ..][0..16], &p); + xorWith(&sum, p); + } + } + while (i < full_blocks) : (i += 1) { + xorWith(&offset, lt[@ctz(usize, i + 1)]); + const q = c[i * 16 ..][0..16].*; + var e = xorBlocks(q, offset); + aes_dec_ctx.decrypt(&e, &e); + const p = xorBlocks(e, offset); + mem.copy(u8, m[i * 16 ..][0..16], &p); + xorWith(&sum, p); + } + const leftover = m.len % 16; + if (leftover > 0) { + xorWith(&offset, lx.star); + var pad = offset; + aes_enc_ctx.encrypt(&pad, &pad); + for (c[i * 16 ..]) |x, j| { + m[i * 16 + j] = pad[j] ^ x; + } + var e = [_]u8{0} ** 16; + mem.copy(u8, e[0..leftover], m[i * 16 ..][0..leftover]); + e[leftover] = 0x80; + xorWith(&sum, e); + } + var e = xorBlocks(xorBlocks(sum, offset), lx.dol); + aes_enc_ctx.encrypt(&e, &e); + var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad)); + const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + crypto.utils.secureZero(u8, &computed_tag); + if (!verify) { + return error.AuthenticationFailed; + } + } + }; +} + +fn xorBlocks(x: Block, y: Block) callconv(.Inline) Block { + var z: Block = x; + for (z) |*v, i| { + v.* = x[i] ^ y[i]; + } + return z; +} + +fn xorWith(x: *Block, y: Block) callconv(.Inline) void { + for (x) |*v, i| { + v.* ^= y[i]; + } +} + +const hexToBytes = std.fmt.hexToBytes; + +test "AesOcb test vector 1" { + var k: [Aes128Ocb.key_length]u8 = undefined; + var nonce: [Aes128Ocb.nonce_length]u8 = undefined; + var tag: [Aes128Ocb.tag_length]u8 = undefined; + _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F"); + _ = try hexToBytes(&nonce, "BBAA99887766554433221100"); + + var c: [0]u8 = undefined; + Aes128Ocb.encrypt(&c, &tag, "", "", nonce, k); + + var expected_c: [c.len]u8 = undefined; + var expected_tag: [tag.len]u8 = undefined; + _ = try hexToBytes(&expected_tag, "785407BFFFC8AD9EDCC5520AC9111EE6"); + + var m: [0]u8 = undefined; + try Aes128Ocb.decrypt(&m, "", tag, "", nonce, k); +} + +test "AesOcb test vector 2" { + var k: [Aes128Ocb.key_length]u8 = undefined; + var nonce: [Aes128Ocb.nonce_length]u8 = undefined; + var tag: [Aes128Ocb.tag_length]u8 = undefined; + var ad: [40]u8 = undefined; + _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F"); + _ = try hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627"); + _ = try hexToBytes(&nonce, "BBAA9988776655443322110E"); + + var c: [0]u8 = undefined; + Aes128Ocb.encrypt(&c, &tag, "", &ad, nonce, k); + + var expected_tag: [tag.len]u8 = undefined; + _ = try hexToBytes(&expected_tag, "C5CD9D1850C141E358649994EE701B68"); + + var m: [0]u8 = undefined; + try Aes128Ocb.decrypt(&m, &c, tag, &ad, nonce, k); +} + +test "AesOcb test vector 3" { + var k: [Aes128Ocb.key_length]u8 = undefined; + var nonce: [Aes128Ocb.nonce_length]u8 = undefined; + var tag: [Aes128Ocb.tag_length]u8 = undefined; + var m: [40]u8 = undefined; + var c: [m.len]u8 = undefined; + _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F"); + _ = try hexToBytes(&m, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627"); + _ = try hexToBytes(&nonce, "BBAA9988776655443322110F"); + + Aes128Ocb.encrypt(&c, &tag, &m, "", nonce, k); + + var expected_c: [c.len]u8 = undefined; + var expected_tag: [tag.len]u8 = undefined; + _ = try hexToBytes(&expected_tag, "479AD363AC366B95A98CA5F3000B1479"); + _ = try hexToBytes(&expected_c, "4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E"); + + var m2: [m.len]u8 = undefined; + try Aes128Ocb.decrypt(&m2, &c, tag, "", nonce, k); + assert(mem.eql(u8, &m, &m2)); +} + +test "AesOcb test vector 4" { + var k: [Aes128Ocb.key_length]u8 = undefined; + var nonce: [Aes128Ocb.nonce_length]u8 = undefined; + var tag: [Aes128Ocb.tag_length]u8 = undefined; + var m: [40]u8 = undefined; + var ad = m; + var c: [m.len]u8 = undefined; + _ = try hexToBytes(&k, "000102030405060708090A0B0C0D0E0F"); + _ = try hexToBytes(&m, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627"); + _ = try hexToBytes(&nonce, "BBAA99887766554433221104"); + + Aes128Ocb.encrypt(&c, &tag, &m, &ad, nonce, k); + + var expected_c: [c.len]u8 = undefined; + var expected_tag: [tag.len]u8 = undefined; + _ = try hexToBytes(&expected_tag, "3AD7A4FF3835B8C5701C1CCEC8FC3358"); + _ = try hexToBytes(&expected_c, "571D535B60B277188BE5147170A9A22C"); + + var m2: [m.len]u8 = undefined; + try Aes128Ocb.decrypt(&m2, &c, tag, &ad, nonce, k); + assert(mem.eql(u8, &m, &m2)); +} diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 476ed86ede..e3ffa62ed1 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -208,6 +208,8 @@ const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" }, Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" }, Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" }, + Crypto{ .ty = crypto.aead.aes_ocb.Aes128Ocb, .name = "aes128-ocb" }, + Crypto{ .ty = crypto.aead.aes_ocb.Aes256Ocb, .name = "aes256-ocb" }, Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" }, };