| ... | @@ -25,12 +25,24 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { | ... | @@ -25,12 +25,24 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { |
| 25 | }; | 25 | }; |
| 26 | } | 26 | } |
| 27 | | 27 | |
| 28 | // The chacha family of ciphers are based on the salsa family. | 28 | fn initContext(key: [8]u32, d: [4]u32) [16]u32 { |
| 29 | fn salsa20_wordtobyte(out: []u8, input: [16]u32) void { | 29 | var ctx: [16]u32 = undefined; |
| 30 | assert(out.len >= 64); | 30 | const c = "expand 32-byte k"; |
| | 31 | const constant_le = comptime [_]u32{ |
| | 32 | mem.readIntLittle(u32, c[0..4]), |
| | 33 | mem.readIntLittle(u32, c[4..8]), |
| | 34 | mem.readIntLittle(u32, c[8..12]), |
| | 35 | mem.readIntLittle(u32, c[12..16]), |
| | 36 | }; |
| | 37 | mem.copy(u32, ctx[0..], constant_le[0..4]); |
| | 38 | mem.copy(u32, ctx[4..12], key[0..8]); |
| | 39 | mem.copy(u32, ctx[12..16], d[0..4]); |
| 31 | | 40 | |
| 32 | var x: [16]u32 = undefined; | 41 | return ctx; |
| | 42 | } |
| 33 | | 43 | |
| | 44 | // The chacha family of ciphers are based on the salsa family. |
| | 45 | fn chacha20Core(x: []u32, input: [16]u32) void { |
| 34 | for (x) |_, i| | 46 | for (x) |_, i| |
| 35 | x[i] = input[i]; | 47 | x[i] = input[i]; |
| 36 | | 48 | |
| ... | @@ -59,33 +71,27 @@ fn salsa20_wordtobyte(out: []u8, input: [16]u32) void { | ... | @@ -59,33 +71,27 @@ fn salsa20_wordtobyte(out: []u8, input: [16]u32) void { |
| 59 | x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); | 71 | x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); |
| 60 | } | 72 | } |
| 61 | } | 73 | } |
| | 74 | } |
| 62 | | 75 | |
| | 76 | fn hashToBytes(out: []u8, x: [16]u32) void { |
| 63 | for (x) |_, i| { | 77 | for (x) |_, i| { |
| 64 | mem.writeIntLittle(u32, out[4 * i ..][0..4], x[i] +% input[i]); | 78 | mem.writeIntLittle(u32, out[4 * i ..][0..4], x[i]); |
| 65 | } | 79 | } |
| 66 | } | 80 | } |
| 67 | | 81 | |
| 68 | fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { | 82 | fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { |
| 69 | var ctx: [16]u32 = undefined; | 83 | var ctx = initContext(key, counter); |
| 70 | var remaining: usize = if (in.len > out.len) in.len else out.len; | 84 | var remaining: usize = if (in.len > out.len) in.len else out.len; |
| 71 | var cursor: usize = 0; | 85 | var cursor: usize = 0; |
| 72 | | 86 | |
| 73 | const c = "expand 32-byte k"; | | |
| 74 | const constant_le = [_]u32{ | | |
| 75 | mem.readIntLittle(u32, c[0..4]), | | |
| 76 | mem.readIntLittle(u32, c[4..8]), | | |
| 77 | mem.readIntLittle(u32, c[8..12]), | | |
| 78 | mem.readIntLittle(u32, c[12..16]), | | |
| 79 | }; | | |
| 80 | | | |
| 81 | mem.copy(u32, ctx[0..], constant_le[0..4]); | | |
| 82 | mem.copy(u32, ctx[4..12], key[0..8]); | | |
| 83 | mem.copy(u32, ctx[12..16], counter[0..4]); | | |
| 84 | | | |
| 85 | while (true) { | 87 | while (true) { |
| | 88 | var x: [16]u32 = undefined; |
| 86 | var buf: [64]u8 = undefined; | 89 | var buf: [64]u8 = undefined; |
| 87 | salsa20_wordtobyte(buf[0..], ctx); | 90 | chacha20Core(x[0..], ctx); |
| 88 | | 91 | for (x) |_, i| { |
| | 92 | x[i] +%= ctx[i]; |
| | 93 | } |
| | 94 | hashToBytes(buf[0..], x); |
| 89 | if (remaining < 64) { | 95 | if (remaining < 64) { |
| 90 | var i: usize = 0; | 96 | var i: usize = 0; |
| 91 | while (i < remaining) : (i += 1) | 97 | while (i < remaining) : (i += 1) |
| ... | @@ -104,6 +110,20 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo | ... | @@ -104,6 +110,20 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo |
| 104 | } | 110 | } |
| 105 | } | 111 | } |
| 106 | | 112 | |
| | 113 | fn keyToWords(key: [32]u8) [8]u32 { |
| | 114 | var k: [8]u32 = undefined; |
| | 115 | k[0] = mem.readIntLittle(u32, key[0..4]); |
| | 116 | k[1] = mem.readIntLittle(u32, key[4..8]); |
| | 117 | k[2] = mem.readIntLittle(u32, key[8..12]); |
| | 118 | k[3] = mem.readIntLittle(u32, key[12..16]); |
| | 119 | k[4] = mem.readIntLittle(u32, key[16..20]); |
| | 120 | k[5] = mem.readIntLittle(u32, key[20..24]); |
| | 121 | k[6] = mem.readIntLittle(u32, key[24..28]); |
| | 122 | k[7] = mem.readIntLittle(u32, key[28..32]); |
| | 123 | |
| | 124 | return k; |
| | 125 | } |
| | 126 | |
| 107 | /// ChaCha20 avoids the possibility of timing attacks, as there are no branches | 127 | /// ChaCha20 avoids the possibility of timing attacks, as there are no branches |
| 108 | /// on secret key data. | 128 | /// on secret key data. |
| 109 | /// | 129 | /// |
| ... | @@ -116,23 +136,12 @@ pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: | ... | @@ -116,23 +136,12 @@ pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: |
| 116 | assert(in.len >= out.len); | 136 | assert(in.len >= out.len); |
| 117 | assert((in.len >> 6) + counter <= maxInt(u32)); | 137 | assert((in.len >> 6) + counter <= maxInt(u32)); |
| 118 | | 138 | |
| 119 | var k: [8]u32 = undefined; | | |
| 120 | var c: [4]u32 = undefined; | 139 | var c: [4]u32 = undefined; |
| 121 | | | |
| 122 | k[0] = mem.readIntLittle(u32, key[0..4]); | | |
| 123 | k[1] = mem.readIntLittle(u32, key[4..8]); | | |
| 124 | k[2] = mem.readIntLittle(u32, key[8..12]); | | |
| 125 | k[3] = mem.readIntLittle(u32, key[12..16]); | | |
| 126 | k[4] = mem.readIntLittle(u32, key[16..20]); | | |
| 127 | k[5] = mem.readIntLittle(u32, key[20..24]); | | |
| 128 | k[6] = mem.readIntLittle(u32, key[24..28]); | | |
| 129 | k[7] = mem.readIntLittle(u32, key[28..32]); | | |
| 130 | | | |
| 131 | c[0] = counter; | 140 | c[0] = counter; |
| 132 | c[1] = mem.readIntLittle(u32, nonce[0..4]); | 141 | c[1] = mem.readIntLittle(u32, nonce[0..4]); |
| 133 | c[2] = mem.readIntLittle(u32, nonce[4..8]); | 142 | c[2] = mem.readIntLittle(u32, nonce[4..8]); |
| 134 | c[3] = mem.readIntLittle(u32, nonce[8..12]); | 143 | c[3] = mem.readIntLittle(u32, nonce[8..12]); |
| 135 | chaCha20_internal(out, in, k, c); | 144 | chaCha20_internal(out, in, keyToWords(key), c); |
| 136 | } | 145 | } |
| 137 | | 146 | |
| 138 | /// This is the original ChaCha20 before RFC 7539, which recommends using the | 147 | /// 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] | ... | @@ -143,18 +152,8 @@ pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32] |
| 143 | assert(counter +% (in.len >> 6) >= counter); | 152 | assert(counter +% (in.len >> 6) >= counter); |
| 144 | | 153 | |
| 145 | var cursor: usize = 0; | 154 | var cursor: usize = 0; |
| 146 | var k: [8]u32 = undefined; | 155 | const k = keyToWords(key); |
| 147 | var c: [4]u32 = undefined; | 156 | var c: [4]u32 = undefined; |
| 148 | | | |
| 149 | k[0] = mem.readIntLittle(u32, key[0..4]); | | |
| 150 | k[1] = mem.readIntLittle(u32, key[4..8]); | | |
| 151 | k[2] = mem.readIntLittle(u32, key[8..12]); | | |
| 152 | k[3] = mem.readIntLittle(u32, key[12..16]); | | |
| 153 | k[4] = mem.readIntLittle(u32, key[16..20]); | | |
| 154 | k[5] = mem.readIntLittle(u32, key[20..24]); | | |
| 155 | k[6] = mem.readIntLittle(u32, key[24..28]); | | |
| 156 | k[7] = mem.readIntLittle(u32, key[28..32]); | | |
| 157 | | | |
| 158 | c[0] = @truncate(u32, counter); | 157 | c[0] = @truncate(u32, counter); |
| 159 | c[1] = @truncate(u32, counter >> 32); | 158 | c[1] = @truncate(u32, counter >> 32); |
| 160 | c[2] = mem.readIntLittle(u32, nonce[0..4]); | 159 | c[2] = mem.readIntLittle(u32, nonce[0..4]); |
| ... | @@ -437,15 +436,15 @@ test "crypto.chacha20 test vector 5" { | ... | @@ -437,15 +436,15 @@ test "crypto.chacha20 test vector 5" { |
| 437 | | 436 | |
| 438 | pub const chacha20poly1305_tag_size = 16; | 437 | pub const chacha20poly1305_tag_size = 16; |
| 439 | | 438 | |
| 440 | pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { | 439 | pub fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { |
| 441 | assert(dst.len >= plaintext.len + chacha20poly1305_tag_size); | 440 | assert(ciphertext.len >= plaintext.len); |
| 442 | | 441 | |
| 443 | // derive poly1305 key | 442 | // derive poly1305 key |
| 444 | var polyKey = [_]u8{0} ** 32; | 443 | var polyKey = [_]u8{0} ** 32; |
| 445 | chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce); | 444 | chaCha20IETF(polyKey[0..], polyKey[0..], 0, key, nonce); |
| 446 | | 445 | |
| 447 | // encrypt plaintext | 446 | // encrypt plaintext |
| 448 | chaCha20IETF(dst[0..plaintext.len], plaintext, 1, key, nonce); | 447 | chaCha20IETF(ciphertext[0..plaintext.len], plaintext, 1, key, nonce); |
| 449 | | 448 | |
| 450 | // construct mac | 449 | // construct mac |
| 451 | var mac = Poly1305.init(polyKey[0..]); | 450 | var mac = Poly1305.init(polyKey[0..]); |
| ... | @@ -455,7 +454,7 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, | ... | @@ -455,7 +454,7 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, |
| 455 | const padding = 16 - (data.len % 16); | 454 | const padding = 16 - (data.len % 16); |
| 456 | mac.update(zeros[0..padding]); | 455 | mac.update(zeros[0..padding]); |
| 457 | } | 456 | } |
| 458 | mac.update(dst[0..plaintext.len]); | 457 | mac.update(ciphertext[0..plaintext.len]); |
| 459 | if (plaintext.len % 16 != 0) { | 458 | if (plaintext.len % 16 != 0) { |
| 460 | const zeros = [_]u8{0} ** 16; | 459 | const zeros = [_]u8{0} ** 16; |
| 461 | const padding = 16 - (plaintext.len % 16); | 460 | const padding = 16 - (plaintext.len % 16); |
| ... | @@ -465,19 +464,17 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, | ... | @@ -465,19 +464,17 @@ pub fn chacha20poly1305Seal(dst: []u8, plaintext: []const u8, data: []const u8, |
| 465 | mem.writeIntLittle(u64, lens[0..8], data.len); | 464 | mem.writeIntLittle(u64, lens[0..8], data.len); |
| 466 | mem.writeIntLittle(u64, lens[8..16], plaintext.len); | 465 | mem.writeIntLittle(u64, lens[8..16], plaintext.len); |
| 467 | mac.update(lens[0..]); | 466 | mac.update(lens[0..]); |
| 468 | mac.final(dst[plaintext.len..]); | 467 | mac.final(tag); |
| 469 | } | 468 | } |
| 470 | | 469 | |
| 471 | /// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal. | 470 | pub fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { |
| 472 | pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { | 471 | return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_size], plaintext, data, key, nonce); |
| 473 | if (msgAndTag.len < chacha20poly1305_tag_size) { | 472 | } |
| 474 | return error.InvalidMessage; | | |
| 475 | } | | |
| 476 | | 473 | |
| | 474 | /// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached. |
| | 475 | pub fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_size]u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { |
| 477 | // split ciphertext and tag | 476 | // split ciphertext and tag |
| 478 | assert(dst.len >= msgAndTag.len - chacha20poly1305_tag_size); | 477 | assert(dst.len >= ciphertext.len); |
| 479 | var ciphertext = msgAndTag[0 .. msgAndTag.len - chacha20poly1305_tag_size]; | | |
| 480 | var polyTag = msgAndTag[ciphertext.len..]; | | |
| 481 | | 478 | |
| 482 | // derive poly1305 key | 479 | // derive poly1305 key |
| 483 | var polyKey = [_]u8{0} ** 32; | 480 | var polyKey = [_]u8{0} ** 32; |
| ... | @@ -510,7 +507,7 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, | ... | @@ -510,7 +507,7 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, |
| 510 | // See https://github.com/ziglang/zig/issues/1776 | 507 | // See https://github.com/ziglang/zig/issues/1776 |
| 511 | var acc: u8 = 0; | 508 | var acc: u8 = 0; |
| 512 | for (computedTag) |_, i| { | 509 | for (computedTag) |_, i| { |
| 513 | acc |= (computedTag[i] ^ polyTag[i]); | 510 | acc |= (computedTag[i] ^ tag.*[i]); |
| 514 | } | 511 | } |
| 515 | if (acc != 0) { | 512 | if (acc != 0) { |
| 516 | return error.AuthenticationFailed; | 513 | return error.AuthenticationFailed; |
| ... | @@ -520,6 +517,75 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, | ... | @@ -520,6 +517,75 @@ pub fn chacha20poly1305Open(dst: []u8, msgAndTag: []const u8, data: []const u8, |
| 520 | chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce); | 517 | chaCha20IETF(dst[0..ciphertext.len], ciphertext, 1, key, nonce); |
| 521 | } | 518 | } |
| 522 | | 519 | |
| | 520 | /// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal. |
| | 521 | pub fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) !void { |
| | 522 | if (ciphertextAndTag.len < chacha20poly1305_tag_size) { |
| | 523 | return error.InvalidMessage; |
| | 524 | } |
| | 525 | const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_size; |
| | 526 | return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_size], data, key, nonce); |
| | 527 | } |
| | 528 | |
| | 529 | fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { |
| | 530 | var c: [4]u32 = undefined; |
| | 531 | for (c) |_, i| { |
| | 532 | c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); |
| | 533 | } |
| | 534 | const ctx = initContext(keyToWords(key), c); |
| | 535 | var x: [16]u32 = undefined; |
| | 536 | chacha20Core(x[0..], ctx); |
| | 537 | var out: [32]u8 = undefined; |
| | 538 | mem.writeIntLittle(u32, out[0..4], x[0]); |
| | 539 | mem.writeIntLittle(u32, out[4..8], x[1]); |
| | 540 | mem.writeIntLittle(u32, out[8..12], x[2]); |
| | 541 | mem.writeIntLittle(u32, out[12..16], x[3]); |
| | 542 | mem.writeIntLittle(u32, out[16..20], x[12]); |
| | 543 | mem.writeIntLittle(u32, out[20..24], x[13]); |
| | 544 | mem.writeIntLittle(u32, out[24..28], x[14]); |
| | 545 | mem.writeIntLittle(u32, out[28..32], x[15]); |
| | 546 | |
| | 547 | return out; |
| | 548 | } |
| | 549 | |
| | 550 | fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } { |
| | 551 | var subnonce: [12]u8 = undefined; |
| | 552 | mem.set(u8, subnonce[0..4], 0); |
| | 553 | mem.copy(u8, subnonce[4..], nonce[16..24]); |
| | 554 | return .{ |
| | 555 | .key = hchacha20(nonce[0..16].*, key), |
| | 556 | .nonce = subnonce, |
| | 557 | }; |
| | 558 | } |
| | 559 | |
| | 560 | pub fn xChaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void { |
| | 561 | const extended = extend(key, nonce); |
| | 562 | chaCha20IETF(out, in, counter, extended.key, extended.nonce); |
| | 563 | } |
| | 564 | |
| | 565 | pub const xchacha20poly1305_tag_size = 16; |
| | 566 | |
| | 567 | pub fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_size]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { |
| | 568 | const extended = extend(key, nonce); |
| | 569 | return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce); |
| | 570 | } |
| | 571 | |
| | 572 | pub fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { |
| | 573 | const extended = extend(key, nonce); |
| | 574 | return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce); |
| | 575 | } |
| | 576 | |
| | 577 | /// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached. |
| | 578 | pub fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_size]u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void { |
| | 579 | const extended = extend(key, nonce); |
| | 580 | return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce); |
| | 581 | } |
| | 582 | |
| | 583 | /// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal. |
| | 584 | pub fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) !void { |
| | 585 | const extended = extend(key, nonce); |
| | 586 | return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce); |
| | 587 | } |
| | 588 | |
| 523 | test "seal" { | 589 | test "seal" { |
| 524 | { | 590 | { |
| 525 | const plaintext = ""; | 591 | const plaintext = ""; |
| ... | @@ -636,3 +702,105 @@ test "open" { | ... | @@ -636,3 +702,105 @@ test "open" { |
| 636 | testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce)); | 702 | testing.expectError(error.InvalidMessage, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce)); |
| 637 | } | 703 | } |
| 638 | } | 704 | } |
| | 705 | |
| | 706 | test "crypto.xchacha20" { |
| | 707 | const key = [_]u8{69} ** 32; |
| | 708 | const nonce = [_]u8{42} ** 24; |
| | 709 | 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."; |
| | 710 | { |
| | 711 | var ciphertext: [input.len]u8 = undefined; |
| | 712 | xChaCha20IETF(ciphertext[0..], input[0..], 0, key, nonce); |
| | 713 | var buf: [2 * ciphertext.len]u8 = undefined; |
| | 714 | testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{ciphertext}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); |
| | 715 | } |
| | 716 | { |
| | 717 | const data = "Additional data"; |
| | 718 | var ciphertext: [input.len + xchacha20poly1305_tag_size]u8 = undefined; |
| | 719 | xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce); |
| | 720 | var out: [input.len]u8 = undefined; |
| | 721 | try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce); |
| | 722 | var buf: [2 * ciphertext.len]u8 = undefined; |
| | 723 | testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{ciphertext}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); |
| | 724 | testing.expectEqualSlices(u8, out[0..], input); |
| | 725 | ciphertext[0] += 1; |
| | 726 | testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce)); |
| | 727 | } |
| | 728 | } |
| | 729 | |
| | 730 | pub const Chacha20Poly1305 = struct { |
| | 731 | pub const tag_length = 16; |
| | 732 | pub const nonce_length = 12; |
| | 733 | pub const key_length = 32; |
| | 734 | |
| | 735 | /// c: ciphertext: output buffer should be of size m.len |
| | 736 | /// at: authentication tag: output MAC |
| | 737 | /// m: message |
| | 738 | /// ad: Associated Data |
| | 739 | /// npub: public nonce |
| | 740 | /// k: private key |
| | 741 | pub fn encrypt(c: []u8, at: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { |
| | 742 | assert(c.len == m.len); |
| | 743 | return chacha20poly1305SealDetached(c, at, m, ad, k, npub); |
| | 744 | } |
| | 745 | |
| | 746 | /// m: message: output buffer should be of size c.len |
| | 747 | /// c: ciphertext |
| | 748 | /// at: authentication tag |
| | 749 | /// ad: Associated Data |
| | 750 | /// npub: public nonce |
| | 751 | /// k: private key |
| | 752 | /// NOTE: the check of the authentication tag is currently not done in constant time |
| | 753 | pub fn decrypt(m: []u8, c: []const u8, at: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { |
| | 754 | assert(c.len == m.len); |
| | 755 | return try chacha20poly1305OpenDetached(m, c, at[0..], ad, k, npub); |
| | 756 | } |
| | 757 | }; |
| | 758 | |
| | 759 | pub const XChacha20Poly1305 = struct { |
| | 760 | pub const tag_length = 16; |
| | 761 | pub const nonce_length = 24; |
| | 762 | pub const key_length = 32; |
| | 763 | |
| | 764 | /// c: ciphertext: output buffer should be of size m.len |
| | 765 | /// at: authentication tag: output MAC |
| | 766 | /// m: message |
| | 767 | /// ad: Associated Data |
| | 768 | /// npub: public nonce |
| | 769 | /// k: private key |
| | 770 | pub fn encrypt(c: []u8, at: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { |
| | 771 | assert(c.len == m.len); |
| | 772 | return xchacha20poly1305SealDetached(c, at, m, ad, k, npub); |
| | 773 | } |
| | 774 | |
| | 775 | /// m: message: output buffer should be of size c.len |
| | 776 | /// c: ciphertext |
| | 777 | /// at: authentication tag |
| | 778 | /// ad: Associated Data |
| | 779 | /// npub: public nonce |
| | 780 | /// k: private key |
| | 781 | /// NOTE: the check of the authentication tag is currently not done in constant time |
| | 782 | pub fn decrypt(m: []u8, c: []const u8, at: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { |
| | 783 | assert(c.len == m.len); |
| | 784 | return try xchacha20poly1305OpenDetached(m, c, at[0..], ad, k, npub); |
| | 785 | } |
| | 786 | }; |
| | 787 | |
| | 788 | test "chacha20 AEAD API" { |
| | 789 | const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 }; |
| | 790 | 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."; |
| | 791 | const data = "Additional data"; |
| | 792 | |
| | 793 | inline for (aeads) |aead| { |
| | 794 | const key = [_]u8{69} ** aead.key_length; |
| | 795 | const nonce = [_]u8{42} ** aead.nonce_length; |
| | 796 | var ciphertext: [input.len]u8 = undefined; |
| | 797 | var tag: [aead.tag_length]u8 = undefined; |
| | 798 | var out: [input.len]u8 = undefined; |
| | 799 | |
| | 800 | aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key); |
| | 801 | try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key); |
| | 802 | testing.expectEqualSlices(u8, out[0..], input); |
| | 803 | ciphertext[0] += 1; |
| | 804 | testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key)); |
| | 805 | } |
| | 806 | } |