NewHome/assets/js/gmcrypto.js

207 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 字节 IV32 位十六进制或恰好 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);