/* 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);