diff options
| -rw-r--r-- | lib/std/crypto.zig | 13 | ||||
| -rw-r--r-- | lib/std/crypto/chacha20.zig | 282 |
2 files changed, 235 insertions, 60 deletions
diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 9fbe70f815..c1909a1823 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -29,9 +29,10 @@ pub const HmacSha1 = hmac.HmacSha1; pub const HmacSha256 = hmac.HmacSha256; pub const HmacBlake2s256 = hmac.HmacBlake2s256; -const import_chaCha20 = @import("crypto/chacha20.zig"); -pub const chaCha20IETF = import_chaCha20.chaCha20IETF; -pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce; +pub const chacha20 = @import("crypto/chacha20.zig"); +pub const chaCha20IETF = chacha20.chaCha20IETF; +pub const chaCha20With64BitNonce = chacha20.chaCha20With64BitNonce; +pub const xChaCha20IETF = chacha20.xChaCha20IETF; pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; @@ -45,6 +46,12 @@ pub const Edwards25519 = @import("crypto/25519/edwards25519.zig").Edwards25519; pub const X25519 = @import("crypto/25519/x25519.zig").X25519; pub const Ristretto255 = @import("crypto/25519/ristretto255.zig").Ristretto255; +pub const aead = struct { + pub const Gimli = gimli.Aead; + pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305; + pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305; +}; + const std = @import("std.zig"); pub const randomBytes = std.os.getrandom; diff --git a/lib/std/crypto/chacha20.zig b/lib/std/crypto/chacha20.zig index f6008745af..e51123eb81 100644 --- a/lib/std/crypto/chacha20.zig +++ b/lib/std/crypto/chacha20.zig @@ -25,12 +25,24 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { }; } -// The chacha family of ciphers are based on the salsa family. -fn salsa20_wordtobyte(out: []u8, input: [16]u32) void { - assert(out.len >= 64); +fn initContext(key: [8]u32, d: [4]u32) [16]u32 { + var ctx: [16]u32 = undefined; + const c = "expand 32-byte k"; + const constant_le = comptime [_]u32{ + mem.readIntLittle(u32, c[0..4]), + mem.readIntLittle(u32, c[4..8]), + mem.readIntLittle(u32, c[8..12]), + mem.readIntLittle(u32, c[12..16]), + }; + mem.copy(u32, ctx[0..], constant_le[0..4]); + mem.copy(u32, ctx[4..12], key[0..8]); + mem.copy(u32, ctx[12..16], d[0..4]); - var x: [16]u32 = undefined; + return ctx; +} +// The chacha family of ciphers are based on the salsa family. +fn chacha20Core(x: []u32, input: [16]u32) void { for (x) |_, i| x[i] = input[i]; @@ -59,33 +71,27 @@ fn salsa20_wordtobyte(out: []u8, input: [16]u32) void { x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); } } +} +fn hashToBytes(out: []u8, x: [16]u32) void { for (x) |_, i| { - mem.writeIntLittle(u32, out[4 * i ..][0..4], x[i] +% input[i]); + mem.writeIntLittle(u32, out[4 * i ..][0..4], x[i]); } } fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { - var ctx: [16]u32 = undefined; + var ctx = initContext(key, counter); var remaining: usize = if (in.len > out.len) in.len else out.len; var cursor: usize = 0; - const c = "expand 32-byte k"; - const constant_le = [_]u32{ - mem.readIntLittle(u32, c[0..4]), - mem.readIntLittle(u32, c[4..8]), - mem.readIntLittle(u32, c[8..12]), - mem.readIntLittle(u32, c[12..16]), - }; - - mem.copy(u32, ctx[0..], constant_le[0..4]); - mem.copy(u32, ctx[4..12], key[0..8]); - mem.copy(u32, ctx[12..16], counter[0..4]); - while (true) { + var x: [16]u32 = undefined; var buf: [64]u8 = undefined; - salsa20_wordtobyte(buf[0..], ctx); - + chacha20Core(x[0..], ctx); + for (x) |_, i| { + x[i] +%= ctx[i]; + } + hashToBytes(buf[0..], x); if (remaining < 64) { var i: usize = 0; while (i < remaining) : (i += 1) @@ -104,6 +110,20 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo } } +fn keyToWords(key: [32]u8) [8]u32 { + var k: [8]u32 = undefined; + k[0] = mem.readIntLittle(u32, key[0..4]); + k[1] = mem.readIntLittle(u32, key[4..8]); + k[2] = mem.readIntLittle(u32, key[8..12]); + k[3] = mem.readIntLittle(u32, key[12..16]); + k[4] = mem.readIntLittle(u32, key[16..20]); + k[5] = mem.readIntLittle(u32, key[20..24]); + k[6] = mem.readIntLittle(u32, key[24..28]); + k[7] = mem.readIntLittle(u32, key[28..32]); + + return k; +} + /// ChaCha20 avoids the possibility of timing attacks, as there are no branches /// on secret key data. /// @@ -116,23 +136,12 @@ pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: assert(in.len >= out.len); assert((in.len >> 6) + counter <= maxInt(u32)); - var k: [8]u32 = undefined; var c: [4]u32 = undefined; - - k[0] = mem.readIntLittle(u32, key[0..4]); - k[1] = mem.readIntLittle(u32, key[4..8]); - k[2] = mem.readIntLittle(u32, key[8..12]); - k[3] = mem.readIntLittle(u32, key[12..16]); - k[4] = mem.readIntLittle(u32, key[16..20]); - k[5] = mem.readIntLittle(u32, key[20..24]); - k[6] = mem.readIntLittle(u32, key[24..28]); - k[7] = mem.readIntLittle(u32, key[28..32]); - c[0] = counter; c[1] = mem.readIntLittle(u32, nonce[0..4]); c[2] = mem.readIntLittle(u32, nonce[4..8]); c[3] = mem.readIntLittle(u32, nonce[8..12]); - chaCha20_internal(out, in, k, c); + chaCha20_internal(out, in, keyToWords(key), c); } /// This is the original ChaCha20 before RFC 7539, which recommends using the @@ -143,18 +152,8 @@ pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32] assert(counter +% (in.len >> 6) >= counter); var cursor: usize = 0; - var k: [8]u32 = undefined; + const k = keyToWords(key); var c: [4]u32 = undefined; - - k[0] = mem.readIntLittle(u32, key[0..4]); - k[1] = mem.readIntLittle(u32, key[4..8]); - k[2] = mem.readIntLittle(u32, key[8..12]); - k[3] = mem.readIntLittle(u32, key[12..16]); - k[4] = mem.readIntLittle(u32, key[16..20]); - k[5] = mem.readIntLittle(u32, key[20..24]); - k[6] = mem.readIntLittle(u32, key[24..28]); - k[7] = mem.readIntLittle(u32, key[28..32]); - c[0] = @truncate(u32, counter); c[1] = @truncate(u32, counter >> 32); c[2] = mem.readIntLittle(u32, nonce[0..4]); @@ -437,15 +436,15 @@ test "crypto.chacha20 test vector 5" { pub const chacha20poly1305_tag_size = 16; -pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { - assert(dst.len >= plaintext.len + chacha20poly1305_tag_size); +pub fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { + assert(ciphertext.len >= plaintext.len); // derive poly1305 key var polyKey = [_]u8{0} ** 32; chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce); // encrypt plaintext - chaCha20IETF(dst[0..plaintext.len], plaintext, 1, key, nonce); + chaCha20IETF(ciphertext[0..plaintext.len], plaintext, 1, key, nonce); // construct mac var mac = Poly1305.init(polyKey[0..]); @@ -455,7 +454,7 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, const padding = 16 - (data.len % 16); mac.update(zeros[0..padding]); } - mac.update(dst[0..plaintext.len]); + mac.update(ciphertext[0..plaintext.len]); if (plaintext.len % 16 != 0) { const zeros = [_]u8{0} ** 16; const padding = 16 - (plaintext.len % 16); @@ -465,19 +464,17 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, mem.writeIntLittle(u64, lens[0..8], data.len); mem.writeIntLittle(u64, lens[8..16], plaintext.len); mac.update(lens[0..]); - mac.final(dst[plaintext.len..]); + mac.final(tag); } -/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal. -pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { - if (msgAndTag.len < chacha20poly1305_tag_size) { - return error.InvalidMessage; - } +pub fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { + return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_size], plaintext, data, key, nonce); +} +/// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached. +pub fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_size]u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { // split ciphertext and tag - assert(dst.len >= msgAndTag.len - chacha20poly1305_tag_size); - var ciphertext = msgAndTag[0 .. msgAndTag.len - chacha20poly1305_tag_size]; - var polyTag = msgAndTag[ciphertext.len..]; + assert(dst.len >= ciphertext.len); // derive poly1305 key var polyKey = [_]u8{0} ** 32; @@ -510,7 +507,7 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, // See https://github.com/ziglang/zig/issues/1776 var acc: u8 = 0; for (computedTag) |_, i| { - acc |= (computedTag[i] ^ polyTag[i]); + acc |= (computedTag[i] ^ tag[i]); } if (acc != 0) { return error.AuthenticationFailed; @@ -520,6 +517,75 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce); } +/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal. +pub fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { + if (ciphertextAndTag.len < chacha20poly1305_tag_size) { + return error.InvalidMessage; + } + const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_size; + return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_size], data, key, nonce); +} + +fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { + var c: [4]u32 = undefined; + for (c) |_, i| { + c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); + } + const ctx = initContext(keyToWords(key), c); + var x: [16]u32 = undefined; + chacha20Core(x[0..], ctx); + var out: [32]u8 = undefined; + mem.writeIntLittle(u32, out[0..4], x[0]); + mem.writeIntLittle(u32, out[4..8], x[1]); + mem.writeIntLittle(u32, out[8..12], x[2]); + mem.writeIntLittle(u32, out[12..16], x[3]); + mem.writeIntLittle(u32, out[16..20], x[12]); + mem.writeIntLittle(u32, out[20..24], x[13]); + mem.writeIntLittle(u32, out[24..28], x[14]); + mem.writeIntLittle(u32, out[28..32], x[15]); + + return out; +} + +fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } { + var subnonce: [12]u8 = undefined; + mem.set(u8, subnonce[0..4], 0); + mem.copy(u8, subnonce[4..], nonce[16..24]); + return .{ + .key = hchacha20(nonce[0..16].*, key), + .nonce = subnonce, + }; +} + +pub fn xChaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void { + const extended = extend(key, nonce); + chaCha20IETF(out, in, counter, extended.key, extended.nonce); +} + +pub const xchacha20poly1305_tag_size = 16; + +pub fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { + const extended = extend(key, nonce); + return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce); +} + +pub fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { + const extended = extend(key, nonce); + return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce); +} + +/// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached. +pub fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_size]u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void { + const extended = extend(key, nonce); + return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce); +} + +/// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal. +pub fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void { + const extended = extend(key, nonce); + return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce); +} + test "seal" { { const plaintext = ""; @@ -636,3 +702,105 @@ test "open" { testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce)); } } + +test "crypto.xchacha20" { + const key = [_]u8{69} ** 32; + const nonce = [_]u8{42} ** 24; + const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + { + var ciphertext: [input.len]u8 = undefined; + xChaCha20IETF(ciphertext[0..], input[0..], 0, key, nonce); + var buf: [2 * ciphertext.len]u8 = undefined; + testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{ciphertext}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); + } + { + const data = "Additional data"; + var ciphertext: [input.len + xchacha20poly1305_tag_size]u8 = undefined; + xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce); + var out: [input.len]u8 = undefined; + try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce); + var buf: [2 * ciphertext.len]u8 = undefined; + testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{ciphertext}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); + testing.expectEqualSlices(u8, out[0..], input); + ciphertext[0] += 1; + testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce)); + } +} + +pub const Chacha20Poly1305 = struct { + pub const tag_length = 16; + pub const nonce_length = 12; + pub const key_length = 32; + + /// c: ciphertext: output buffer should be of size m.len + /// at: authentication tag: output MAC + /// m: message + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn encrypt(c: []u8, at: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { + assert(c.len == m.len); + return chacha20poly1305SealDetached(c, at, m, ad, k, npub); + } + + /// m: message: output buffer should be of size c.len + /// c: ciphertext + /// at: authentication tag + /// ad: Associated Data + /// npub: public nonce + /// k: private key + /// NOTE: the check of the authentication tag is currently not done in constant time + pub fn decrypt(m: []u8, c: []const u8, at: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { + assert(c.len == m.len); + return try chacha20poly1305OpenDetached(m, c, at[0..], ad, k, npub); + } +}; + +pub const XChacha20Poly1305 = struct { + pub const tag_length = 16; + pub const nonce_length = 24; + pub const key_length = 32; + + /// c: ciphertext: output buffer should be of size m.len + /// at: authentication tag: output MAC + /// m: message + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn encrypt(c: []u8, at: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { + assert(c.len == m.len); + return xchacha20poly1305SealDetached(c, at, m, ad, k, npub); + } + + /// m: message: output buffer should be of size c.len + /// c: ciphertext + /// at: authentication tag + /// ad: Associated Data + /// npub: public nonce + /// k: private key + /// NOTE: the check of the authentication tag is currently not done in constant time + pub fn decrypt(m: []u8, c: []const u8, at: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { + assert(c.len == m.len); + return try xchacha20poly1305OpenDetached(m, c, at[0..], ad, k, npub); + } +}; + +test "chacha20 AEAD API" { + const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 }; + const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + const data = "Additional data"; + + inline for (aeads) |aead| { + const key = [_]u8{69} ** aead.key_length; + const nonce = [_]u8{42} ** aead.nonce_length; + var ciphertext: [input.len]u8 = undefined; + var tag: [aead.tag_length]u8 = undefined; + var out: [input.len]u8 = undefined; + + aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key); + try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key); + testing.expectEqualSlices(u8, out[0..], input); + ciphertext[0] += 1; + testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key)); + } +} |
