207 lines
7.6 KiB
JavaScript
207 lines
7.6 KiB
JavaScript
/* gmcrypto.js —— 国密 SM2 / SM3 / SM4 前端封装
|
||
* SM1 为国密硬件算法(不公开、依赖专用加密芯片),无法在浏览器纯软件实现,
|
||
* 故本页提供可在网页中直接使用的 SM2(非对称)/ SM3(摘要)/ SM4(对称分组)。
|
||
* 底层使用 sm-crypto@0.3.13,运行时按需双 CDN 懒加载(jsdelivr → unpkg)。
|
||
*/
|
||
(function (global) {
|
||
'use strict';
|
||
|
||
var VERSION = '0.3.13';
|
||
var LIBS = {
|
||
sm2: {
|
||
file: 'sm2.js', key: 'sm2',
|
||
urls: [
|
||
'https://cdn.jsdelivr.net/npm/sm-crypto@' + VERSION + '/dist/sm2.js',
|
||
'https://unpkg.com/sm-crypto@' + VERSION + '/dist/sm2.js'
|
||
]
|
||
},
|
||
sm3: {
|
||
file: 'sm3.js', key: 'sm3',
|
||
urls: [
|
||
'https://cdn.jsdelivr.net/npm/sm-crypto@' + VERSION + '/dist/sm3.js',
|
||
'https://unpkg.com/sm-crypto@' + VERSION + '/dist/sm3.js'
|
||
]
|
||
},
|
||
sm4: {
|
||
file: 'sm4.js', key: 'sm4',
|
||
urls: [
|
||
'https://cdn.jsdelivr.net/npm/sm-crypto@' + VERSION + '/dist/sm4.js',
|
||
'https://unpkg.com/sm-crypto@' + VERSION + '/dist/sm4.js'
|
||
]
|
||
}
|
||
};
|
||
var loadCache = {};
|
||
|
||
/** 加载指定算法库(返回 Promise<库对象|null>) */
|
||
function loadLib(name) {
|
||
var lib = LIBS[name];
|
||
if (!lib) return Promise.resolve(null);
|
||
if (global[lib.key]) return Promise.resolve(global[lib.key]);
|
||
if (loadCache[name]) return loadCache[name];
|
||
loadCache[name] = new Promise(function (resolve) {
|
||
var load = function (idx) {
|
||
if (global[lib.key]) { resolve(global[lib.key]); return; }
|
||
if (idx >= lib.urls.length) { resolve(null); return; }
|
||
var s = document.createElement('script');
|
||
s.src = lib.urls[idx];
|
||
s.onload = function () { resolve(global[lib.key] || null); };
|
||
s.onerror = function () { load(idx + 1); };
|
||
document.head.appendChild(s);
|
||
};
|
||
load(0);
|
||
});
|
||
return loadCache[name];
|
||
}
|
||
|
||
/* ---------- 基础编码工具 ---------- */
|
||
function utf8Bytes(str) {
|
||
if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(String(str));
|
||
var out = [], code, i;
|
||
for (i = 0; i < str.length; i++) {
|
||
code = str.charCodeAt(i);
|
||
if (code < 0x80) out.push(code);
|
||
else if (code < 0x800) out.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
|
||
else if (code < 0xd800 || code >= 0xe000) {
|
||
out.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
||
} else {
|
||
var ch = (code - 0xd800) * 0x400 + (str.charCodeAt(++i) - 0xdc00) + 0x10000;
|
||
out.push(0xf0 | (ch >> 18), 0x80 | ((ch >> 12) & 0x3f), 0x80 | ((ch >> 6) & 0x3f), 0x80 | (ch & 0x3f));
|
||
}
|
||
}
|
||
return new Uint8Array(out);
|
||
}
|
||
|
||
function hexToBytes(hexStr) {
|
||
var h = String(hexStr).replace(/[^0-9a-fA-F]/g, '');
|
||
if (h === '' || h.length % 2 !== 0) throw new Error('十六进制串长度需为偶数');
|
||
var out = new Uint8Array(h.length / 2), i;
|
||
for (i = 0; i < out.length; i++) {
|
||
out[i] = parseInt(h.substr(i * 2, 2), 16);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function bytesToHex(bytes) {
|
||
var s = '', i, t;
|
||
for (i = 0; i < bytes.length; i++) {
|
||
t = bytes[i].toString(16);
|
||
s += t.length < 2 ? '0' + t : t;
|
||
}
|
||
return s;
|
||
}
|
||
|
||
function bytesToB64(bytes) {
|
||
var s = '', i;
|
||
for (i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
||
return global.btoa(s);
|
||
}
|
||
|
||
function b64ToBytes(b64) {
|
||
var s = global.atob(String(b64).trim());
|
||
var out = new Uint8Array(s.length), i;
|
||
for (i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
|
||
return out;
|
||
}
|
||
|
||
function randomHex(bytesLen) {
|
||
var c = global.crypto || global.msCrypto;
|
||
var out = new Uint8Array(bytesLen), i;
|
||
if (c && c.getRandomValues) {
|
||
c.getRandomValues(out);
|
||
} else {
|
||
for (i = 0; i < bytesLen; i++) out[i] = Math.floor(Math.random() * 256);
|
||
}
|
||
return bytesToHex(out);
|
||
}
|
||
|
||
/* ---------- SM4 密钥 / IV 规范化 ---------- */
|
||
function sm4KeyBytes(keyStr) {
|
||
var s = String(keyStr || '').trim();
|
||
if (!s) throw new Error('请输入 SM4 密钥');
|
||
if (/^[0-9a-fA-F]{32}$/.test(s)) return hexToBytes(s);
|
||
var b = utf8Bytes(s);
|
||
if (b.length === 16) return b;
|
||
throw new Error('SM4 密钥需为 128 位(16 字节):可输入 32 位十六进制串,或恰好 16 个 ASCII 字符');
|
||
}
|
||
|
||
function sm4IvHex(ivStr) {
|
||
var s = String(ivStr || '').trim();
|
||
if (/^[0-9a-fA-F]{32}$/.test(s)) return s;
|
||
var b = utf8Bytes(s);
|
||
if (b.length === 16) return bytesToHex(b);
|
||
throw new Error('SM4 CBC 模式需要 16 字节 IV(32 位十六进制或恰好 16 个字符)');
|
||
}
|
||
|
||
/* ---------- 业务 Promise ---------- */
|
||
function asArray(bytes) { return Array.prototype.slice.call(bytes); }
|
||
|
||
function need(name) {
|
||
return loadLib(name).then(function (lib) {
|
||
if (!lib) throw new Error('国密 ' + name.toUpperCase() + ' 组件加载失败,请检查网络后重试');
|
||
return lib;
|
||
});
|
||
}
|
||
|
||
function sm3Digest(text, hmacKeyHex) {
|
||
return need('sm3').then(function (sm3) {
|
||
if (hmacKeyHex) return sm3(String(text), { key: hmacKeyHex });
|
||
return sm3(String(text));
|
||
});
|
||
}
|
||
|
||
function sm4Run(text, keyStr, mode, ivStr, isEncrypt) {
|
||
return need('sm4').then(function (sm4) {
|
||
var kb = asArray(sm4KeyBytes(keyStr));
|
||
var opt = {};
|
||
if (mode === 'cbc') opt = { mode: 'cbc', iv: sm4IvHex(ivStr) };
|
||
if (isEncrypt) {
|
||
var hex = sm4.encrypt(String(text), kb, opt);
|
||
return { ok: true, hex: hex };
|
||
}
|
||
var raw = String(text).trim().replace(/\s+/g, '');
|
||
var inArr;
|
||
if (/^[0-9a-fA-F]+$/.test(raw) && raw.length % 2 === 0) inArr = asArray(hexToBytes(raw));
|
||
else inArr = asArray(b64ToBytes(raw));
|
||
var out = sm4.decrypt(inArr, kb, opt);
|
||
return { ok: true, hex: '', text: out };
|
||
});
|
||
}
|
||
|
||
function sm2GenKey() {
|
||
return need('sm2').then(function (sm2) {
|
||
var kp = sm2.generateKeyPairHex();
|
||
return { publicKey: kp.publicKey, privateKey: kp.privateKey };
|
||
});
|
||
}
|
||
|
||
function sm2Encrypt(text, publicKey) {
|
||
return need('sm2').then(function (sm2) {
|
||
if (!String(publicKey || '').trim()) throw new Error('SM2 加密请填写对方公钥');
|
||
return sm2.doEncrypt(String(text), String(publicKey).trim(), 1);
|
||
});
|
||
}
|
||
|
||
function sm2Decrypt(cipherHex, privateKey) {
|
||
return need('sm2').then(function (sm2) {
|
||
if (!String(privateKey || '').trim()) throw new Error('SM2 解密请填写自己的私钥');
|
||
return sm2.doDecrypt(String(cipherHex).trim().replace(/\s+/g, ''), String(privateKey).trim(), 1);
|
||
});
|
||
}
|
||
|
||
/* ---------- 导出 ---------- */
|
||
global.GmCrypto = {
|
||
load: loadLib,
|
||
utf8Bytes: utf8Bytes,
|
||
hexToBytes: hexToBytes,
|
||
bytesToHex: bytesToHex,
|
||
bytesToB64: bytesToB64,
|
||
b64ToBytes: b64ToBytes,
|
||
randomHex: randomHex,
|
||
sm3: sm3Digest,
|
||
sm4: sm4Run,
|
||
sm2Key: sm2GenKey,
|
||
sm2Encrypt: sm2Encrypt,
|
||
sm2Decrypt: sm2Decrypt
|
||
};
|
||
})(window);
|