| 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const crypto = std.crypto; |
| 4 | const debug = std.debug; |
| 5 | const math = std.math; |
| 6 | const mem = std.mem; |
| 7 | |
| 8 | const Poly1305 = crypto.onetimeauth.Poly1305; |
| 9 | const Blake2b = crypto.hash.blake2.Blake2b; |
| 10 | const X25519 = crypto.dh.X25519; |
| 11 | |
| 12 | const AuthenticationError = crypto.errors.AuthenticationError; |
| 13 | const IdentityElementError = crypto.errors.IdentityElementError; |
| 14 | const WeakPublicKeyError = crypto.errors.WeakPublicKeyError; |
| 15 | |
| 16 | /// The Salsa cipher with 20 rounds. |
| 17 | pub const Salsa20 = Salsa(20); |
| 18 | |
| 19 | /// The XSalsa cipher with 20 rounds. |
| 20 | pub const XSalsa20 = XSalsa(20); |
| 21 | |
| 22 | fn SalsaVecImpl(comptime rounds: comptime_int) type { |
| 23 | return struct { |
| 24 | const Lane = @Vector(4, u32); |
| 25 | const Half = @Vector(2, u32); |
| 26 | const BlockVec = [4]Lane; |
| 27 | |
| 28 | fn initContext(key: [8]u32, d: [4]u32) BlockVec { |
| 29 | const c = "expand 32-byte k"; |
| 30 | const constant_le = comptime [4]u32{ |
| 31 | mem.readInt(u32, c[0..4], .little), |
| 32 | mem.readInt(u32, c[4..8], .little), |
| 33 | mem.readInt(u32, c[8..12], .little), |
| 34 | mem.readInt(u32, c[12..16], .little), |
| 35 | }; |
| 36 | return BlockVec{ |
| 37 | Lane{ key[0], key[1], key[2], key[3] }, |
| 38 | Lane{ key[4], key[5], key[6], key[7] }, |
| 39 | Lane{ constant_le[0], constant_le[1], constant_le[2], constant_le[3] }, |
| 40 | Lane{ d[0], d[1], d[2], d[3] }, |
| 41 | }; |
| 42 | } |
| 43 | |
| 44 | fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void { |
| 45 | const n1n2n3n0 = Lane{ input[3][1], input[3][2], input[3][3], input[3][0] }; |
| 46 | const n1n2 = Half{ n1n2n3n0[0], n1n2n3n0[1] }; |
| 47 | const n3n0 = Half{ n1n2n3n0[2], n1n2n3n0[3] }; |
| 48 | const k0k1 = Half{ input[0][0], input[0][1] }; |
| 49 | const k2k3 = Half{ input[0][2], input[0][3] }; |
| 50 | const k4k5 = Half{ input[1][0], input[1][1] }; |
| 51 | const k6k7 = Half{ input[1][2], input[1][3] }; |
| 52 | const n0k0 = Half{ n3n0[1], k0k1[0] }; |
| 53 | const k0n0 = Half{ n0k0[1], n0k0[0] }; |
| 54 | const k4k5k0n0 = Lane{ k4k5[0], k4k5[1], k0n0[0], k0n0[1] }; |
| 55 | const k1k6 = Half{ k0k1[1], k6k7[0] }; |
| 56 | const k6k1 = Half{ k1k6[1], k1k6[0] }; |
| 57 | const n1n2k6k1 = Lane{ n1n2[0], n1n2[1], k6k1[0], k6k1[1] }; |
| 58 | const k7n3 = Half{ k6k7[1], n3n0[0] }; |
| 59 | const n3k7 = Half{ k7n3[1], k7n3[0] }; |
| 60 | const k2k3n3k7 = Lane{ k2k3[0], k2k3[1], n3k7[0], n3k7[1] }; |
| 61 | |
| 62 | var diag0 = input[2]; |
| 63 | var diag1 = @shuffle(u32, k4k5k0n0, undefined, [_]i32{ 1, 2, 3, 0 }); |
| 64 | var diag2 = @shuffle(u32, n1n2k6k1, undefined, [_]i32{ 1, 2, 3, 0 }); |
| 65 | var diag3 = @shuffle(u32, k2k3n3k7, undefined, [_]i32{ 1, 2, 3, 0 }); |
| 66 | |
| 67 | const start0 = diag0; |
| 68 | const start1 = diag1; |
| 69 | const start2 = diag2; |
| 70 | const start3 = diag3; |
| 71 | |
| 72 | var i: usize = 0; |
| 73 | while (i < rounds) : (i += 2) { |
| 74 | diag3 ^= math.rotl(Lane, diag1 +% diag0, 7); |
| 75 | diag2 ^= math.rotl(Lane, diag0 +% diag3, 9); |
| 76 | diag1 ^= math.rotl(Lane, diag3 +% diag2, 13); |
| 77 | diag0 ^= math.rotl(Lane, diag2 +% diag1, 18); |
| 78 | |
| 79 | diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 3, 0, 1, 2 }); |
| 80 | diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 }); |
| 81 | diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 1, 2, 3, 0 }); |
| 82 | |
| 83 | diag1 ^= math.rotl(Lane, diag3 +% diag0, 7); |
| 84 | diag2 ^= math.rotl(Lane, diag0 +% diag1, 9); |
| 85 | diag3 ^= math.rotl(Lane, diag1 +% diag2, 13); |
| 86 | diag0 ^= math.rotl(Lane, diag2 +% diag3, 18); |
| 87 | |
| 88 | diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 3, 0, 1, 2 }); |
| 89 | diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 }); |
| 90 | diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 1, 2, 3, 0 }); |
| 91 | } |
| 92 | |
| 93 | if (feedback) { |
| 94 | diag0 +%= start0; |
| 95 | diag1 +%= start1; |
| 96 | diag2 +%= start2; |
| 97 | diag3 +%= start3; |
| 98 | } |
| 99 | |
| 100 | const x0x1x10x11 = Lane{ diag0[0], diag1[1], diag0[2], diag1[3] }; |
| 101 | const x12x13x6x7 = Lane{ diag1[0], diag2[1], diag1[2], diag2[3] }; |
| 102 | const x8x9x2x3 = Lane{ diag2[0], diag3[1], diag2[2], diag3[3] }; |
| 103 | const x4x5x14x15 = Lane{ diag3[0], diag0[1], diag3[2], diag0[3] }; |
| 104 | |
| 105 | x[0] = Lane{ x0x1x10x11[0], x0x1x10x11[1], x8x9x2x3[2], x8x9x2x3[3] }; |
| 106 | x[1] = Lane{ x4x5x14x15[0], x4x5x14x15[1], x12x13x6x7[2], x12x13x6x7[3] }; |
| 107 | x[2] = Lane{ x8x9x2x3[0], x8x9x2x3[1], x0x1x10x11[2], x0x1x10x11[3] }; |
| 108 | x[3] = Lane{ x12x13x6x7[0], x12x13x6x7[1], x4x5x14x15[2], x4x5x14x15[3] }; |
| 109 | } |
| 110 | |
| 111 | fn hashToBytes(out: *[64]u8, x: BlockVec) void { |
| 112 | var i: usize = 0; |
| 113 | while (i < 4) : (i += 1) { |
| 114 | mem.writeInt(u32, out[16 * i + 0 ..][0..4], x[i][0], .little); |
| 115 | mem.writeInt(u32, out[16 * i + 4 ..][0..4], x[i][1], .little); |
| 116 | mem.writeInt(u32, out[16 * i + 8 ..][0..4], x[i][2], .little); |
| 117 | mem.writeInt(u32, out[16 * i + 12 ..][0..4], x[i][3], .little); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void { |
| 122 | var ctx = initContext(key, d); |
| 123 | var x: BlockVec = undefined; |
| 124 | var buf: [64]u8 = undefined; |
| 125 | var i: usize = 0; |
| 126 | while (i + 64 <= in.len) : (i += 64) { |
| 127 | salsaCore(x[0..], ctx, true); |
| 128 | hashToBytes(buf[0..], x); |
| 129 | var xout = out[i..]; |
| 130 | const xin = in[i..]; |
| 131 | var j: usize = 0; |
| 132 | while (j < 64) : (j += 1) { |
| 133 | xout[j] = xin[j]; |
| 134 | } |
| 135 | j = 0; |
| 136 | while (j < 64) : (j += 1) { |
| 137 | xout[j] ^= buf[j]; |
| 138 | } |
| 139 | ctx[3][2] +%= 1; |
| 140 | if (ctx[3][2] == 0) { |
| 141 | ctx[3][3] += 1; |
| 142 | } |
| 143 | } |
| 144 | if (i < in.len) { |
| 145 | salsaCore(x[0..], ctx, true); |
| 146 | hashToBytes(buf[0..], x); |
| 147 | |
| 148 | var xout = out[i..]; |
| 149 | const xin = in[i..]; |
| 150 | var j: usize = 0; |
| 151 | while (j < in.len % 64) : (j += 1) { |
| 152 | xout[j] = xin[j] ^ buf[j]; |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 { |
| 158 | var c: [4]u32 = undefined; |
| 159 | for (c, 0..) |_, i| { |
| 160 | c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little); |
| 161 | } |
| 162 | const ctx = initContext(keyToWords(key), c); |
| 163 | var x: BlockVec = undefined; |
| 164 | salsaCore(x[0..], ctx, false); |
| 165 | var out: [32]u8 = undefined; |
| 166 | mem.writeInt(u32, out[0..4], x[0][0], .little); |
| 167 | mem.writeInt(u32, out[4..8], x[1][1], .little); |
| 168 | mem.writeInt(u32, out[8..12], x[2][2], .little); |
| 169 | mem.writeInt(u32, out[12..16], x[3][3], .little); |
| 170 | mem.writeInt(u32, out[16..20], x[1][2], .little); |
| 171 | mem.writeInt(u32, out[20..24], x[1][3], .little); |
| 172 | mem.writeInt(u32, out[24..28], x[2][0], .little); |
| 173 | mem.writeInt(u32, out[28..32], x[2][1], .little); |
| 174 | return out; |
| 175 | } |
| 176 | }; |
| 177 | } |
| 178 | |
| 179 | fn SalsaNonVecImpl(comptime rounds: comptime_int) type { |
| 180 | return struct { |
| 181 | const BlockVec = [16]u32; |
| 182 | |
| 183 | fn initContext(key: [8]u32, d: [4]u32) BlockVec { |
| 184 | const c = "expand 32-byte k"; |
| 185 | const constant_le = comptime [4]u32{ |
| 186 | mem.readInt(u32, c[0..4], .little), |
| 187 | mem.readInt(u32, c[4..8], .little), |
| 188 | mem.readInt(u32, c[8..12], .little), |
| 189 | mem.readInt(u32, c[12..16], .little), |
| 190 | }; |
| 191 | return BlockVec{ |
| 192 | constant_le[0], key[0], key[1], key[2], |
| 193 | key[3], constant_le[1], d[0], d[1], |
| 194 | d[2], d[3], constant_le[2], key[4], |
| 195 | key[5], key[6], key[7], constant_le[3], |
| 196 | }; |
| 197 | } |
| 198 | |
| 199 | const QuarterRound = struct { |
| 200 | a: usize, |
| 201 | b: usize, |
| 202 | c: usize, |
| 203 | d: u6, |
| 204 | }; |
| 205 | |
| 206 | fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound { |
| 207 | return QuarterRound{ |
| 208 | .a = a, |
| 209 | .b = b, |
| 210 | .c = c, |
| 211 | .d = d, |
| 212 | }; |
| 213 | } |
| 214 | |
| 215 | fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void { |
| 216 | const arx_steps = comptime [_]QuarterRound{ |
| 217 | Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18), |
| 218 | Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18), |
| 219 | Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18), |
| 220 | Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18), |
| 221 | Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18), |
| 222 | Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18), |
| 223 | Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18), |
| 224 | Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18), |
| 225 | }; |
| 226 | x.* = input; |
| 227 | var j: usize = 0; |
| 228 | while (j < rounds) : (j += 2) { |
| 229 | inline for (arx_steps) |r| { |
| 230 | x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d); |
| 231 | } |
| 232 | } |
| 233 | if (feedback) { |
| 234 | j = 0; |
| 235 | while (j < 16) : (j += 1) { |
| 236 | x[j] +%= input[j]; |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | fn hashToBytes(out: *[64]u8, x: BlockVec) void { |
| 242 | for (x, 0..) |w, i| { |
| 243 | mem.writeInt(u32, out[i * 4 ..][0..4], w, .little); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void { |
| 248 | var ctx = initContext(key, d); |
| 249 | var x: BlockVec = undefined; |
| 250 | var buf: [64]u8 = undefined; |
| 251 | var i: usize = 0; |
| 252 | while (i + 64 <= in.len) : (i += 64) { |
| 253 | salsaCore(x[0..], ctx, true); |
| 254 | hashToBytes(buf[0..], x); |
| 255 | var xout = out[i..]; |
| 256 | const xin = in[i..]; |
| 257 | var j: usize = 0; |
| 258 | while (j < 64) : (j += 1) { |
| 259 | xout[j] = xin[j]; |
| 260 | } |
| 261 | j = 0; |
| 262 | while (j < 64) : (j += 1) { |
| 263 | xout[j] ^= buf[j]; |
| 264 | } |
| 265 | const ov = @addWithOverflow(ctx[8], 1); |
| 266 | ctx[8] = ov[0]; |
| 267 | ctx[9] += ov[1]; |
| 268 | } |
| 269 | if (i < in.len) { |
| 270 | salsaCore(x[0..], ctx, true); |
| 271 | hashToBytes(buf[0..], x); |
| 272 | |
| 273 | var xout = out[i..]; |
| 274 | const xin = in[i..]; |
| 275 | var j: usize = 0; |
| 276 | while (j < in.len % 64) : (j += 1) { |
| 277 | xout[j] = xin[j] ^ buf[j]; |
| 278 | } |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 { |
| 283 | var c: [4]u32 = undefined; |
| 284 | for (c, 0..) |_, i| { |
| 285 | c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little); |
| 286 | } |
| 287 | const ctx = initContext(keyToWords(key), c); |
| 288 | var x: BlockVec = undefined; |
| 289 | salsaCore(x[0..], ctx, false); |
| 290 | var out: [32]u8 = undefined; |
| 291 | mem.writeInt(u32, out[0..4], x[0], .little); |
| 292 | mem.writeInt(u32, out[4..8], x[5], .little); |
| 293 | mem.writeInt(u32, out[8..12], x[10], .little); |
| 294 | mem.writeInt(u32, out[12..16], x[15], .little); |
| 295 | mem.writeInt(u32, out[16..20], x[6], .little); |
| 296 | mem.writeInt(u32, out[20..24], x[7], .little); |
| 297 | mem.writeInt(u32, out[24..28], x[8], .little); |
| 298 | mem.writeInt(u32, out[28..32], x[9], .little); |
| 299 | return out; |
| 300 | } |
| 301 | }; |
| 302 | } |
| 303 | |
| 304 | const SalsaImpl = if (builtin.cpu.arch == .x86_64) |
| 305 | SalsaVecImpl |
| 306 | else |
| 307 | SalsaNonVecImpl; |
| 308 | |
| 309 | fn keyToWords(key: [32]u8) [8]u32 { |
| 310 | var k: [8]u32 = undefined; |
| 311 | var i: usize = 0; |
| 312 | while (i < 8) : (i += 1) { |
| 313 | k[i] = mem.readInt(u32, key[i * 4 ..][0..4], .little); |
| 314 | } |
| 315 | return k; |
| 316 | } |
| 317 | |
| 318 | fn extend(comptime rounds: comptime_int, key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } { |
| 319 | return .{ |
| 320 | .key = SalsaImpl(rounds).hsalsa(nonce[0..16].*, key), |
| 321 | .nonce = nonce[16..24].*, |
| 322 | }; |
| 323 | } |
| 324 | |
| 325 | /// The Salsa stream cipher. |
| 326 | pub fn Salsa(comptime rounds: comptime_int) type { |
| 327 | return struct { |
| 328 | /// Nonce length in bytes. |
| 329 | pub const nonce_length = 8; |
| 330 | /// Key length in bytes. |
| 331 | pub const key_length = 32; |
| 332 | |
| 333 | /// Add the output of the Salsa stream cipher to `in` and stores the result into `out`. |
| 334 | /// WARNING: This function doesn't provide authenticated encryption. |
| 335 | /// Using the AEAD or one of the `box` versions is usually preferred. |
| 336 | pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void { |
| 337 | debug.assert(in.len == out.len); |
| 338 | |
| 339 | var d: [4]u32 = undefined; |
| 340 | d[0] = mem.readInt(u32, nonce[0..4], .little); |
| 341 | d[1] = mem.readInt(u32, nonce[4..8], .little); |
| 342 | d[2] = @as(u32, @truncate(counter)); |
| 343 | d[3] = @as(u32, @truncate(counter >> 32)); |
| 344 | SalsaImpl(rounds).salsaXor(out, in, keyToWords(key), d); |
| 345 | } |
| 346 | }; |
| 347 | } |
| 348 | |
| 349 | /// The XSalsa stream cipher. |
| 350 | pub fn XSalsa(comptime rounds: comptime_int) type { |
| 351 | return struct { |
| 352 | /// Nonce length in bytes. |
| 353 | pub const nonce_length = 24; |
| 354 | /// Key length in bytes. |
| 355 | pub const key_length = 32; |
| 356 | |
| 357 | /// Add the output of the XSalsa stream cipher to `in` and stores the result into `out`. |
| 358 | /// WARNING: This function doesn't provide authenticated encryption. |
| 359 | /// Using the AEAD or one of the `box` versions is usually preferred. |
| 360 | pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void { |
| 361 | const extended = extend(rounds, key, nonce); |
| 362 | Salsa(rounds).xor(out, in, counter, extended.key, extended.nonce); |
| 363 | } |
| 364 | }; |
| 365 | } |
| 366 | |
| 367 | /// The XSalsa stream cipher, combined with the Poly1305 MAC |
| 368 | pub const XSalsa20Poly1305 = struct { |
| 369 | /// Authentication tag length in bytes. |
| 370 | pub const tag_length = Poly1305.mac_length; |
| 371 | /// Nonce length in bytes. |
| 372 | pub const nonce_length = XSalsa20.nonce_length; |
| 373 | /// Key length in bytes. |
| 374 | pub const key_length = XSalsa20.key_length; |
| 375 | |
| 376 | const rounds = 20; |
| 377 | |
| 378 | /// c: ciphertext: output buffer should be of size m.len |
| 379 | /// tag: authentication tag: output MAC |
| 380 | /// m: message |
| 381 | /// ad: Associated Data (see below) |
| 382 | /// npub: public nonce |
| 383 | /// k: private key |
| 384 | /// |
| 385 | /// With this construction, if the associated data is not empty, |
| 386 | /// it must only contain fixed-length information, such as |
| 387 | /// session identifiers and sequence numbers. |
| 388 | /// |
| 389 | /// Since there is no separation between the associated data and |
| 390 | /// the ciphertext, its length must not be under an adversary's |
| 391 | /// control. |
| 392 | pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { |
| 393 | debug.assert(c.len == m.len); |
| 394 | const extended = extend(rounds, k, npub); |
| 395 | var block0: [64]u8 = @splat(0); |
| 396 | const mlen0 = @min(32, m.len); |
| 397 | @memcpy(block0[32..][0..mlen0], m[0..mlen0]); |
| 398 | Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce); |
| 399 | @memcpy(c[0..mlen0], block0[32..][0..mlen0]); |
| 400 | Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce); |
| 401 | var mac = Poly1305.init(block0[0..32]); |
| 402 | mac.update(ad); |
| 403 | mac.update(c); |
| 404 | mac.final(tag); |
| 405 | } |
| 406 | |
| 407 | /// `m`: Message |
| 408 | /// `c`: Ciphertext |
| 409 | /// `tag`: Authentication tag |
| 410 | /// `ad`: Associated data (see below) |
| 411 | /// `npub`: Public nonce |
| 412 | /// `k`: Private key |
| 413 | /// Asserts `c.len == m.len`. |
| 414 | /// |
| 415 | /// With this construction, if the associated data is not empty, |
| 416 | /// it must only contain fixed-length information, such as |
| 417 | /// sessions identifiers and sequence numbers. |
| 418 | /// |
| 419 | /// Since there is no separation between the associated data and |
| 420 | /// the ciphertext, its length must not be under an adversary's |
| 421 | /// control. |
| 422 | /// |
| 423 | /// Contents of `m` are undefined if an error is returned. |
| 424 | 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 { |
| 425 | debug.assert(c.len == m.len); |
| 426 | const extended = extend(rounds, k, npub); |
| 427 | var block0: [64]u8 = @splat(0); |
| 428 | const mlen0 = @min(32, c.len); |
| 429 | @memcpy(block0[32..][0..mlen0], c[0..mlen0]); |
| 430 | Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce); |
| 431 | var mac = Poly1305.init(block0[0..32]); |
| 432 | mac.update(ad); |
| 433 | mac.update(c); |
| 434 | var computed_tag: [tag_length]u8 = undefined; |
| 435 | mac.final(&computed_tag); |
| 436 | |
| 437 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); |
| 438 | if (!verify) { |
| 439 | crypto.secureZero(u8, &computed_tag); |
| 440 | @memset(m, undefined); |
| 441 | return error.AuthenticationFailed; |
| 442 | } |
| 443 | @memcpy(m[0..mlen0], block0[32..][0..mlen0]); |
| 444 | Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce); |
| 445 | } |
| 446 | }; |
| 447 | |
| 448 | /// NaCl-compatible secretbox API. |
| 449 | /// |
| 450 | /// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with. |
| 451 | /// A secret key shared by all the recipients must be already known in order to use this API. |
| 452 | /// |
| 453 | /// Nonces are 192-bit large and can safely be chosen with a random number generator. |
| 454 | pub const SecretBox = struct { |
| 455 | /// Key length in bytes. |
| 456 | pub const key_length = XSalsa20Poly1305.key_length; |
| 457 | /// Nonce length in bytes. |
| 458 | pub const nonce_length = XSalsa20Poly1305.nonce_length; |
| 459 | /// Authentication tag length in bytes. |
| 460 | pub const tag_length = XSalsa20Poly1305.tag_length; |
| 461 | |
| 462 | /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`. |
| 463 | /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag. |
| 464 | pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { |
| 465 | debug.assert(c.len == tag_length + m.len); |
| 466 | XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k); |
| 467 | } |
| 468 | |
| 469 | /// Verify and decrypt `c` using a nonce `npub` and a key `k`. |
| 470 | /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message. |
| 471 | pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void { |
| 472 | if (c.len < tag_length) { |
| 473 | return error.AuthenticationFailed; |
| 474 | } |
| 475 | debug.assert(m.len == c.len - tag_length); |
| 476 | return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k); |
| 477 | } |
| 478 | }; |
| 479 | |
| 480 | /// NaCl-compatible box API. |
| 481 | /// |
| 482 | /// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with. |
| 483 | /// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties. |
| 484 | /// Instead, a message is encrypted using a sender's secret key and a recipient's public key, |
| 485 | /// and is decrypted using the recipient's secret key and the sender's public key. |
| 486 | /// |
| 487 | /// Nonces are 192-bit large and can safely be chosen with a random number generator. |
| 488 | pub const Box = struct { |
| 489 | /// Public key length in bytes. |
| 490 | pub const public_length = X25519.public_length; |
| 491 | /// Secret key length in bytes. |
| 492 | pub const secret_length = X25519.secret_length; |
| 493 | /// Shared key length in bytes. |
| 494 | pub const shared_length = XSalsa20Poly1305.key_length; |
| 495 | /// Seed (for key pair creation) length in bytes. |
| 496 | pub const seed_length = X25519.seed_length; |
| 497 | /// Nonce length in bytes. |
| 498 | pub const nonce_length = XSalsa20Poly1305.nonce_length; |
| 499 | /// Authentication tag length in bytes. |
| 500 | pub const tag_length = XSalsa20Poly1305.tag_length; |
| 501 | |
| 502 | /// A key pair. |
| 503 | pub const KeyPair = X25519.KeyPair; |
| 504 | |
| 505 | /// Compute a secret suitable for `secretbox` given a recipient's public key and a sender's secret key. |
| 506 | pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)![shared_length]u8 { |
| 507 | const p = try X25519.scalarmult(secret_key, public_key); |
| 508 | const zero: [16]u8 = @splat(0); |
| 509 | return SalsaImpl(20).hsalsa(zero, p); |
| 510 | } |
| 511 | |
| 512 | /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`. |
| 513 | pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)!void { |
| 514 | const shared_key = try createSharedSecret(public_key, secret_key); |
| 515 | return SecretBox.seal(c, m, npub, shared_key); |
| 516 | } |
| 517 | |
| 518 | /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`. |
| 519 | pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void { |
| 520 | const shared_key = try createSharedSecret(public_key, secret_key); |
| 521 | return SecretBox.open(m, c, npub, shared_key); |
| 522 | } |
| 523 | }; |
| 524 | |
| 525 | /// libsodium-compatible sealed boxes |
| 526 | /// |
| 527 | /// Sealed boxes are designed to anonymously send messages to a recipient given their public key. |
| 528 | /// Only the recipient can decrypt these messages, using their private key. |
| 529 | /// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender. |
| 530 | /// |
| 531 | /// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process. |
| 532 | pub const SealedBox = struct { |
| 533 | pub const public_length = Box.public_length; |
| 534 | pub const secret_length = Box.secret_length; |
| 535 | pub const seed_length = Box.seed_length; |
| 536 | pub const seal_length = Box.public_length + Box.tag_length; |
| 537 | |
| 538 | /// A key pair. |
| 539 | pub const KeyPair = Box.KeyPair; |
| 540 | |
| 541 | fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [Box.nonce_length]u8 { |
| 542 | var hasher = Blake2b(Box.nonce_length * 8).init(.{}); |
| 543 | hasher.update(&pk1); |
| 544 | hasher.update(&pk2); |
| 545 | var nonce: [Box.nonce_length]u8 = undefined; |
| 546 | hasher.final(&nonce); |
| 547 | return nonce; |
| 548 | } |
| 549 | |
| 550 | /// Encrypt a message `m` for a recipient whose public key is `public_key`. |
| 551 | /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added. |
| 552 | pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void { |
| 553 | debug.assert(c.len == m.len + seal_length); |
| 554 | var ekp = KeyPair.generate(io); |
| 555 | const nonce = createNonce(ekp.public_key, public_key); |
| 556 | c[0..public_length].* = ekp.public_key; |
| 557 | try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key); |
| 558 | crypto.secureZero(u8, ekp.secret_key[0..]); |
| 559 | } |
| 560 | |
| 561 | /// Decrypt a message using a key pair. |
| 562 | /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata. |
| 563 | pub fn open(m: []u8, c: []const u8, keypair: KeyPair) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void { |
| 564 | if (c.len < seal_length) { |
| 565 | return error.AuthenticationFailed; |
| 566 | } |
| 567 | const epk = c[0..public_length]; |
| 568 | const nonce = createNonce(epk.*, keypair.public_key); |
| 569 | return Box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key); |
| 570 | } |
| 571 | }; |
| 572 | |
| 573 | const htest = @import("test.zig"); |
| 574 | |
| 575 | test "(x)salsa20" { |
| 576 | const key: [32]u8 = @splat(0x69); |
| 577 | const nonce: [8]u8 = @splat(0x42); |
| 578 | const msg: [20]u8 = @splat(0); |
| 579 | var c: [msg.len]u8 = undefined; |
| 580 | |
| 581 | Salsa20.xor(&c, msg[0..], 0, key, nonce); |
| 582 | try htest.assertEqual("30ff9933aa6534ff5207142593cd1fca4b23bdd8", c[0..]); |
| 583 | |
| 584 | const extended_nonce: [24]u8 = @splat(0x42); |
| 585 | XSalsa20.xor(&c, msg[0..], 0, key, extended_nonce); |
| 586 | try htest.assertEqual("b4ab7d82e750ec07644fa3281bce6cd91d4243f9", c[0..]); |
| 587 | } |
| 588 | |
| 589 | test "xsalsa20poly1305" { |
| 590 | const io = std.testing.io; |
| 591 | var msg: [100]u8 = undefined; |
| 592 | var msg2: [msg.len]u8 = undefined; |
| 593 | var c: [msg.len]u8 = undefined; |
| 594 | var key: [XSalsa20Poly1305.key_length]u8 = undefined; |
| 595 | var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined; |
| 596 | var tag: [XSalsa20Poly1305.tag_length]u8 = undefined; |
| 597 | io.random(&msg); |
| 598 | io.random(&key); |
| 599 | io.random(&nonce); |
| 600 | |
| 601 | XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key); |
| 602 | try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key); |
| 603 | } |
| 604 | |
| 605 | test "xsalsa20poly1305 secretbox" { |
| 606 | const io = std.testing.io; |
| 607 | var msg: [100]u8 = undefined; |
| 608 | var msg2: [msg.len]u8 = undefined; |
| 609 | var key: [XSalsa20Poly1305.key_length]u8 = undefined; |
| 610 | var nonce: [Box.nonce_length]u8 = undefined; |
| 611 | var boxed: [msg.len + Box.tag_length]u8 = undefined; |
| 612 | io.random(&msg); |
| 613 | io.random(&key); |
| 614 | io.random(&nonce); |
| 615 | |
| 616 | SecretBox.seal(boxed[0..], msg[0..], nonce, key); |
| 617 | try SecretBox.open(msg2[0..], boxed[0..], nonce, key); |
| 618 | } |
| 619 | |
| 620 | test "xsalsa20poly1305 box" { |
| 621 | const io = std.testing.io; |
| 622 | var msg: [100]u8 = undefined; |
| 623 | var msg2: [msg.len]u8 = undefined; |
| 624 | var nonce: [Box.nonce_length]u8 = undefined; |
| 625 | var boxed: [msg.len + Box.tag_length]u8 = undefined; |
| 626 | io.random(&msg); |
| 627 | io.random(&nonce); |
| 628 | |
| 629 | const kp1 = Box.KeyPair.generate(io); |
| 630 | const kp2 = Box.KeyPair.generate(io); |
| 631 | try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key); |
| 632 | try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key); |
| 633 | } |
| 634 | |
| 635 | test "xsalsa20poly1305 sealedbox" { |
| 636 | const io = std.testing.io; |
| 637 | var msg: [100]u8 = undefined; |
| 638 | var msg2: [msg.len]u8 = undefined; |
| 639 | var boxed: [msg.len + SealedBox.seal_length]u8 = undefined; |
| 640 | io.random(&msg); |
| 641 | |
| 642 | const kp = Box.KeyPair.generate(io); |
| 643 | try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key); |
| 644 | try SealedBox.open(msg2[0..], boxed[0..], kp); |
| 645 | } |
| 646 | |
| 647 | test "secretbox twoblocks" { |
| 648 | const key = [_]u8{ 0xc9, 0xc9, 0x4d, 0xcf, 0x68, 0xbe, 0x00, 0xe4, 0x7f, 0xe6, 0x13, 0x26, 0xfc, 0xc4, 0x2f, 0xd0, 0xdb, 0x93, 0x91, 0x1c, 0x09, 0x94, 0x89, 0xe1, 0x1b, 0x88, 0x63, 0x18, 0x86, 0x64, 0x8b, 0x7b }; |
| 649 | const nonce = [_]u8{ 0xa4, 0x33, 0xe9, 0x0a, 0x07, 0x68, 0x6e, 0x9a, 0x2b, 0x6d, 0xd4, 0x59, 0x04, 0x72, 0x3e, 0xd3, 0x8a, 0x67, 0x55, 0xc7, 0x9e, 0x3e, 0x77, 0xdc }; |
| 650 | const msg: [97]u8 = @splat('a'); |
| 651 | var ciphertext: [msg.len + SecretBox.tag_length]u8 = undefined; |
| 652 | SecretBox.seal(&ciphertext, &msg, nonce, key); |
| 653 | try htest.assertEqual("b05760e217288ba079caa2fd57fd3701784974ffcfda20fe523b89211ad8af065a6eb37cdb29d51aca5bd75dafdd21d18b044c54bb7c526cf576c94ee8900f911ceab0147e82b667a28c52d58ceb29554ff45471224d37b03256b01c119b89ff6d36855de8138d103386dbc9d971f52261", &ciphertext); |
| 654 | } |