| author | |
| committer | |
| log | 9135115573051eff58ffcf1ba0a3cce51ed0b413 |
| tree | 2fb1cc52c2e7c0fbda59bf4b7af80b5117df710a |
| parent | 8f3ccbbe367bea66d7f0f364a957870eb2cc95a0 |
| signature |
* Consistent decryption tail for all AEADs
* Remove outdated note
This was previously copied here from another function. There used
to be another comment on the tag verification linking to issue #1776,
but that one was not copied over. As it stands, this note seems fairly
misleading/irrelevant.
* Prettier docs
* Add note about plaintext contents to docs
* Capitalization
* Fixup missing XChaChaPoly docs6 files changed, 108 insertions(+), 77 deletions(-)
lib/std/crypto/aegis.zig+29-26| ... | ... | @@ -17,10 +17,11 @@ |
| 17 | 17 | //! https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ |
| 18 | 18 | |
| 19 | 19 | const std = @import("std"); |
| 20 | const crypto = std.crypto; | |
| 20 | 21 | const mem = std.mem; |
| 21 | 22 | const assert = std.debug.assert; |
| 22 | const AesBlock = std.crypto.core.aes.Block; | |
| 23 | const AuthenticationError = std.crypto.errors.AuthenticationError; | |
| 23 | const AesBlock = crypto.core.aes.Block; | |
| 24 | const AuthenticationError = crypto.errors.AuthenticationError; | |
| 24 | 25 | |
| 25 | 26 | /// AEGIS-128L with a 128-bit authentication tag. |
| 26 | 27 | pub const Aegis128L = Aegis128LGeneric(128); |
| ... | ... | @@ -169,12 +170,15 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type { |
| 169 | 170 | tag.* = state.mac(tag_bits, ad.len, m.len); |
| 170 | 171 | } |
| 171 | 172 | |
| 172 | /// m: message: output buffer should be of size c.len | |
| 173 | /// c: ciphertext | |
| 174 | /// tag: authentication tag | |
| 175 | /// ad: Associated Data | |
| 176 | /// npub: public nonce | |
| 177 | /// k: private key | |
| 173 | /// `m`: Message | |
| 174 | /// `c`: Ciphertext | |
| 175 | /// `tag`: Authentication tag | |
| 176 | /// `ad`: Associated data | |
| 177 | /// `npub`: Public nonce | |
| 178 | /// `k`: Private key | |
| 179 | /// Asserts `c.len == m.len`. | |
| 180 | /// | |
| 181 | /// Contents of `m` are undefined if an error is returned. | |
| 178 | 182 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void { |
| 179 | 183 | assert(c.len == m.len); |
| 180 | 184 | var state = State128L.init(key, npub); |
| ... | ... | @@ -203,12 +207,10 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type { |
| 203 | 207 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(dst[0..16])); |
| 204 | 208 | blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32])); |
| 205 | 209 | } |
| 206 | const computed_tag = state.mac(tag_bits, ad.len, m.len); | |
| 207 | var acc: u8 = 0; | |
| 208 | for (computed_tag, 0..) |_, j| { | |
| 209 | acc |= (computed_tag[j] ^ tag[j]); | |
| 210 | } | |
| 211 | if (acc != 0) { | |
| 210 | var computed_tag = state.mac(tag_bits, ad.len, m.len); | |
| 211 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 212 | if (!verify) { | |
| 213 | crypto.utils.secureZero(u8, &computed_tag); | |
| 212 | 214 | @memset(m, undefined); |
| 213 | 215 | return error.AuthenticationFailed; |
| 214 | 216 | } |
| ... | ... | @@ -351,12 +353,15 @@ fn Aegis256Generic(comptime tag_bits: u9) type { |
| 351 | 353 | tag.* = state.mac(tag_bits, ad.len, m.len); |
| 352 | 354 | } |
| 353 | 355 | |
| 354 | /// m: message: output buffer should be of size c.len | |
| 355 | /// c: ciphertext | |
| 356 | /// tag: authentication tag | |
| 357 | /// ad: Associated Data | |
| 358 | /// npub: public nonce | |
| 359 | /// k: private key | |
| 356 | /// `m`: Message | |
| 357 | /// `c`: Ciphertext | |
| 358 | /// `tag`: Authentication tag | |
| 359 | /// `ad`: Associated data | |
| 360 | /// `npub`: Public nonce | |
| 361 | /// `k`: Private key | |
| 362 | /// Asserts `c.len == m.len`. | |
| 363 | /// | |
| 364 | /// Contents of `m` are undefined if an error is returned. | |
| 360 | 365 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void { |
| 361 | 366 | assert(c.len == m.len); |
| 362 | 367 | var state = State256.init(key, npub); |
| ... | ... | @@ -384,12 +389,10 @@ fn Aegis256Generic(comptime tag_bits: u9) type { |
| 384 | 389 | const blocks = &state.blocks; |
| 385 | 390 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst)); |
| 386 | 391 | } |
| 387 | const computed_tag = state.mac(tag_bits, ad.len, m.len); | |
| 388 | var acc: u8 = 0; | |
| 389 | for (computed_tag, 0..) |_, j| { | |
| 390 | acc |= (computed_tag[j] ^ tag[j]); | |
| 391 | } | |
| 392 | if (acc != 0) { | |
| 392 | var computed_tag = state.mac(tag_bits, ad.len, m.len); | |
| 393 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 394 | if (!verify) { | |
| 395 | crypto.utils.secureZero(u8, &computed_tag); | |
| 393 | 396 | @memset(m, undefined); |
| 394 | 397 | return error.AuthenticationFailed; |
| 395 | 398 | } |
lib/std/crypto/aes_gcm.zig+12-5| ... | ... | @@ -55,6 +55,15 @@ fn AesGcm(comptime Aes: anytype) type { |
| 55 | 55 | } |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | /// `m`: Message | |
| 59 | /// `c`: Ciphertext | |
| 60 | /// `tag`: Authentication tag | |
| 61 | /// `ad`: Associated data | |
| 62 | /// `npub`: Public nonce | |
| 63 | /// `k`: Private key | |
| 64 | /// Asserts `c.len == m.len`. | |
| 65 | /// | |
| 66 | /// Contents of `m` are undefined if an error is returned. | |
| 58 | 67 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void { |
| 59 | 68 | assert(c.len == m.len); |
| 60 | 69 | |
| ... | ... | @@ -86,11 +95,9 @@ fn AesGcm(comptime Aes: anytype) type { |
| 86 | 95 | computed_tag[i] ^= x; |
| 87 | 96 | } |
| 88 | 97 | |
| 89 | var acc: u8 = 0; | |
| 90 | for (computed_tag, 0..) |_, p| { | |
| 91 | acc |= (computed_tag[p] ^ tag[p]); | |
| 92 | } | |
| 93 | if (acc != 0) { | |
| 98 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 99 | if (!verify) { | |
| 100 | crypto.utils.secureZero(u8, &computed_tag); | |
| 94 | 101 | @memset(m, undefined); |
| 95 | 102 | return error.AuthenticationFailed; |
| 96 | 103 | } |
lib/std/crypto/aes_ocb.zig+11-7| ... | ... | @@ -168,12 +168,15 @@ fn AesOcb(comptime Aes: anytype) type { |
| 168 | 168 | tag.* = xorBlocks(e, hash(aes_enc_ctx, &lx, ad)); |
| 169 | 169 | } |
| 170 | 170 | |
| 171 | /// m: message: output buffer should be of size c.len | |
| 172 | /// c: ciphertext | |
| 173 | /// tag: authentication tag | |
| 174 | /// ad: Associated Data | |
| 175 | /// npub: public nonce | |
| 176 | /// k: secret key | |
| 171 | /// `m`: Message | |
| 172 | /// `c`: Ciphertext | |
| 173 | /// `tag`: Authentication tag | |
| 174 | /// `ad`: Associated data | |
| 175 | /// `npub`: Public nonce | |
| 176 | /// `k`: Private key | |
| 177 | /// Asserts `c.len == m.len`. | |
| 178 | /// | |
| 179 | /// Contents of `m` are undefined if an error is returned. | |
| 177 | 180 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void { |
| 178 | 181 | assert(c.len == m.len); |
| 179 | 182 | |
| ... | ... | @@ -232,8 +235,9 @@ fn AesOcb(comptime Aes: anytype) type { |
| 232 | 235 | aes_enc_ctx.encrypt(&e, &e); |
| 233 | 236 | var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad)); |
| 234 | 237 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); |
| 235 | crypto.utils.secureZero(u8, &computed_tag); | |
| 236 | 238 | if (!verify) { |
| 239 | crypto.utils.secureZero(u8, &computed_tag); | |
| 240 | @memset(m, undefined); | |
| 237 | 241 | return error.AuthenticationFailed; |
| 238 | 242 | } |
| 239 | 243 | } |
lib/std/crypto/chacha20.zig+27-22| ... | ... | @@ -2,13 +2,14 @@ |
| 2 | 2 | |
| 3 | 3 | const std = @import("../std.zig"); |
| 4 | 4 | const builtin = @import("builtin"); |
| 5 | const crypto = std.crypto; | |
| 5 | 6 | const math = std.math; |
| 6 | 7 | const mem = std.mem; |
| 7 | 8 | const assert = std.debug.assert; |
| 8 | 9 | const testing = std.testing; |
| 9 | 10 | const maxInt = math.maxInt; |
| 10 | const Poly1305 = std.crypto.onetimeauth.Poly1305; | |
| 11 | const AuthenticationError = std.crypto.errors.AuthenticationError; | |
| 11 | const Poly1305 = crypto.onetimeauth.Poly1305; | |
| 12 | const AuthenticationError = crypto.errors.AuthenticationError; | |
| 12 | 13 | |
| 13 | 14 | /// IETF-variant of the ChaCha20 stream cipher, as designed for TLS. |
| 14 | 15 | pub const ChaCha20IETF = ChaChaIETF(20); |
| ... | ... | @@ -675,13 +676,15 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type { |
| 675 | 676 | mac.final(tag); |
| 676 | 677 | } |
| 677 | 678 | |
| 678 | /// m: message: output buffer should be of size c.len | |
| 679 | /// c: ciphertext | |
| 680 | /// tag: authentication tag | |
| 681 | /// ad: Associated Data | |
| 682 | /// npub: public nonce | |
| 683 | /// k: private key | |
| 684 | /// NOTE: the check of the authentication tag is currently not done in constant time | |
| 679 | /// `m`: Message | |
| 680 | /// `c`: Ciphertext | |
| 681 | /// `tag`: Authentication tag | |
| 682 | /// `ad`: Associated data | |
| 683 | /// `npub`: Public nonce | |
| 684 | /// `k`: Private key | |
| 685 | /// Asserts `c.len == m.len`. | |
| 686 | /// | |
| 687 | /// Contents of `m` are undefined if an error is returned. | |
| 685 | 688 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void { |
| 686 | 689 | assert(c.len == m.len); |
| 687 | 690 | |
| ... | ... | @@ -706,14 +709,13 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type { |
| 706 | 709 | mem.writeIntLittle(u64, lens[0..8], ad.len); |
| 707 | 710 | mem.writeIntLittle(u64, lens[8..16], c.len); |
| 708 | 711 | mac.update(lens[0..]); |
| 709 | var computedTag: [16]u8 = undefined; | |
| 710 | mac.final(computedTag[0..]); | |
| 712 | var computed_tag: [16]u8 = undefined; | |
| 713 | mac.final(computed_tag[0..]); | |
| 711 | 714 | |
| 712 | var acc: u8 = 0; | |
| 713 | for (computedTag, 0..) |_, i| { | |
| 714 | acc |= computedTag[i] ^ tag[i]; | |
| 715 | } | |
| 716 | if (acc != 0) { | |
| 715 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 716 | if (!verify) { | |
| 717 | crypto.utils.secureZero(u8, &computed_tag); | |
| 718 | @memset(m, undefined); | |
| 717 | 719 | return error.AuthenticationFailed; |
| 718 | 720 | } |
| 719 | 721 | ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub); |
| ... | ... | @@ -738,12 +740,15 @@ fn XChaChaPoly1305(comptime rounds_nb: usize) type { |
| 738 | 740 | return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key); |
| 739 | 741 | } |
| 740 | 742 | |
| 741 | /// m: message: output buffer should be of size c.len | |
| 742 | /// c: ciphertext | |
| 743 | /// tag: authentication tag | |
| 744 | /// ad: Associated Data | |
| 745 | /// npub: public nonce | |
| 746 | /// k: private key | |
| 743 | /// `m`: Message | |
| 744 | /// `c`: Ciphertext | |
| 745 | /// `tag`: Authentication tag | |
| 746 | /// `ad`: Associated data | |
| 747 | /// `npub`: Public nonce | |
| 748 | /// `k`: Private key | |
| 749 | /// Asserts `c.len == m.len`. | |
| 750 | /// | |
| 751 | /// Contents of `m` are undefined if an error is returned. | |
| 747 | 752 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void { |
| 748 | 753 | const extended = extend(k, npub, rounds_nb); |
| 749 | 754 | return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key); |
lib/std/crypto/isap.zig+13-3| ... | ... | @@ -147,11 +147,21 @@ pub const IsapA128A = struct { |
| 147 | 147 | tag.* = mac(c, ad, npub, key); |
| 148 | 148 | } |
| 149 | 149 | |
| 150 | /// `m`: Message | |
| 151 | /// `c`: Ciphertext | |
| 152 | /// `tag`: Authentication tag | |
| 153 | /// `ad`: Associated data | |
| 154 | /// `npub`: Public nonce | |
| 155 | /// `k`: Private key | |
| 156 | /// Asserts `c.len == m.len`. | |
| 157 | /// | |
| 158 | /// Contents of `m` are undefined if an error is returned. | |
| 150 | 159 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void { |
| 151 | 160 | var computed_tag = mac(c, ad, npub, key); |
| 152 | const res = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 153 | crypto.utils.secureZero(u8, &computed_tag); | |
| 154 | if (!res) { | |
| 161 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 162 | if (!verify) { | |
| 163 | crypto.utils.secureZero(u8, &computed_tag); | |
| 164 | @memset(m, undefined); | |
| 155 | 165 | return error.AuthenticationFailed; |
| 156 | 166 | } |
| 157 | 167 | xor(m, c, npub, key); |
lib/std/crypto/salsa20.zig+16-14| ... | ... | @@ -394,12 +394,15 @@ pub const XSalsa20Poly1305 = struct { |
| 394 | 394 | mac.final(tag); |
| 395 | 395 | } |
| 396 | 396 | |
| 397 | /// m: message: output buffer should be of size c.len | |
| 398 | /// c: ciphertext | |
| 399 | /// tag: authentication tag | |
| 400 | /// ad: Associated Data | |
| 401 | /// npub: public nonce | |
| 402 | /// k: private key | |
| 397 | /// `m`: Message | |
| 398 | /// `c`: Ciphertext | |
| 399 | /// `tag`: Authentication tag | |
| 400 | /// `ad`: Associated data | |
| 401 | /// `npub`: Public nonce | |
| 402 | /// `k`: Private key | |
| 403 | /// Asserts `c.len == m.len`. | |
| 404 | /// | |
| 405 | /// Contents of `m` are undefined if an error is returned. | |
| 403 | 406 | pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void { |
| 404 | 407 | debug.assert(c.len == m.len); |
| 405 | 408 | const extended = extend(rounds, k, npub); |
| ... | ... | @@ -410,14 +413,13 @@ pub const XSalsa20Poly1305 = struct { |
| 410 | 413 | var mac = Poly1305.init(block0[0..32]); |
| 411 | 414 | mac.update(ad); |
| 412 | 415 | mac.update(c); |
| 413 | var computedTag: [tag_length]u8 = undefined; | |
| 414 | mac.final(&computedTag); | |
| 415 | var acc: u8 = 0; | |
| 416 | for (computedTag, 0..) |_, i| { | |
| 417 | acc |= computedTag[i] ^ tag[i]; | |
| 418 | } | |
| 419 | if (acc != 0) { | |
| 420 | utils.secureZero(u8, &computedTag); | |
| 416 | var computed_tag: [tag_length]u8 = undefined; | |
| 417 | mac.final(&computed_tag); | |
| 418 | ||
| 419 | const verify = utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 420 | if (!verify) { | |
| 421 | utils.secureZero(u8, &computed_tag); | |
| 422 | @memset(m, undefined); | |
| 421 | 423 | return error.AuthenticationFailed; |
| 422 | 424 | } |
| 423 | 425 | @memcpy(m[0..mlen0], block0[32..][0..mlen0]); |