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 实现的密码学套件。目标是提供一个简单易用的密码学库。
479 lines (478 loc) • 16.4 kB
JavaScript
import { Counter, KitError, U8, joinBuffer, wrap } from './utils';
export function createCipher(algorithm, description) {
return wrap((key, iv) => wrap(algorithm(key, iv), description), description);
}
export function createPadding(doPad, unPad, description) {
return wrap((M, BLOCK_SIZE) => (typeof BLOCK_SIZE === 'number'
? doPad(M, BLOCK_SIZE)
: unPad(M)), description);
}
// * 填充方案
/** PKCS7 填充方案 / Padding Scheme */
export const PKCS7_PAD = createPadding((M, BLOCK_SIZE) => {
const pad = BLOCK_SIZE - M.length % BLOCK_SIZE;
return joinBuffer(M, new Uint8Array(pad).fill(pad));
}, (P) => {
const pad = P[P.length - 1];
return new U8(P.slice(0, P.length - pad));
}, { ALGORITHM: 'PKCS#7' });
/** ISO/IEC 7816 填充方案 / Padding Scheme */
export const ISO7816_PAD = createPadding((M, BLOCK_SIZE) => {
const BLOCK_TOTAL = Math.ceil((M.length + 1) / BLOCK_SIZE);
const P = new U8(BLOCK_TOTAL * BLOCK_SIZE);
P.set(M);
P[M.length] = 0x80;
return P;
}, (P) => {
let i = P.length - 1;
while (P[i] === 0x80) {
i = i - 1;
if (i < 0) {
console.warn('This message may not be ISO/IEC 7816-4 padded');
return new U8();
}
}
return new U8(P.slice(0, i + 1));
}, { ALGORITHM: 'ISO/IEC 7816-4' });
/** ANSI X9.23 填充方案 / Padding Scheme */
export const X923_PAD = createPadding((M, BLOCK_SIZE) => {
const BLOCK_TOTAL = Math.ceil((M.length + 1) / BLOCK_SIZE);
const P = new U8(BLOCK_TOTAL * BLOCK_SIZE);
P.set(M);
P[P.length - 1] = P.length - M.length;
return P;
}, (P) => {
const pad = P[P.length - 1];
return new U8(P.slice(0, P.length - pad));
}, { ALGORITHM: 'ANSI X9.23' });
/** Zero 零填充方案 / Padding Scheme */
export const ZERO_PAD = createPadding((M, BLOCK_SIZE) => {
const pad = BLOCK_SIZE - M.length % BLOCK_SIZE;
return joinBuffer(M, new Uint8Array(pad));
}, (P) => {
let i = P.length - 1;
while (P[i] === 0) {
i = i - 1;
if (i < 0) {
return new U8();
}
}
return new U8(P.slice(0, i + 1));
}, { ALGORITHM: 'Zero Padding' });
/** 无填充 / No Padding */
export const NO_PAD = createPadding((M) => new U8(M.slice(0)), (P) => new U8(P.slice(0)), { ALGORITHM: 'No Padding' });
/** 电子密码本模式 / Electronic Code Book Mode */
export const ecb = wrap((cipher, padding = PKCS7_PAD) => {
const info = {
ALGORITHM: `ECB-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: 0,
MIN_IV_SIZE: 0,
MAX_IV_SIZE: 0,
};
const suite = (K) => {
const { BLOCK_SIZE } = cipher;
const c = cipher(K);
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
const C = new U8(P.length);
for (let i = 0; i < P.length;) {
const offset = i;
const B = P.subarray(i, i += BLOCK_SIZE);
C.set(c.encrypt(B), offset);
}
return C;
};
const decrypt = (C) => {
if (C.length % BLOCK_SIZE !== 0) {
throw new KitError('Decryption error');
}
const P = new U8(C.length);
for (let i = 0; i < C.length;) {
const offset = i;
const B = C.subarray(i, i += BLOCK_SIZE);
P.set(c.decrypt(B), offset);
}
return padding(P);
};
return wrap({ encrypt, decrypt }, info);
};
return wrap(suite, info);
}, { ALGORITHM: 'ECB' });
/** 密码块链接模式 / Cipher Block Chaining Mode */
export const cbc = wrap((cipher, padding = PKCS7_PAD) => {
const info = {
ALGORITHM: `CBC-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: cipher.BLOCK_SIZE,
MIN_IV_SIZE: cipher.BLOCK_SIZE,
MAX_IV_SIZE: cipher.BLOCK_SIZE,
};
const suite = (K, iv) => {
// iv 检查
const { BLOCK_SIZE } = cipher;
if (iv.length !== BLOCK_SIZE) {
throw new KitError(`${info.ALGORITHM} iv must be ${BLOCK_SIZE} byte`);
}
const c = cipher(K);
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
const C = new U8(P.length);
let prev = iv.slice(0);
for (let i = 0; i < P.length;) {
const offset = i;
const B = P.subarray(i, i += BLOCK_SIZE);
prev.forEach((_, i) => prev[i] ^= B[i]);
prev = c.encrypt(prev);
C.set(prev, offset);
}
return C;
};
const decrypt = (C) => {
if (C.length % BLOCK_SIZE !== 0) {
throw new KitError('Decryption error');
}
const P = new U8(C.length);
let prev = iv;
for (let i = 0; i < C.length;) {
const offset = i;
const B = C.slice(i, i += BLOCK_SIZE);
c.decrypt(B).forEach((_, i) => prev[i] ^= _);
P.set(prev, offset);
prev = B;
}
return padding(P);
};
return wrap({ encrypt, decrypt }, info);
};
return wrap(suite, info);
}, { ALGORITHM: 'CBC' });
/** 传播密码块链接模式 / Propagating Cipher Block Chaining Mode */
export const pcbc = wrap((cipher, padding = PKCS7_PAD) => {
const info = {
ALGORITHM: `PCBC-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: cipher.BLOCK_SIZE,
MIN_IV_SIZE: cipher.BLOCK_SIZE,
MAX_IV_SIZE: cipher.BLOCK_SIZE,
};
const suite = (K, IV) => {
// iv 检查
const { BLOCK_SIZE } = cipher;
if (IV.length !== BLOCK_SIZE) {
throw new KitError(`${info.ALGORITHM} iv must be ${BLOCK_SIZE} byte`);
}
const c = cipher(K);
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
const C = new U8(P.length);
const prev = IV.slice(0);
for (let i = 0; i < P.length;) {
const offset = i;
const B = P.subarray(i, i += BLOCK_SIZE);
prev.forEach((_, i) => prev[i] ^= B[i]);
const _C = c.encrypt(prev);
C.set(_C, offset);
prev.forEach((_, i) => prev[i] = _C[i] ^ B[i]);
}
return C;
};
const decrypt = (C) => {
if (C.length % BLOCK_SIZE !== 0) {
throw new KitError('Decryption error');
}
const P = new U8(C.length);
const prev = IV.slice(0);
for (let i = 0; i < C.length;) {
const offset = i;
const B = C.slice(i, i += BLOCK_SIZE);
const _P = c.decrypt(B);
_P.forEach((_, i) => _P[i] ^= prev[i]);
P.set(_P, offset);
B.forEach((_, i) => prev[i] = B[i] ^ _P[i]);
}
return padding(P);
};
return wrap({ encrypt, decrypt }, info);
};
return wrap(suite, info);
}, { ALGORITHM: 'PCBC' });
/** 密码反馈模式 / Cipher Feedback Mode */
export const cfb = wrap((cipher, padding = PKCS7_PAD) => {
const info = {
ALGORITHM: `CFB-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: cipher.BLOCK_SIZE,
MIN_IV_SIZE: cipher.BLOCK_SIZE,
MAX_IV_SIZE: cipher.BLOCK_SIZE,
};
const suite = (K, iv) => {
// iv 检查
const { BLOCK_SIZE } = cipher;
if (iv.length !== BLOCK_SIZE) {
throw new KitError(`${info.ALGORITHM} iv must be ${BLOCK_SIZE} byte`);
}
const c = cipher(K);
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
const C = new U8(P.length);
let prev = iv;
for (let i = 0; i < P.length;) {
const offset = i;
const B = P.subarray(i, i += BLOCK_SIZE);
prev = c.encrypt(prev);
prev.forEach((_, i) => prev[i] ^= B[i]);
C.set(prev.subarray(0, B.length), offset);
}
return C;
};
const decrypt = (C) => {
const P = new U8(C.length);
let prev = iv;
for (let i = 0; i < C.length;) {
const offset = i;
const B = C.subarray(i, i += BLOCK_SIZE);
prev = c.encrypt(prev);
B.forEach((_, i) => prev[i] ^= B[i]);
P.set(prev.subarray(0, B.length), offset);
prev = B;
}
return padding(P);
};
return wrap({ encrypt, decrypt }, info);
};
return wrap(suite, info);
}, { ALGORITHM: 'CFB' });
/** 输出反馈模式 / Output Feedback Mode */
export const ofb = wrap((cipher, padding = PKCS7_PAD) => {
const info = {
ALGORITHM: `OFB-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: cipher.BLOCK_SIZE,
MIN_IV_SIZE: cipher.BLOCK_SIZE,
MAX_IV_SIZE: cipher.BLOCK_SIZE,
};
const suite = (K, iv) => {
// iv 检查
const { BLOCK_SIZE } = cipher;
if (iv.length !== BLOCK_SIZE) {
throw new KitError(`${info.ALGORITHM} iv must be ${BLOCK_SIZE} byte`);
}
const c = cipher(K);
let prev = c.encrypt(iv);
let S = prev;
let SByte = BLOCK_SIZE;
const squeeze = (TByte) => {
if (SByte > TByte) {
return S;
}
const buffer = [S];
while (SByte < TByte) {
prev = c.encrypt(prev);
buffer.push(prev);
SByte += BLOCK_SIZE;
}
S = joinBuffer(...buffer);
return S;
};
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
S = squeeze(P.length);
return P.map((_, i) => _ ^ S[i]);
};
const decrypt = (C) => {
S = squeeze(C.length);
return padding(C.map((_, i) => _ ^ S[i]));
};
return wrap({ encrypt, decrypt }, info);
};
return wrap(suite, info);
}, { ALGORITHM: 'OFB' });
/** 计数器模式 / Counter Mode */
export const ctr = wrap((cipher, padding = PKCS7_PAD) => {
const info = {
ALGORITHM: `CTR-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: cipher.BLOCK_SIZE,
MIN_IV_SIZE: cipher.BLOCK_SIZE,
MAX_IV_SIZE: cipher.BLOCK_SIZE,
};
const suite = (K, iv) => {
// iv 检查
const { BLOCK_SIZE } = cipher;
if (iv.length !== BLOCK_SIZE) {
throw new KitError(`{info.ALGORITHM} iv must be ${BLOCK_SIZE} byte`);
}
const c = cipher(K);
const counter = new Counter(iv.slice());
let S = new U8();
let SByte = 0;
const squeeze = (TByte) => {
if (SByte > TByte) {
return S;
}
const buffer = [S];
while (SByte < TByte) {
buffer.push(c.encrypt(counter));
counter.inc();
SByte += BLOCK_SIZE;
}
S = joinBuffer(...buffer);
return S;
};
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
S = squeeze(P.length);
return P.map((_, i) => _ ^ S[i]);
};
const decrypt = (C) => {
S = squeeze(C.length);
return padding(C.map((_, i) => _ ^ S[i]));
};
return wrap({ encrypt, decrypt }, info);
};
return wrap(suite, info);
}, { ALGORITHM: 'CTR' });
function GF128Mul(X, Y) {
// R: E1000000000000000000000000000000
const RH = 0xe1n << 56n;
const YView = new DataView(Y.buffer);
let VH = YView.getBigUint64(0, false);
let VL = YView.getBigUint64(8, false);
let ZH = 0n;
let ZL = 0n;
for (let i = 0; i < 16; i++) {
const x = X[i];
for (let j = 7; j >= 0; j--) {
if ((x >> j) & 1) {
ZH ^= VH;
ZL ^= VL;
}
const carry = VL & 1n;
VL = (VH << 63n) | (VL >> 1n);
VL = VL & 0xffffffffffffffffn;
VH = (VH >> 1n);
if (carry) {
VH ^= RH;
}
}
}
const Z = new U8(16);
const ZView = new DataView(Z.buffer);
ZView.setBigUint64(0, ZH, false);
ZView.setBigUint64(8, ZL, false);
return Z;
}
function GHASH(H, A, C) {
const A_BLOCK_TOTAL = Math.ceil(A.length / 16);
const C_BLOCK_TOTAL = Math.ceil(C.length / 16);
const D = new Uint8Array((A_BLOCK_TOTAL + C_BLOCK_TOTAL + 1) * 16);
const view = new DataView(D.buffer);
D.set(A);
D.set(C, A_BLOCK_TOTAL * 16);
view.setBigUint64(D.length - 16, BigInt(A.length << 3), false);
view.setBigUint64(D.length - 8, BigInt(C.length << 3), false);
let X = new U8(16);
for (let i = 0; i < D.length; i += 16) {
const B = D.subarray(i, i + 16);
X.forEach((_, i) => X[i] ^= B[i]);
X = GF128Mul(H, X);
}
return X;
}
/** 伽罗瓦计数器模式 / Galois Counter Mode */
export const gcm = wrap((cipher, padding = PKCS7_PAD, tag_size = 16) => {
const { BLOCK_SIZE } = cipher;
if (BLOCK_SIZE !== 16) {
throw new KitError('GCM cipher block must be 128 bit');
}
const info = {
ALGORITHM: `GCM-${cipher.ALGORITHM}`,
PADDING: padding,
BLOCK_SIZE: cipher.BLOCK_SIZE,
KEY_SIZE: cipher.KEY_SIZE,
MIN_KEY_SIZE: cipher.MIN_KEY_SIZE,
MAX_KEY_SIZE: cipher.MAX_KEY_SIZE,
IV_SIZE: 12,
MIN_IV_SIZE: 0,
MAX_IV_SIZE: Infinity,
AUTH_TAG_SIZE: tag_size,
};
const suite = (K, iv) => {
const c = cipher(K);
const H = c.encrypt(new Uint8Array(BLOCK_SIZE));
let IV = new Counter(16);
if (iv.length === 12) {
IV.set(iv);
IV[15] = 1;
}
else {
IV = new Counter(GHASH(H, new Uint8Array(), iv.slice(0)));
}
let S = c.encrypt(IV);
let SByte = 0;
const squeeze = (TByte) => {
if (SByte > TByte) {
return S;
}
const buffer = [S];
while (SByte < TByte) {
IV.inc();
buffer.push(c.encrypt(IV));
SByte += BLOCK_SIZE;
}
S = joinBuffer(...buffer);
return S;
};
const encrypt = (M) => {
const P = padding(M, BLOCK_SIZE);
S = squeeze(P.length);
return P.map((_, i) => _ ^ S[i + BLOCK_SIZE]);
};
const decrypt = (C) => {
S = squeeze(C.length);
return padding(C.map((_, i) => _ ^ S[i + BLOCK_SIZE]));
};
const sign = (C, A = new Uint8Array()) => {
const T = GHASH(H, A, C);
T.forEach((_, i) => T[i] ^= S[i]);
return T.slice(0, tag_size);
};
const verify = (T, C, A) => {
if (T.length !== tag_size) {
return false;
}
const T1 = sign(C, A);
return T.every((_, i) => _ === T1[i]);
};
return wrap({ encrypt, decrypt, sign, verify }, info);
};
return wrap(suite, info);
}, {
ALGORITHM: 'GCM',
IV_SIZE: 12,
});