UNPKG

mima-kit

Version:

mima-kit is a cryptographic suite implemented in TypeScript. The goal is to provide an easy-to-use cryptographic library. mima-kit 是一个使用 TypeScript 实现的密码学套件。目标是提供一个简单易用的密码学库。

229 lines (228 loc) 8.79 kB
import { hmac } from '../hash/hmac'; import { sha256 } from '../hash/sha256'; import { Counter, joinBuffer, KitError, rotateL32, U8, u8, u32 } from './utils'; /** * ANSI-X9.63 Key Derivation Function * * ANSI-X9.63 密钥派生函数 */ export function x963kdf(hash, info = new Uint8Array(0)) { const d_byte = hash.DIGEST_SIZE; return (k_byte, ikm) => { /** Output Keying Material */ const okm = []; const counter = new Counter([0, 0, 0, 1]); for (let okm_byte = 0; okm_byte < k_byte; okm_byte += d_byte) { const data = joinBuffer(ikm, counter, info); okm.push(hash(data)); counter.inc(); } return joinBuffer(...okm).slice(0, k_byte); }; } /** * HMAC-based Key Derivation Function (HKDF), please combine `hmac` and `hash` externally to control the behavior of calling `hmac` inside the function. * * 基于 HMAC 的密钥派生函数 (HKDF), 请在外部组合 `hmac` 和 `hash` 函数, 以控制在函数内部调用 `hmac` 时的行为. */ export function hkdf(k_hash, info = new Uint8Array(0)) { const d_byte = k_hash.DIGEST_SIZE; return (k_byte, ikm, salt = new Uint8Array(0)) => { ikm = u8(ikm); salt = u8(salt); /** Pseudo-Random Key */ const prk = k_hash(salt, ikm); /** Output Keying Material */ const okm = []; const counter = new Uint8Array([1]); let prv = new Uint8Array(0); for (let okm_byte = 0; okm_byte < k_byte; okm_byte += d_byte) { prv = k_hash(prk, joinBuffer(prv, info, counter)); okm.push(prv); counter[0]++; } return joinBuffer(...okm).slice(0, k_byte); }; } /** * Password-Based Key Derivation Function 2 (PBKDF2), please combine `hmac` and `hash` externally to control the behavior of calling `hmac` inside the function. * * PBKDF2 密码基础密钥派生函数 (PBKDF2), 请在外部组合 `hmac` 和 `hash` 函数, 以控制在函数内部调用 `hmac` 时的行为. */ export function pbkdf2(k_hash, iterations = 1000) { const d_byte = k_hash.DIGEST_SIZE; return (k_byte, ikm, salt = new Uint8Array(0)) => { ikm = u8(ikm); /** Output Keying Material */ const okm = new U8(k_byte); let T; let U; const counter = new Counter([0, 0, 0, 1]); for (let okm_byte = 0; okm_byte < k_byte; okm_byte += d_byte) { T = new Uint8Array(k_hash.DIGEST_SIZE); U = joinBuffer(salt, counter); for (let i = 0; i < iterations; i++) { U = k_hash(ikm, U); T.forEach((_, j) => { T[j] ^= U[j]; }); } okm.set(T, okm_byte); counter.inc(); } return okm; }; } /** * Scrypt Key Derivation Function * * Scrypt 密钥派生函数 * * Based on https://github.com/paulmillr/noble-hashes */ export function scrypt(config) { const { N = 16384, r = 8, p = 1, maxmem = 0x40000400, kdf = pbkdf2(hmac(sha256), 1) } = config ?? {}; const BLOCK_SIZE = r << 7; const BLOCK_SIZE_32 = r << 5; const MAX_p = (0x1fffffffe0 / BLOCK_SIZE) >>> 0; const MEM_COST = BLOCK_SIZE * (N + p); const N_1 = N - 1; if (N === 0 || (N & N_1) !== 0) throw new KitError(`N must be a power of 2`); if (p < 1 || p > MAX_p) throw new KitError(`p must be in range [1, ${MAX_p}]`); if (MEM_COST > maxmem) throw new KitError(`Memory cost exceeds maxmem: ${MEM_COST} > ${maxmem}`); // 内联 rotateL32 (神秘的黑魔法) const rotl = rotateL32; function fast_xor_salsa_hash(prev, pi, input, ii, output, oi) { const y00 = prev[pi++] ^ input[ii++]; const y01 = prev[pi++] ^ input[ii++]; const y02 = prev[pi++] ^ input[ii++]; const y03 = prev[pi++] ^ input[ii++]; const y04 = prev[pi++] ^ input[ii++]; const y05 = prev[pi++] ^ input[ii++]; const y06 = prev[pi++] ^ input[ii++]; const y07 = prev[pi++] ^ input[ii++]; const y08 = prev[pi++] ^ input[ii++]; const y09 = prev[pi++] ^ input[ii++]; const y10 = prev[pi++] ^ input[ii++]; const y11 = prev[pi++] ^ input[ii++]; const y12 = prev[pi++] ^ input[ii++]; const y13 = prev[pi++] ^ input[ii++]; const y14 = prev[pi++] ^ input[ii++]; const y15 = prev[pi++] ^ input[ii++]; // Save state to temporary variables (salsa) let x00 = y00; let x01 = y01; let x02 = y02; let x03 = y03; let x04 = y04; let x05 = y05; let x06 = y06; let x07 = y07; let x08 = y08; let x09 = y09; let x10 = y10; let x11 = y11; let x12 = y12; let x13 = y13; let x14 = y14; let x15 = y15; // Main loop (salsa) for (let i = 0; i < 8; i += 2) { x04 ^= rotl((x00 + x12) | 0, 7); x08 ^= rotl((x04 + x00) | 0, 9); x12 ^= rotl((x08 + x04) | 0, 13); x00 ^= rotl((x12 + x08) | 0, 18); x09 ^= rotl((x05 + x01) | 0, 7); x13 ^= rotl((x09 + x05) | 0, 9); x01 ^= rotl((x13 + x09) | 0, 13); x05 ^= rotl((x01 + x13) | 0, 18); x14 ^= rotl((x10 + x06) | 0, 7); x02 ^= rotl((x14 + x10) | 0, 9); x06 ^= rotl((x02 + x14) | 0, 13); x10 ^= rotl((x06 + x02) | 0, 18); x03 ^= rotl((x15 + x11) | 0, 7); x07 ^= rotl((x03 + x15) | 0, 9); x11 ^= rotl((x07 + x03) | 0, 13); x15 ^= rotl((x11 + x07) | 0, 18); x01 ^= rotl((x00 + x03) | 0, 7); x02 ^= rotl((x01 + x00) | 0, 9); x03 ^= rotl((x02 + x01) | 0, 13); x00 ^= rotl((x03 + x02) | 0, 18); x06 ^= rotl((x05 + x04) | 0, 7); x07 ^= rotl((x06 + x05) | 0, 9); x04 ^= rotl((x07 + x06) | 0, 13); x05 ^= rotl((x04 + x07) | 0, 18); x11 ^= rotl((x10 + x09) | 0, 7); x08 ^= rotl((x11 + x10) | 0, 9); x09 ^= rotl((x08 + x11) | 0, 13); x10 ^= rotl((x09 + x08) | 0, 18); x12 ^= rotl((x15 + x14) | 0, 7); x13 ^= rotl((x12 + x15) | 0, 9); x14 ^= rotl((x13 + x12) | 0, 13); x15 ^= rotl((x14 + x13) | 0, 18); } // Write output (salsa) output[oi++] = (y00 + x00) | 0; output[oi++] = (y01 + x01) | 0; output[oi++] = (y02 + x02) | 0; output[oi++] = (y03 + x03) | 0; output[oi++] = (y04 + x04) | 0; output[oi++] = (y05 + x05) | 0; output[oi++] = (y06 + x06) | 0; output[oi++] = (y07 + x07) | 0; output[oi++] = (y08 + x08) | 0; output[oi++] = (y09 + x09) | 0; output[oi++] = (y10 + x10) | 0; output[oi++] = (y11 + x11) | 0; output[oi++] = (y12 + x12) | 0; output[oi++] = (y13 + x13) | 0; output[oi++] = (y14 + x14) | 0; output[oi++] = (y15 + x15) | 0; } function block_mix(input, input_index, output, output_index, r) { let head = output_index; let tail = output_index + (r << 4); const t = ((r << 1) - 1) << 4; for (let i = 0; i < 16; i++) output[tail + i] = input[input_index + i + t]; for (let i = 0; i < r; i++) { fast_xor_salsa_hash(output, tail, input, input_index, output, head); if (i > 0) { tail += 16; } input_index += 16; fast_xor_salsa_hash(output, head, input, input_index, output, tail); head += 16; input_index += 16; } } return (k_byte, ikm, salt = new Uint8Array(0)) => { ikm = u8(ikm); salt = u8(salt); const B = kdf(BLOCK_SIZE * p, ikm, salt); const B32 = u32(B); const V32 = u32(new Uint8Array(BLOCK_SIZE * N)); const tmp = u32(new Uint8Array(BLOCK_SIZE)); for (let pi = 0; pi < p; pi++) { const PI = BLOCK_SIZE_32 * pi; V32.set(B32.subarray(PI, PI + BLOCK_SIZE_32), 0); let pos = 0; for (let i = 0; i < N_1; i++) { block_mix(V32, pos, V32, pos + BLOCK_SIZE_32, r); pos += BLOCK_SIZE_32; } block_mix(V32, N_1 * BLOCK_SIZE_32, B32, PI, r); for (let i = 0; i < N; i++) { const j = B32[PI + BLOCK_SIZE_32 - 16] % N; for (let k = 0; k < BLOCK_SIZE_32; k++) { tmp[k] = B32[PI + k] ^ V32[j * BLOCK_SIZE_32 + k]; } block_mix(tmp, 0, B32, PI, r); } } return kdf(k_byte, ikm, B); }; }