You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
9.4 KiB
287 lines
9.4 KiB
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */ |
|
// prettier-ignore |
|
import { createView, setBigUint64, wrapCipher, bytesToHex, concatBytes, equalBytes, hexToNumber, numberToBytesBE, } from './utils.js'; |
|
import { createCipher, rotl } from './_arx.js'; |
|
import { bytes as abytes } from './_assert.js'; |
|
/* |
|
noble-ciphers-micro: more auditable, but slower version of salsa20, chacha & poly1305. |
|
Implements the same algorithms that are present in other files, but without |
|
unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling). |
|
*/ |
|
function bytesToNumberLE(bytes) { |
|
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); |
|
} |
|
function numberToBytesLE(n, len) { |
|
return numberToBytesBE(n, len).reverse(); |
|
} |
|
function salsaQR(x, a, b, c, d) { |
|
x[b] ^= rotl((x[a] + x[d]) | 0, 7); |
|
x[c] ^= rotl((x[b] + x[a]) | 0, 9); |
|
x[d] ^= rotl((x[c] + x[b]) | 0, 13); |
|
x[a] ^= rotl((x[d] + x[c]) | 0, 18); |
|
} |
|
// prettier-ignore |
|
function chachaQR(x, a, b, c, d) { |
|
x[a] = (x[a] + x[b]) | 0; |
|
x[d] = rotl(x[d] ^ x[a], 16); |
|
x[c] = (x[c] + x[d]) | 0; |
|
x[b] = rotl(x[b] ^ x[c], 12); |
|
x[a] = (x[a] + x[b]) | 0; |
|
x[d] = rotl(x[d] ^ x[a], 8); |
|
x[c] = (x[c] + x[d]) | 0; |
|
x[b] = rotl(x[b] ^ x[c], 7); |
|
} |
|
function salsaRound(x, rounds = 20) { |
|
for (let r = 0; r < rounds; r += 2) { |
|
salsaQR(x, 0, 4, 8, 12); |
|
salsaQR(x, 5, 9, 13, 1); |
|
salsaQR(x, 10, 14, 2, 6); |
|
salsaQR(x, 15, 3, 7, 11); |
|
salsaQR(x, 0, 1, 2, 3); |
|
salsaQR(x, 5, 6, 7, 4); |
|
salsaQR(x, 10, 11, 8, 9); |
|
salsaQR(x, 15, 12, 13, 14); |
|
} |
|
} |
|
function chachaRound(x, rounds = 20) { |
|
for (let r = 0; r < rounds; r += 2) { |
|
chachaQR(x, 0, 4, 8, 12); |
|
chachaQR(x, 1, 5, 9, 13); |
|
chachaQR(x, 2, 6, 10, 14); |
|
chachaQR(x, 3, 7, 11, 15); |
|
chachaQR(x, 0, 5, 10, 15); |
|
chachaQR(x, 1, 6, 11, 12); |
|
chachaQR(x, 2, 7, 8, 13); |
|
chachaQR(x, 3, 4, 9, 14); |
|
} |
|
} |
|
function salsaCore(s, k, n, out, cnt, rounds = 20) { |
|
// prettier-ignore |
|
const y = new Uint32Array([ |
|
s[0], k[0], k[1], k[2], // "expa" Key Key Key |
|
k[3], s[1], n[0], n[1], // Key "nd 3" Nonce Nonce |
|
cnt, 0, s[2], k[4], // Pos. Pos. "2-by" Key |
|
k[5], k[6], k[7], s[3], // Key Key Key "te k" |
|
]); |
|
const x = y.slice(); |
|
salsaRound(x, rounds); |
|
for (let i = 0; i < 16; i++) |
|
out[i] = (y[i] + x[i]) | 0; |
|
} |
|
// prettier-ignore |
|
export function hsalsa(s, k, i, o32) { |
|
const x = new Uint32Array([ |
|
s[0], k[0], k[1], k[2], |
|
k[3], s[1], i[0], i[1], |
|
i[2], i[3], s[2], k[4], |
|
k[5], k[6], k[7], s[3] |
|
]); |
|
salsaRound(x, 20); |
|
let oi = 0; |
|
o32[oi++] = x[0]; |
|
o32[oi++] = x[5]; |
|
o32[oi++] = x[10]; |
|
o32[oi++] = x[15]; |
|
o32[oi++] = x[6]; |
|
o32[oi++] = x[7]; |
|
o32[oi++] = x[8]; |
|
o32[oi++] = x[9]; |
|
} |
|
function chachaCore(s, k, n, out, cnt, rounds = 20) { |
|
// prettier-ignore |
|
const y = new Uint32Array([ |
|
s[0], s[1], s[2], s[3], // "expa" "nd 3" "2-by" "te k" |
|
k[0], k[1], k[2], k[3], // Key Key Key Key |
|
k[4], k[5], k[6], k[7], // Key Key Key Key |
|
cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce |
|
]); |
|
const x = y.slice(); |
|
chachaRound(x, rounds); |
|
for (let i = 0; i < 16; i++) |
|
out[i] = (y[i] + x[i]) | 0; |
|
} |
|
// prettier-ignore |
|
export function hchacha(s, k, i, o32) { |
|
const x = new Uint32Array([ |
|
s[0], s[1], s[2], s[3], |
|
k[0], k[1], k[2], k[3], |
|
k[4], k[5], k[6], k[7], |
|
i[0], i[1], i[2], i[3], |
|
]); |
|
chachaRound(x, 20); |
|
let oi = 0; |
|
o32[oi++] = x[0]; |
|
o32[oi++] = x[1]; |
|
o32[oi++] = x[2]; |
|
o32[oi++] = x[3]; |
|
o32[oi++] = x[12]; |
|
o32[oi++] = x[13]; |
|
o32[oi++] = x[14]; |
|
o32[oi++] = x[15]; |
|
} |
|
/** |
|
* salsa20, 12-byte nonce. |
|
*/ |
|
export const salsa20 = /* @__PURE__ */ createCipher(salsaCore, { |
|
allowShortKeys: true, |
|
counterRight: true, |
|
}); |
|
/** |
|
* xsalsa20, 24-byte nonce. |
|
*/ |
|
export const xsalsa20 = /* @__PURE__ */ createCipher(salsaCore, { |
|
counterRight: true, |
|
extendNonceFn: hsalsa, |
|
}); |
|
/** |
|
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter. |
|
*/ |
|
export const chacha20orig = /* @__PURE__ */ createCipher(chachaCore, { |
|
allowShortKeys: true, |
|
counterRight: false, |
|
counterLength: 8, |
|
}); |
|
/** |
|
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter. |
|
*/ |
|
export const chacha20 = /* @__PURE__ */ createCipher(chachaCore, { |
|
counterRight: false, |
|
counterLength: 4, |
|
}); |
|
/** |
|
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha |
|
*/ |
|
export const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, { |
|
counterRight: false, |
|
counterLength: 8, |
|
extendNonceFn: hchacha, |
|
}); |
|
/** |
|
* 8-round chacha from the original paper. |
|
*/ |
|
export const chacha8 = /* @__PURE__ */ createCipher(chachaCore, { |
|
counterRight: false, |
|
counterLength: 4, |
|
rounds: 8, |
|
}); |
|
/** |
|
* 12-round chacha from the original paper. |
|
*/ |
|
export const chacha12 = /* @__PURE__ */ createCipher(chachaCore, { |
|
counterRight: false, |
|
counterLength: 4, |
|
rounds: 12, |
|
}); |
|
const POW_2_130_5 = BigInt(2) ** BigInt(130) - BigInt(5); |
|
const POW_2_128_1 = BigInt(2) ** BigInt(16 * 8) - BigInt(1); |
|
const CLAMP_R = BigInt('0x0ffffffc0ffffffc0ffffffc0fffffff'); |
|
const _0 = BigInt(0); |
|
const _1 = BigInt(1); |
|
// Can be speed-up using BigUint64Array, but would be more complicated |
|
export function poly1305(msg, key) { |
|
abytes(msg); |
|
abytes(key); |
|
let acc = _0; |
|
const r = bytesToNumberLE(key.subarray(0, 16)) & CLAMP_R; |
|
const s = bytesToNumberLE(key.subarray(16)); |
|
// Process by 16 byte chunks |
|
for (let i = 0; i < msg.length; i += 16) { |
|
const m = msg.subarray(i, i + 16); |
|
const n = bytesToNumberLE(m) | (_1 << BigInt(8 * m.length)); |
|
acc = ((acc + n) * r) % POW_2_130_5; |
|
} |
|
const res = (acc + s) & POW_2_128_1; |
|
return numberToBytesLE(res, 16); |
|
} |
|
function computeTag(fn, key, nonce, ciphertext, AAD) { |
|
const res = []; |
|
if (AAD) { |
|
res.push(AAD); |
|
const leftover = AAD.length % 16; |
|
if (leftover > 0) |
|
res.push(new Uint8Array(16 - leftover)); |
|
} |
|
res.push(ciphertext); |
|
const leftover = ciphertext.length % 16; |
|
if (leftover > 0) |
|
res.push(new Uint8Array(16 - leftover)); |
|
// Lengths |
|
const num = new Uint8Array(16); |
|
const view = createView(num); |
|
setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true); |
|
setBigUint64(view, 8, BigInt(ciphertext.length), true); |
|
res.push(num); |
|
const authKey = fn(key, nonce, new Uint8Array(32)); |
|
return poly1305(concatBytes(...res), authKey); |
|
} |
|
/** |
|
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa. |
|
*/ |
|
export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, function xsalsa20poly1305(key, nonce) { |
|
abytes(key); |
|
abytes(nonce); |
|
return { |
|
encrypt: (plaintext) => { |
|
abytes(plaintext); |
|
const m = concatBytes(new Uint8Array(32), plaintext); |
|
const c = xsalsa20(key, nonce, m); |
|
const authKey = c.subarray(0, 32); |
|
const data = c.subarray(32); |
|
const tag = poly1305(data, authKey); |
|
return concatBytes(tag, data); |
|
}, |
|
decrypt: (ciphertext) => { |
|
abytes(ciphertext); |
|
if (ciphertext.length < 16) |
|
throw new Error('encrypted data must be at least 16 bytes'); |
|
const c = concatBytes(new Uint8Array(16), ciphertext); |
|
const authKey = xsalsa20(key, nonce, new Uint8Array(32)); |
|
const tag = poly1305(c.subarray(32), authKey); |
|
if (!equalBytes(c.subarray(16, 32), tag)) |
|
throw new Error('invalid poly1305 tag'); |
|
return xsalsa20(key, nonce, c).subarray(32); |
|
}, |
|
}; |
|
}); |
|
/** |
|
* Alias to xsalsa20-poly1305 |
|
*/ |
|
export function secretbox(key, nonce) { |
|
const xs = xsalsa20poly1305(key, nonce); |
|
return { seal: xs.encrypt, open: xs.decrypt }; |
|
} |
|
export const _poly1305_aead = (fn) => (key, nonce, AAD) => { |
|
const tagLength = 16; |
|
const keyLength = 32; |
|
abytes(key, keyLength); |
|
abytes(nonce); |
|
return { |
|
encrypt: (plaintext) => { |
|
abytes(plaintext); |
|
const res = fn(key, nonce, plaintext, undefined, 1); |
|
const tag = computeTag(fn, key, nonce, res, AAD); |
|
return concatBytes(res, tag); |
|
}, |
|
decrypt: (ciphertext) => { |
|
abytes(ciphertext); |
|
if (ciphertext.length < tagLength) |
|
throw new Error(`encrypted data must be at least ${tagLength} bytes`); |
|
const passedTag = ciphertext.subarray(-tagLength); |
|
const data = ciphertext.subarray(0, -tagLength); |
|
const tag = computeTag(fn, key, nonce, data, AAD); |
|
if (!equalBytes(passedTag, tag)) |
|
throw new Error('invalid poly1305 tag'); |
|
return fn(key, nonce, data, undefined, 1); |
|
}, |
|
}; |
|
}; |
|
/** |
|
* chacha20-poly1305 12-byte-nonce chacha. |
|
*/ |
|
export const chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20)); |
|
/** |
|
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha. |
|
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG). |
|
*/ |
|
export const xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, _poly1305_aead(xchacha20)); |
|
//# sourceMappingURL=_micro.js.map
|