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 实现的密码学套件。目标是提供一个简单易用的密码学库。

479 lines (478 loc) 16.4 kB
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, });