Commit 795033c35f
Changed files (1)
lib
std
crypto
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));
+ }
+}