| ... | @@ -1,9 +1,39 @@ | ... | @@ -1,9 +1,39 @@ |
| | 1 | //! AEGIS is a very fast authenticated encryption system built on top of the core AES function. |
| | 2 | //! |
| | 3 | //! The AEGIS-128L variant has a 128 bit key, a 128 bit nonce, and processes 256 bit message blocks. |
| | 4 | //! The AEGIS-256 variant has a 256 bit key, a 256 bit nonce, and processes 128 bit message blocks. |
| | 5 | //! |
| | 6 | //! The AEGIS cipher family offers performance that significantly exceeds that of AES-GCM with |
| | 7 | //! hardware support for parallelizable AES block encryption. |
| | 8 | //! |
| | 9 | //! Unlike with AES-GCM, nonces can be safely chosen at random with no practical limit when using AEGIS-256. |
| | 10 | //! AEGIS-128L also allows for more messages to be safely encrypted when using random nonces. |
| | 11 | //! |
| | 12 | //! AEGIS is believed to be key-committing, making it a safer choice than most other AEADs |
| | 13 | //! when the key has low entropy, or can be controlled by an attacker. |
| | 14 | //! |
| | 15 | //! Finally, leaking the state does not leak the key. |
| | 16 | //! |
| | 17 | //! https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ |
| | 18 | |
| 1 | const std = @import("std"); | 19 | const std = @import("std"); |
| 2 | const mem = std.mem; | 20 | const mem = std.mem; |
| 3 | const assert = std.debug.assert; | 21 | const assert = std.debug.assert; |
| 4 | const AesBlock = std.crypto.core.aes.Block; | 22 | const AesBlock = std.crypto.core.aes.Block; |
| 5 | const AuthenticationError = std.crypto.errors.AuthenticationError; | 23 | const AuthenticationError = std.crypto.errors.AuthenticationError; |
| 6 | | 24 | |
| | 25 | /// AEGIS-128L with a 128-bit authentication tag. |
| | 26 | pub const Aegis128L = Aegis128LGeneric(128); |
| | 27 | |
| | 28 | /// AEGIS-128L with a 256-bit authentication tag. |
| | 29 | pub const Aegis128L_256 = Aegis128LGeneric(256); |
| | 30 | |
| | 31 | /// AEGIS-256 with a 128-bit authentication tag. |
| | 32 | pub const Aegis256 = Aegis256Generic(128); |
| | 33 | |
| | 34 | /// AEGIS-256 with a 256-bit authentication tag. |
| | 35 | pub const Aegis256_256 = Aegis256Generic(256); |
| | 36 | |
| 7 | const State128L = struct { | 37 | const State128L = struct { |
| 8 | blocks: [8]AesBlock, | 38 | blocks: [8]AesBlock, |
| 9 | | 39 | |
| ... | @@ -72,7 +102,7 @@ const State128L = struct { | ... | @@ -72,7 +102,7 @@ const State128L = struct { |
| 72 | state.update(msg0, msg1); | 102 | state.update(msg0, msg1); |
| 73 | } | 103 | } |
| 74 | | 104 | |
| 75 | fn mac(state: *State128L, adlen: usize, mlen: usize) [16]u8 { | 105 | fn mac(state: *State128L, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 { |
| 76 | const blocks = &state.blocks; | 106 | const blocks = &state.blocks; |
| 77 | var sizes: [16]u8 = undefined; | 107 | var sizes: [16]u8 = undefined; |
| 78 | mem.writeIntLittle(u64, sizes[0..8], adlen * 8); | 108 | mem.writeIntLittle(u64, sizes[0..8], adlen * 8); |
| ... | @@ -82,103 +112,109 @@ const State128L = struct { | ... | @@ -82,103 +112,109 @@ const State128L = struct { |
| 82 | while (i < 7) : (i += 1) { | 112 | while (i < 7) : (i += 1) { |
| 83 | state.update(tmp, tmp); | 113 | state.update(tmp, tmp); |
| 84 | } | 114 | } |
| 85 | return blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]) | 115 | return switch (tag_bits) { |
| 86 | .xorBlocks(blocks[5]).xorBlocks(blocks[6]).toBytes(); | 116 | 128 => blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]) |
| | 117 | .xorBlocks(blocks[4]).xorBlocks(blocks[5]).xorBlocks(blocks[6]).toBytes(), |
| | 118 | 256 => tag: { |
| | 119 | const t1 = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]); |
| | 120 | const t2 = blocks[4].xorBlocks(blocks[5]).xorBlocks(blocks[6]).xorBlocks(blocks[7]); |
| | 121 | break :tag t1.toBytes() ++ t2.toBytes(); |
| | 122 | }, |
| | 123 | else => unreachable, |
| | 124 | }; |
| 87 | } | 125 | } |
| 88 | }; | 126 | }; |
| 89 | | 127 | |
| 90 | /// AEGIS is a very fast authenticated encryption system built on top of the core AES function. | 128 | fn Aegis128LGeneric(comptime tag_bits: u9) type { |
| 91 | /// | 129 | comptime assert(tag_bits == 128 or tag_bits == 256); // tag must be 128 or 256 bits |
| 92 | /// The 128L variant of AEGIS has a 128 bit key, a 128 bit nonce, and processes 256 bit message blocks. | | |
| 93 | /// It was designed to fully exploit the parallelism and built-in AES support of recent Intel and ARM CPUs. | | |
| 94 | /// | | |
| 95 | /// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ | | |
| 96 | pub const Aegis128L = struct { | | |
| 97 | pub const tag_length = 16; | | |
| 98 | pub const nonce_length = 16; | | |
| 99 | pub const key_length = 16; | | |
| 100 | pub const block_length = 32; | | |
| 101 | | | |
| 102 | const State = State128L; | | |
| 103 | | | |
| 104 | /// c: ciphertext: output buffer should be of size m.len | | |
| 105 | /// tag: authentication tag: output MAC | | |
| 106 | /// m: message | | |
| 107 | /// ad: Associated Data | | |
| 108 | /// npub: public nonce | | |
| 109 | /// k: private key | | |
| 110 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { | | |
| 111 | assert(c.len == m.len); | | |
| 112 | var state = State128L.init(key, npub); | | |
| 113 | var src: [32]u8 align(16) = undefined; | | |
| 114 | var dst: [32]u8 align(16) = undefined; | | |
| 115 | var i: usize = 0; | | |
| 116 | while (i + 32 <= ad.len) : (i += 32) { | | |
| 117 | state.absorb(ad[i..][0..32]); | | |
| 118 | } | | |
| 119 | if (ad.len % 32 != 0) { | | |
| 120 | mem.set(u8, src[0..], 0); | | |
| 121 | mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]); | | |
| 122 | state.absorb(&src); | | |
| 123 | } | | |
| 124 | i = 0; | | |
| 125 | while (i + 32 <= m.len) : (i += 32) { | | |
| 126 | state.enc(c[i..][0..32], m[i..][0..32]); | | |
| 127 | } | | |
| 128 | if (m.len % 32 != 0) { | | |
| 129 | mem.set(u8, src[0..], 0); | | |
| 130 | mem.copy(u8, src[0 .. m.len % 32], m[i .. i + m.len % 32]); | | |
| 131 | state.enc(&dst, &src); | | |
| 132 | mem.copy(u8, c[i .. i + m.len % 32], dst[0 .. m.len % 32]); | | |
| 133 | } | | |
| 134 | tag.* = state.mac(ad.len, m.len); | | |
| 135 | } | | |
| 136 | | 130 | |
| 137 | /// m: message: output buffer should be of size c.len | 131 | return struct { |
| 138 | /// c: ciphertext | 132 | pub const tag_length = tag_bits / 8; |
| 139 | /// tag: authentication tag | 133 | pub const nonce_length = 16; |
| 140 | /// ad: Associated Data | 134 | pub const key_length = 16; |
| 141 | /// npub: public nonce | 135 | pub const block_length = 32; |
| 142 | /// k: private key | 136 | |
| 143 | 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 { | 137 | const State = State128L; |
| 144 | assert(c.len == m.len); | 138 | |
| 145 | var state = State128L.init(key, npub); | 139 | /// c: ciphertext: output buffer should be of size m.len |
| 146 | var src: [32]u8 align(16) = undefined; | 140 | /// tag: authentication tag: output MAC |
| 147 | var dst: [32]u8 align(16) = undefined; | 141 | /// m: message |
| 148 | var i: usize = 0; | 142 | /// ad: Associated Data |
| 149 | while (i + 32 <= ad.len) : (i += 32) { | 143 | /// npub: public nonce |
| 150 | state.absorb(ad[i..][0..32]); | 144 | /// k: private key |
| 151 | } | 145 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { |
| 152 | if (ad.len % 32 != 0) { | 146 | assert(c.len == m.len); |
| 153 | mem.set(u8, src[0..], 0); | 147 | var state = State128L.init(key, npub); |
| 154 | mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]); | 148 | var src: [32]u8 align(16) = undefined; |
| 155 | state.absorb(&src); | 149 | var dst: [32]u8 align(16) = undefined; |
| 156 | } | 150 | var i: usize = 0; |
| 157 | i = 0; | 151 | while (i + 32 <= ad.len) : (i += 32) { |
| 158 | while (i + 32 <= m.len) : (i += 32) { | 152 | state.absorb(ad[i..][0..32]); |
| 159 | state.dec(m[i..][0..32], c[i..][0..32]); | 153 | } |
| 160 | } | 154 | if (ad.len % 32 != 0) { |
| 161 | if (m.len % 32 != 0) { | 155 | mem.set(u8, src[0..], 0); |
| 162 | mem.set(u8, src[0..], 0); | 156 | mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]); |
| 163 | mem.copy(u8, src[0 .. m.len % 32], c[i .. i + m.len % 32]); | 157 | state.absorb(&src); |
| 164 | state.dec(&dst, &src); | 158 | } |
| 165 | mem.copy(u8, m[i .. i + m.len % 32], dst[0 .. m.len % 32]); | 159 | i = 0; |
| 166 | mem.set(u8, dst[0 .. m.len % 32], 0); | 160 | while (i + 32 <= m.len) : (i += 32) { |
| 167 | const blocks = &state.blocks; | 161 | state.enc(c[i..][0..32], m[i..][0..32]); |
| 168 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(dst[0..16])); | 162 | } |
| 169 | blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32])); | 163 | if (m.len % 32 != 0) { |
| 170 | } | 164 | mem.set(u8, src[0..], 0); |
| 171 | const computed_tag = state.mac(ad.len, m.len); | 165 | mem.copy(u8, src[0 .. m.len % 32], m[i .. i + m.len % 32]); |
| 172 | var acc: u8 = 0; | 166 | state.enc(&dst, &src); |
| 173 | for (computed_tag, 0..) |_, j| { | 167 | mem.copy(u8, c[i .. i + m.len % 32], dst[0 .. m.len % 32]); |
| 174 | acc |= (computed_tag[j] ^ tag[j]); | 168 | } |
| 175 | } | 169 | tag.* = state.mac(tag_bits, ad.len, m.len); |
| 176 | if (acc != 0) { | 170 | } |
| 177 | @memset(m.ptr, undefined, m.len); | 171 | |
| 178 | return error.AuthenticationFailed; | 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 |
| | 178 | 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 | assert(c.len == m.len); |
| | 180 | var state = State128L.init(key, npub); |
| | 181 | var src: [32]u8 align(16) = undefined; |
| | 182 | var dst: [32]u8 align(16) = undefined; |
| | 183 | var i: usize = 0; |
| | 184 | while (i + 32 <= ad.len) : (i += 32) { |
| | 185 | state.absorb(ad[i..][0..32]); |
| | 186 | } |
| | 187 | if (ad.len % 32 != 0) { |
| | 188 | mem.set(u8, src[0..], 0); |
| | 189 | mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]); |
| | 190 | state.absorb(&src); |
| | 191 | } |
| | 192 | i = 0; |
| | 193 | while (i + 32 <= m.len) : (i += 32) { |
| | 194 | state.dec(m[i..][0..32], c[i..][0..32]); |
| | 195 | } |
| | 196 | if (m.len % 32 != 0) { |
| | 197 | mem.set(u8, src[0..], 0); |
| | 198 | mem.copy(u8, src[0 .. m.len % 32], c[i .. i + m.len % 32]); |
| | 199 | state.dec(&dst, &src); |
| | 200 | mem.copy(u8, m[i .. i + m.len % 32], dst[0 .. m.len % 32]); |
| | 201 | mem.set(u8, dst[0 .. m.len % 32], 0); |
| | 202 | const blocks = &state.blocks; |
| | 203 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(dst[0..16])); |
| | 204 | blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32])); |
| | 205 | } |
| | 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) { |
| | 212 | @memset(m.ptr, undefined, m.len); |
| | 213 | return error.AuthenticationFailed; |
| | 214 | } |
| 179 | } | 215 | } |
| 180 | } | 216 | }; |
| 181 | }; | 217 | } |
| 182 | | 218 | |
| 183 | const State256 = struct { | 219 | const State256 = struct { |
| 184 | blocks: [6]AesBlock, | 220 | blocks: [6]AesBlock, |
| ... | @@ -243,7 +279,7 @@ const State256 = struct { | ... | @@ -243,7 +279,7 @@ const State256 = struct { |
| 243 | state.update(msg); | 279 | state.update(msg); |
| 244 | } | 280 | } |
| 245 | | 281 | |
| 246 | fn mac(state: *State256, adlen: usize, mlen: usize) [16]u8 { | 282 | fn mac(state: *State256, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 { |
| 247 | const blocks = &state.blocks; | 283 | const blocks = &state.blocks; |
| 248 | var sizes: [16]u8 = undefined; | 284 | var sizes: [16]u8 = undefined; |
| 249 | mem.writeIntLittle(u64, sizes[0..8], adlen * 8); | 285 | mem.writeIntLittle(u64, sizes[0..8], adlen * 8); |
| ... | @@ -253,8 +289,16 @@ const State256 = struct { | ... | @@ -253,8 +289,16 @@ const State256 = struct { |
| 253 | while (i < 7) : (i += 1) { | 289 | while (i < 7) : (i += 1) { |
| 254 | state.update(tmp); | 290 | state.update(tmp); |
| 255 | } | 291 | } |
| 256 | return blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]) | 292 | return switch (tag_bits) { |
| 257 | .xorBlocks(blocks[5]).toBytes(); | 293 | 128 => blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]) |
| | 294 | .xorBlocks(blocks[4]).xorBlocks(blocks[5]).toBytes(), |
| | 295 | 256 => tag: { |
| | 296 | const t1 = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]); |
| | 297 | const t2 = blocks[3].xorBlocks(blocks[4]).xorBlocks(blocks[5]); |
| | 298 | break :tag t1.toBytes() ++ t2.toBytes(); |
| | 299 | }, |
| | 300 | else => unreachable, |
| | 301 | }; |
| 258 | } | 302 | } |
| 259 | }; | 303 | }; |
| 260 | | 304 | |
| ... | @@ -263,111 +307,115 @@ const State256 = struct { | ... | @@ -263,111 +307,115 @@ const State256 = struct { |
| 263 | /// The 256 bit variant of AEGIS has a 256 bit key, a 256 bit nonce, and processes 128 bit message blocks. | 307 | /// The 256 bit variant of AEGIS has a 256 bit key, a 256 bit nonce, and processes 128 bit message blocks. |
| 264 | /// | 308 | /// |
| 265 | /// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ | 309 | /// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ |
| 266 | pub const Aegis256 = struct { | 310 | fn Aegis256Generic(comptime tag_bits: u9) type { |
| 267 | pub const tag_length = 16; | 311 | comptime assert(tag_bits == 128 or tag_bits == 256); // tag must be 128 or 256 bits |
| 268 | pub const nonce_length = 32; | | |
| 269 | pub const key_length = 32; | | |
| 270 | pub const block_length = 16; | | |
| 271 | | | |
| 272 | const State = State256; | | |
| 273 | | | |
| 274 | /// c: ciphertext: output buffer should be of size m.len | | |
| 275 | /// tag: authentication tag: output MAC | | |
| 276 | /// m: message | | |
| 277 | /// ad: Associated Data | | |
| 278 | /// npub: public nonce | | |
| 279 | /// k: private key | | |
| 280 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { | | |
| 281 | assert(c.len == m.len); | | |
| 282 | var state = State256.init(key, npub); | | |
| 283 | var src: [16]u8 align(16) = undefined; | | |
| 284 | var dst: [16]u8 align(16) = undefined; | | |
| 285 | var i: usize = 0; | | |
| 286 | while (i + 16 <= ad.len) : (i += 16) { | | |
| 287 | state.enc(&dst, ad[i..][0..16]); | | |
| 288 | } | | |
| 289 | if (ad.len % 16 != 0) { | | |
| 290 | mem.set(u8, src[0..], 0); | | |
| 291 | mem.copy(u8, src[0 .. ad.len % 16], ad[i .. i + ad.len % 16]); | | |
| 292 | state.enc(&dst, &src); | | |
| 293 | } | | |
| 294 | i = 0; | | |
| 295 | while (i + 16 <= m.len) : (i += 16) { | | |
| 296 | state.enc(c[i..][0..16], m[i..][0..16]); | | |
| 297 | } | | |
| 298 | if (m.len % 16 != 0) { | | |
| 299 | mem.set(u8, src[0..], 0); | | |
| 300 | mem.copy(u8, src[0 .. m.len % 16], m[i .. i + m.len % 16]); | | |
| 301 | state.enc(&dst, &src); | | |
| 302 | mem.copy(u8, c[i .. i + m.len % 16], dst[0 .. m.len % 16]); | | |
| 303 | } | | |
| 304 | tag.* = state.mac(ad.len, m.len); | | |
| 305 | } | | |
| 306 | | 312 | |
| 307 | /// m: message: output buffer should be of size c.len | 313 | return struct { |
| 308 | /// c: ciphertext | 314 | pub const tag_length = tag_bits / 8; |
| 309 | /// tag: authentication tag | 315 | pub const nonce_length = 32; |
| 310 | /// ad: Associated Data | 316 | pub const key_length = 32; |
| 311 | /// npub: public nonce | 317 | pub const block_length = 16; |
| 312 | /// k: private key | 318 | |
| 313 | 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 { | 319 | const State = State256; |
| 314 | assert(c.len == m.len); | 320 | |
| 315 | var state = State256.init(key, npub); | 321 | /// c: ciphertext: output buffer should be of size m.len |
| 316 | var src: [16]u8 align(16) = undefined; | 322 | /// tag: authentication tag: output MAC |
| 317 | var dst: [16]u8 align(16) = undefined; | 323 | /// m: message |
| 318 | var i: usize = 0; | 324 | /// ad: Associated Data |
| 319 | while (i + 16 <= ad.len) : (i += 16) { | 325 | /// npub: public nonce |
| 320 | state.enc(&dst, ad[i..][0..16]); | 326 | /// k: private key |
| 321 | } | 327 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { |
| 322 | if (ad.len % 16 != 0) { | 328 | assert(c.len == m.len); |
| 323 | mem.set(u8, src[0..], 0); | 329 | var state = State256.init(key, npub); |
| 324 | mem.copy(u8, src[0 .. ad.len % 16], ad[i .. i + ad.len % 16]); | 330 | var src: [16]u8 align(16) = undefined; |
| 325 | state.enc(&dst, &src); | 331 | var dst: [16]u8 align(16) = undefined; |
| 326 | } | 332 | var i: usize = 0; |
| 327 | i = 0; | 333 | while (i + 16 <= ad.len) : (i += 16) { |
| 328 | while (i + 16 <= m.len) : (i += 16) { | 334 | state.enc(&dst, ad[i..][0..16]); |
| 329 | state.dec(m[i..][0..16], c[i..][0..16]); | 335 | } |
| 330 | } | 336 | if (ad.len % 16 != 0) { |
| 331 | if (m.len % 16 != 0) { | 337 | mem.set(u8, src[0..], 0); |
| 332 | mem.set(u8, src[0..], 0); | 338 | mem.copy(u8, src[0 .. ad.len % 16], ad[i .. i + ad.len % 16]); |
| 333 | mem.copy(u8, src[0 .. m.len % 16], c[i .. i + m.len % 16]); | 339 | state.enc(&dst, &src); |
| 334 | state.dec(&dst, &src); | 340 | } |
| 335 | mem.copy(u8, m[i .. i + m.len % 16], dst[0 .. m.len % 16]); | 341 | i = 0; |
| 336 | mem.set(u8, dst[0 .. m.len % 16], 0); | 342 | while (i + 16 <= m.len) : (i += 16) { |
| 337 | const blocks = &state.blocks; | 343 | state.enc(c[i..][0..16], m[i..][0..16]); |
| 338 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst)); | 344 | } |
| 339 | } | 345 | if (m.len % 16 != 0) { |
| 340 | const computed_tag = state.mac(ad.len, m.len); | 346 | mem.set(u8, src[0..], 0); |
| 341 | var acc: u8 = 0; | 347 | mem.copy(u8, src[0 .. m.len % 16], m[i .. i + m.len % 16]); |
| 342 | for (computed_tag, 0..) |_, j| { | 348 | state.enc(&dst, &src); |
| 343 | acc |= (computed_tag[j] ^ tag[j]); | 349 | mem.copy(u8, c[i .. i + m.len % 16], dst[0 .. m.len % 16]); |
| 344 | } | 350 | } |
| 345 | if (acc != 0) { | 351 | tag.* = state.mac(tag_bits, ad.len, m.len); |
| 346 | @memset(m.ptr, undefined, m.len); | 352 | } |
| 347 | return error.AuthenticationFailed; | 353 | |
| | 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 |
| | 360 | 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 | assert(c.len == m.len); |
| | 362 | var state = State256.init(key, npub); |
| | 363 | var src: [16]u8 align(16) = undefined; |
| | 364 | var dst: [16]u8 align(16) = undefined; |
| | 365 | var i: usize = 0; |
| | 366 | while (i + 16 <= ad.len) : (i += 16) { |
| | 367 | state.enc(&dst, ad[i..][0..16]); |
| | 368 | } |
| | 369 | if (ad.len % 16 != 0) { |
| | 370 | mem.set(u8, src[0..], 0); |
| | 371 | mem.copy(u8, src[0 .. ad.len % 16], ad[i .. i + ad.len % 16]); |
| | 372 | state.enc(&dst, &src); |
| | 373 | } |
| | 374 | i = 0; |
| | 375 | while (i + 16 <= m.len) : (i += 16) { |
| | 376 | state.dec(m[i..][0..16], c[i..][0..16]); |
| | 377 | } |
| | 378 | if (m.len % 16 != 0) { |
| | 379 | mem.set(u8, src[0..], 0); |
| | 380 | mem.copy(u8, src[0 .. m.len % 16], c[i .. i + m.len % 16]); |
| | 381 | state.dec(&dst, &src); |
| | 382 | mem.copy(u8, m[i .. i + m.len % 16], dst[0 .. m.len % 16]); |
| | 383 | mem.set(u8, dst[0 .. m.len % 16], 0); |
| | 384 | const blocks = &state.blocks; |
| | 385 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst)); |
| | 386 | } |
| | 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) { |
| | 393 | @memset(m.ptr, undefined, m.len); |
| | 394 | return error.AuthenticationFailed; |
| | 395 | } |
| 348 | } | 396 | } |
| 349 | } | 397 | }; |
| 350 | }; | 398 | } |
| 351 | | 399 | |
| 352 | /// The AEGIS-128L message authentication function outputs 128 bit tags. | 400 | /// The `Aegis128LMac` message authentication function outputs 256 bit tags. |
| 353 | /// In addition to being extremely fast, its large state, non-linearity | 401 | /// In addition to being extremely fast, its large state, non-linearity |
| 354 | /// and non-invertibility provides the following properties: | 402 | /// and non-invertibility provides the following properties: |
| 355 | /// - 128 bit security, stronger than GHash/Polyval/Poly1305. | 403 | /// - 128 bit security, stronger than GHash/Polyval/Poly1305. |
| 356 | /// - Recovering the secret key from the state would require ~2^128 attempts, | 404 | /// - Recovering the secret key from the state would require ~2^128 attempts, |
| 357 | /// which is infeasible for any practical adversary. | 405 | /// which is infeasible for any practical adversary. |
| 358 | /// - It has a large security margin against internal collisions. | 406 | /// - It has a large security margin against internal collisions. |
| 359 | pub const Aegis128LMac = AegisMac(Aegis128L); | 407 | pub const Aegis128LMac = AegisMac(Aegis128L_256); |
| 360 | | 408 | |
| 361 | /// The AEGIS-256 message authentication function has a 256-bit key size, | 409 | /// The `Aegis256Mac` message authentication function has a 256-bit key size, |
| 362 | /// but outputs 128 bit tags. Unless theoretical multi-target attacks are a | 410 | /// and outputs 256 bit tags. Unless theoretical multi-target attacks are a |
| 363 | /// concern, the AEGIS-128L variant should be preferred. | 411 | /// concern, the AEGIS-128L variant should be preferred. |
| 364 | /// AEGIS' large state, non-linearity and non-invertibility provides the | 412 | /// AEGIS' large state, non-linearity and non-invertibility provides the |
| 365 | /// following properties: | 413 | /// following properties: |
| 366 | /// - 128 bit security, stronger than GHash/Polyval/Poly1305. | 414 | /// - 256 bit security against forgery. |
| 367 | /// - Recovering the secret key from the state would require ~2^128 attempts, | 415 | /// - Recovering the secret key from the state would require ~2^256 attempts, |
| 368 | /// which is infeasible for any practical adversary. | 416 | /// which is infeasible for any practical adversary. |
| 369 | /// - It has a large security margin against internal collisions. | 417 | /// - It has a large security margin against internal collisions. |
| 370 | pub const Aegis256Mac = AegisMac(Aegis256); | 418 | pub const Aegis256Mac = AegisMac(Aegis256_256); |
| 371 | | 419 | |
| 372 | fn AegisMac(comptime T: type) type { | 420 | fn AegisMac(comptime T: type) type { |
| 373 | return struct { | 421 | return struct { |
| ... | @@ -420,7 +468,7 @@ fn AegisMac(comptime T: type) type { | ... | @@ -420,7 +468,7 @@ fn AegisMac(comptime T: type) type { |
| 420 | mem.copy(u8, pad[0..], self.buf[0..self.off]); | 468 | mem.copy(u8, pad[0..], self.buf[0..self.off]); |
| 421 | self.state.absorb(&pad); | 469 | self.state.absorb(&pad); |
| 422 | } | 470 | } |
| 423 | out.* = self.state.mac(self.msg_len, 0); | 471 | out.* = self.state.mac(T.tag_length * 8, self.msg_len, 0); |
| 424 | } | 472 | } |
| 425 | | 473 | |
| 426 | /// Return an authentication tag for a message and a key | 474 | /// Return an authentication tag for a message and a key |
| ... | @@ -572,23 +620,23 @@ test "Aegis MAC" { | ... | @@ -572,23 +620,23 @@ test "Aegis MAC" { |
| 572 | st.update(msg[0..32]); | 620 | st.update(msg[0..32]); |
| 573 | st.update(msg[32..]); | 621 | st.update(msg[32..]); |
| 574 | st.final(&tag); | 622 | st.final(&tag); |
| 575 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); | 623 | try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); |
| 576 | | 624 | |
| 577 | st = st_init; | 625 | st = st_init; |
| 578 | st.update(msg[0..31]); | 626 | st.update(msg[0..31]); |
| 579 | st.update(msg[31..]); | 627 | st.update(msg[31..]); |
| 580 | st.final(&tag); | 628 | st.final(&tag); |
| 581 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); | 629 | try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); |
| 582 | | 630 | |
| 583 | st = st_init; | 631 | st = st_init; |
| 584 | st.update(msg[0..14]); | 632 | st.update(msg[0..14]); |
| 585 | st.update(msg[14..30]); | 633 | st.update(msg[14..30]); |
| 586 | st.update(msg[30..]); | 634 | st.update(msg[30..]); |
| 587 | st.final(&tag); | 635 | st.final(&tag); |
| 588 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); | 636 | try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); |
| 589 | | 637 | |
| 590 | var empty: [0]u8 = undefined; | 638 | var empty: [0]u8 = undefined; |
| 591 | const nonce = [_]u8{0x00} ** Aegis128L.nonce_length; | 639 | const nonce = [_]u8{0x00} ** Aegis128L_256.nonce_length; |
| 592 | Aegis128L.encrypt(&empty, &tag, &empty, &msg, nonce, key); | 640 | Aegis128L_256.encrypt(&empty, &tag, &empty, &msg, nonce, key); |
| 593 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); | 641 | try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); |
| 594 | } | 642 | } |