wxmp-rsa
Version:
兼容小程序环境的rsa加解密库,支持超长文本和中文字符
472 lines (471 loc) • 16.2 kB
JavaScript
// 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;