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 实现的密码学套件。目标是提供一个简单易用的密码学库。
121 lines (120 loc) • 3.6 kB
JavaScript
import { createCipher } from '../../core/cipher';
import { Counter, KitError, U8, resizeBuffer, rotateL32 } from '../../core/utils';
// * Functions
function QR(a, b, c, d) {
b ^= rotateL32(a + d, 7);
c ^= rotateL32(b + a, 9);
d ^= rotateL32(c + b, 13);
a ^= rotateL32(d + c, 18);
return [a, b, c, d];
}
function hash(x, rounds = 20) {
// to word
const X = new Uint32Array(x.buffer);
const W = X.slice(0);
// main loop
for (let i = 0; i < rounds; i += 2) {
// ODD Rounds
[W[0], W[4], W[8], W[12]] = QR(W[0], W[4], W[8], W[12]);
[W[5], W[9], W[13], W[1]] = QR(W[5], W[9], W[13], W[1]);
[W[10], W[14], W[2], W[6]] = QR(W[10], W[14], W[2], W[6]);
[W[15], W[3], W[7], W[11]] = QR(W[15], W[3], W[7], W[11]);
// EVEN Rounds
[W[0], W[1], W[2], W[3]] = QR(W[0], W[1], W[2], W[3]);
[W[5], W[6], W[7], W[4]] = QR(W[5], W[6], W[7], W[4]);
[W[10], W[11], W[8], W[9]] = QR(W[10], W[11], W[8], W[9]);
[W[15], W[12], W[13], W[14]] = QR(W[15], W[12], W[13], W[14]);
}
// mix
const Z = new U8(64);
const Z32 = new Uint32Array(Z.buffer);
for (let i = 0; i < 16; i++) {
Z32[i] = X[i] + W[i];
}
return Z;
}
function expand(K, iv) {
if (iv.byteLength !== 8) {
throw new KitError(`Salsa20 iv must be 8 byte`);
}
const S = new Counter(64);
const S32 = new Uint32Array(S.buffer);
const K32 = new Uint32Array(K.buffer);
const N32 = new Uint32Array(iv.buffer);
switch (K.byteLength) {
case 16: // use tau
S32[0] = 0x61707865;
S32[1] = K32[0];
S32[2] = K32[1];
S32[3] = K32[2];
S32[4] = K32[3];
S32[5] = 0x3120646E;
S32[6] = N32[0];
S32[7] = N32[1];
S32[10] = 0x79622D36;
S32[11] = K32[0];
S32[12] = K32[1];
S32[13] = K32[2];
S32[14] = K32[3];
S32[15] = 0x6B206574;
break;
case 32: // use sigma
S32[0] = 0x61707865;
S32[1] = K32[0];
S32[2] = K32[1];
S32[3] = K32[2];
S32[4] = K32[3];
S32[5] = 0x3320646E;
S32[6] = N32[0];
S32[7] = N32[1];
S32[10] = 0x79622D32;
S32[11] = K32[4];
S32[12] = K32[5];
S32[13] = K32[6];
S32[14] = K32[7];
S32[15] = 0x6B206574;
break;
default:
throw new KitError(`Salsa20 key must be 16 or 32 byte`);
}
return S;
}
// * Salsa20 Algorithm
function _salsa20(key, iv) {
/** Counter Block */
const E = expand(key, iv);
/** Presudo Random Byte Stream */
let S = hash(E);
let current = 1;
const cipher = (M) => {
const R = U8.from(M);
const BLOCK_TOTAL = (R.length >> 6) + 1;
if (current > BLOCK_TOTAL) {
return R.map((byte, i) => byte ^ S[i]);
}
// Squeeze
S = resizeBuffer(S, BLOCK_TOTAL << 6);
while (BLOCK_TOTAL > current) {
E.inc(32, 8, true);
S.set(hash(E), current << 6);
current++;
}
return R.map((byte, i) => byte ^ S[i]);
};
return {
encrypt: (M) => cipher(M),
decrypt: (C) => cipher(C),
};
}
/**
* Salsa20 流密码 / Stream Cipher
*/
export const salsa20 = createCipher(_salsa20, {
ALGORITHM: 'Salsa20',
KEY_SIZE: 32,
MIN_KEY_SIZE: 16,
MAX_KEY_SIZE: 32,
IV_SIZE: 8,
MIN_IV_SIZE: 8,
MAX_IV_SIZE: 8,
});