UNPKG

wxmp-rsa

Version:

兼容小程序环境的rsa加解密库,支持超长文本和中文字符

472 lines (471 loc) 16.2 kB
// Depends on jsbn.js and rng.js // Version 1.1: support utf-8 encoding in pkcs1pad2 // convert a (hex) string to a bignum object import { BigInteger, nbi, parseBigInt } from "./jsbn"; import { SecureRandom } from "./rng"; // function linebrk(s,n) { // var ret = ""; // var i = 0; // while(i + n < s.length) { // ret += s.substring(i,i+n) + "\n"; // i += n; // } // return ret + s.substring(i,s.length); // } // function byte2Hex(b) { // if(b < 0x10) // return "0" + b.toString(16); // else // return b.toString(16); // } function pkcs1pad1(s, n) { if (n < s.length + 22) { console.error("Message too long for RSA"); return null; } var len = n - s.length - 6; var filler = ""; for (var f = 0; f < len; f += 2) { filler += "ff"; } var m = "0001" + filler + "00" + s; return parseBigInt(m, 16); } // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint function pkcs1pad2(s, n) { if (n < s.length + 11) { // TODO: fix for utf-8 console.error("Message too long for RSA"); return null; } var ba = []; var i = s.length - 1; while (i >= 0 && n > 0) { var c = s.charCodeAt(i--); if (c < 128) { // encode using utf-8 ba[--n] = c; } else if ((c > 127) && (c < 2048)) { ba[--n] = (c & 63) | 128; ba[--n] = (c >> 6) | 192; } else { ba[--n] = (c & 63) | 128; ba[--n] = ((c >> 6) & 63) | 128; ba[--n] = (c >> 12) | 224; } } ba[--n] = 0; var rng = new SecureRandom(); var x = []; while (n > 2) { // random non-zero pad x[0] = 0; while (x[0] == 0) { rng.nextBytes(x); } ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new BigInteger(ba); } // "empty" RSA key constructor var RSAKey = /** @class */ (function () { function RSAKey() { this.n = null; this.e = 0; this.d = null; this.p = null; this.q = null; this.dmp1 = null; this.dmq1 = null; this.coeff = null; } //#region PROTECTED // protected // RSAKey.prototype.doPublic = RSADoPublic; // Perform raw public operation on "x": return x^e (mod n) RSAKey.prototype.doPublic = function (x) { return x.modPowInt(this.e, this.n); }; // RSAKey.prototype.doPrivate = RSADoPrivate; // Perform raw private operation on "x": return x^d (mod n) RSAKey.prototype.doPrivate = function (x) { if (this.p == null || this.q == null) { return x.modPow(this.d, this.n); } // TODO: re-calculate any missing CRT params var xp = x.mod(this.p).modPow(this.dmp1, this.p); var xq = x.mod(this.q).modPow(this.dmq1, this.q); while (xp.compareTo(xq) < 0) { xp = xp.add(this.p); } return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); }; //#endregion PROTECTED //#region PUBLIC // RSAKey.prototype.setPublic = RSASetPublic; // Set the public key fields N and e from hex strings RSAKey.prototype.setPublic = function (N, E) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); } else { console.error("Invalid RSA public key"); } }; // RSAKey.prototype.encrypt = RSAEncrypt; // Return the PKCS#1 RSA encryption of "text" as an even-length hex string RSAKey.prototype.encrypt = function (text) { var maxLength = (this.n.bitLength() + 7) >> 3; var m = pkcs1pad2(text, maxLength); if (m == null) { return null; } var c = this.doPublic(m); if (c == null) { return null; } var h = c.toString(16); var length = h.length; // fix zero before result for (var i = 0; i < maxLength * 2 - length; i++) { h = "0" + h; } return h; }; // RSAKey.prototype.setPrivate = RSASetPrivate; // Set the private key fields N, e, and d from hex strings RSAKey.prototype.setPrivate = function (N, E, D) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = parseBigInt(D, 16); } else { console.error("Invalid RSA private key"); } }; // RSAKey.prototype.setPrivateEx = RSASetPrivateEx; // Set the private key fields N, e, d and CRT params from hex strings RSAKey.prototype.setPrivateEx = function (N, E, D, P, Q, DP, DQ, C) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = parseBigInt(D, 16); this.p = parseBigInt(P, 16); this.q = parseBigInt(Q, 16); this.dmp1 = parseBigInt(DP, 16); this.dmq1 = parseBigInt(DQ, 16); this.coeff = parseBigInt(C, 16); } else { console.error("Invalid RSA private key"); } }; // RSAKey.prototype.generate = RSAGenerate; // Generate a new random private key B bits long, using public expt E RSAKey.prototype.generate = function (B, E) { var rng = new SecureRandom(); var qs = B >> 1; this.e = parseInt(E, 16); var ee = new BigInteger(E, 16); for (;;) { for (;;) { this.p = new BigInteger(B - qs, 1, rng); if (this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) { break; } } for (;;) { this.q = new BigInteger(qs, 1, rng); if (this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) { break; } } if (this.p.compareTo(this.q) <= 0) { var t = this.p; this.p = this.q; this.q = t; } var p1 = this.p.subtract(BigInteger.ONE); var q1 = this.q.subtract(BigInteger.ONE); var phi = p1.multiply(q1); if (phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { this.n = this.p.multiply(this.q); this.d = ee.modInverse(phi); this.dmp1 = this.d.mod(p1); this.dmq1 = this.d.mod(q1); this.coeff = this.q.modInverse(this.p); break; } } }; // RSAKey.prototype.decrypt = RSADecrypt; // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is an even-length hex string and the output is a plain string. RSAKey.prototype.decrypt = function (ctext) { var c = parseBigInt(ctext, 16); var m = this.doPrivate(c); if (m == null) { return null; } return pkcs1unpad2(m, (this.n.bitLength() + 7) >> 3); }; // Generate a new random private key B bits long, using public expt E RSAKey.prototype.generateAsync = function (B, E, callback) { var rng = new SecureRandom(); var qs = B >> 1; this.e = parseInt(E, 16); var ee = new BigInteger(E, 16); var rsa = this; // These functions have non-descript names because they were originally for(;;) loops. // I don't know about cryptography to give them better names than loop1-4. var loop1 = function () { var loop4 = function () { if (rsa.p.compareTo(rsa.q) <= 0) { var t = rsa.p; rsa.p = rsa.q; rsa.q = t; } var p1 = rsa.p.subtract(BigInteger.ONE); var q1 = rsa.q.subtract(BigInteger.ONE); var phi = p1.multiply(q1); if (phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { rsa.n = rsa.p.multiply(rsa.q); rsa.d = ee.modInverse(phi); rsa.dmp1 = rsa.d.mod(p1); rsa.dmq1 = rsa.d.mod(q1); rsa.coeff = rsa.q.modInverse(rsa.p); setTimeout(function () { callback(); }, 0); // escape } else { setTimeout(loop1, 0); } }; var loop3 = function () { rsa.q = nbi(); rsa.q.fromNumberAsync(qs, 1, rng, function () { rsa.q.subtract(BigInteger.ONE).gcda(ee, function (r) { if (r.compareTo(BigInteger.ONE) == 0 && rsa.q.isProbablePrime(10)) { setTimeout(loop4, 0); } else { setTimeout(loop3, 0); } }); }); }; var loop2 = function () { rsa.p = nbi(); rsa.p.fromNumberAsync(B - qs, 1, rng, function () { rsa.p.subtract(BigInteger.ONE).gcda(ee, function (r) { if (r.compareTo(BigInteger.ONE) == 0 && rsa.p.isProbablePrime(10)) { setTimeout(loop3, 0); } else { setTimeout(loop2, 0); } }); }); }; setTimeout(loop2, 0); }; setTimeout(loop1, 0); }; RSAKey.prototype.sign = function (text, digestMethod, digestName) { var header = getDigestHeader(digestName); var digest = header + digestMethod(text).toString(); var m = pkcs1pad1(digest, this.n.bitLength() / 4); if (m == null) { return null; } var c = this.doPrivate(m); if (c == null) { return null; } var h = c.toString(16); if ((h.length & 1) == 0) { return h; } else { return "0" + h; } }; RSAKey.prototype.verify = function (text, signature, digestMethod) { var c = parseBigInt(signature, 16); var m = this.doPublic(c); if (m == null) { return null; } var unpadded = m.toString(16).replace(/^1f+00/, ""); var digest = removeDigestHeader(unpadded); return digest == digestMethod(text).toString(); }; RSAKey.prototype.encryptLong = function (text) { var _this = this; var res = ''; var maxLen = ((this.n.bitLength() + 7) >> 3) - 11; var textArr = this.setSplitChn(text, maxLen); textArr.forEach(function (v) { res += _this.encrypt(v); }); return res; }; RSAKey.prototype.decryptLong = function (ctext) { var res = ''; var maxLen = (this.n.bitLength() + 7) >> 3; var splitMaxLen = maxLen * 2; if (ctext.length > splitMaxLen) { var ctextArr = ctext.match(new RegExp('.{1,' + splitMaxLen + '}', 'g')) || []; var mArr = []; for (var i = 0; i < ctextArr.length; i++) { var c = parseBigInt(ctextArr[i], 16); var m = this.doPrivate(c); if (m == null) { return null; } mArr.push(m); } res = pkcs1unpad2Long(mArr, maxLen); } else { res = this.decrypt(ctext); } return res; }; RSAKey.prototype.setSplitChn = function (str, maxLen, res) { if (res === void 0) { res = []; } var arr = str.split(''); var len = 0; for (var i = 0; i < arr.length; i++) { var charCode = arr[i].charCodeAt(0); if (charCode <= 0x007f) { len += 1; } else if (charCode <= 0x07ff) { len += 2; } else if (charCode <= 0xffff) { len += 3; } else { len += 4; } if (len > maxLen) { var currentStr = str.substring(0, i); res.push(currentStr); return this.setSplitChn(str.substring(i), maxLen, res); } } res.push(str); return res; }; return RSAKey; }()); export { RSAKey }; // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext function pkcs1unpad2(d, n) { var b = d.toByteArray(); var i = 0; while (i < b.length && b[i] == 0) { ++i; } if (b.length - i != n - 1 || b[i] != 2) { return null; } ++i; while (b[i] != 0) { if (++i >= b.length) { return null; } } var ret = ""; while (++i < b.length) { var c = b[i] & 255; if (c < 128) { // utf-8 decode ret += String.fromCharCode(c); } else if ((c > 191) && (c < 224)) { ret += String.fromCharCode(((c & 31) << 6) | (b[i + 1] & 63)); ++i; } else { ret += String.fromCharCode(((c & 15) << 12) | ((b[i + 1] & 63) << 6) | (b[i + 2] & 63)); i += 2; } } return ret; } function pkcs1unpad2Long(dArr, n) { var bArr = []; for (var j = 0; j < dArr.length; j++) { var d = dArr[j]; var b_1 = d.toByteArray(); var i_1 = 0; while (i_1 < b_1.length && b_1[i_1] == 0) { ++i_1; } if (b_1.length - i_1 != n - 1 || b_1[i_1] != 2) { return null; } ++i_1; while (b_1[i_1] != 0) { if (++i_1 >= b_1.length) { return null; } } bArr = bArr.concat(b_1.slice(i_1 + 1)); } var b = bArr; var i = -1; var ret = ""; while (++i < b.length) { var c = b[i] & 255; if (c < 128) { // utf-8 decode ret += String.fromCharCode(c); } else if ((c > 191) && (c < 224)) { ret += String.fromCharCode(((c & 31) << 6) | (b[i + 1] & 63)); ++i; } else { ret += String.fromCharCode(((c & 15) << 12) | ((b[i + 1] & 63) << 6) | (b[i + 2] & 63)); i += 2; } } return ret; } // https://tools.ietf.org/html/rfc3447#page-43 var DIGEST_HEADERS = { md2: "3020300c06082a864886f70d020205000410", md5: "3020300c06082a864886f70d020505000410", sha1: "3021300906052b0e03021a05000414", sha224: "302d300d06096086480165030402040500041c", sha256: "3031300d060960864801650304020105000420", sha384: "3041300d060960864801650304020205000430", sha512: "3051300d060960864801650304020305000440", ripemd160: "3021300906052b2403020105000414" }; function getDigestHeader(name) { return DIGEST_HEADERS[name] || ""; } function removeDigestHeader(str) { for (var name_1 in DIGEST_HEADERS) { if (DIGEST_HEADERS.hasOwnProperty(name_1)) { var header = DIGEST_HEADERS[name_1]; var len = header.length; if (str.substr(0, len) == header) { return str.substr(len); } } } return str; } // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string // function RSAEncryptB64(text) { // var h = this.encrypt(text); // if(h) return hex2b64(h); else return null; // } // public // RSAKey.prototype.encrypt_b64 = RSAEncryptB64;