You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
5.9 KiB
JavaScript
229 lines
5.9 KiB
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
|
|
|
|
const { BigInteger } = require('./jsbn');
|
|
const { SecureRandom } = require('./rng');
|
|
const {KJUR} = require('./crypto-1.1.js');
|
|
|
|
function parseBigInt(str, r) {
|
|
return new BigInteger(str, r);
|
|
}
|
|
|
|
function linebrk(s, n) {
|
|
let ret = '';
|
|
let 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)}`;
|
|
return b.toString(16);
|
|
}
|
|
|
|
/**
|
|
* convert a raw string including non printable characters to hexadecimal encoded string.<br/>
|
|
* @name rstrtohex
|
|
* @function
|
|
* @param {String} s raw string
|
|
* @return {String} hexadecimal encoded string
|
|
* @since 1.1.2
|
|
* @example
|
|
* rstrtohex("a\x00a") → "610061"
|
|
*/
|
|
function rstrtohex(s) {
|
|
var result = "";
|
|
for (var i = 0; i < s.length; i++) {
|
|
result += ("0" + s.charCodeAt(i).toString(16)).slice(-2);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function hextorstr(sHex) {
|
|
var s = "";
|
|
for (var i = 0; i < sHex.length - 1; i += 2) {
|
|
s += String.fromCharCode(parseInt(sHex.substr(i, 2), 16));
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// 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
|
|
alert('Message too long for RSA');
|
|
return null;
|
|
}
|
|
const ba = new Array();
|
|
let i = s.length - 1;
|
|
while (i >= 0 && n > 0) {
|
|
const 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;
|
|
const rng = new SecureRandom();
|
|
const x = new Array();
|
|
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);
|
|
}
|
|
|
|
|
|
// PKCS#1 (OAEP) mask generation function
|
|
function oaep_mgf1_arr(seed, len, hash) {
|
|
var mask = '', i = 0;
|
|
|
|
while (mask.length < len) {
|
|
mask += hash(String.fromCharCode.apply(String, seed.concat([
|
|
(i & 0xff000000) >> 24,
|
|
(i & 0x00ff0000) >> 16,
|
|
(i & 0x0000ff00) >> 8,
|
|
i & 0x000000ff])));
|
|
i += 1;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
/**
|
|
* PKCS#1 (OAEP) pad input string s to n bytes, and return a bigint
|
|
* @name oaep_pad
|
|
* @param s raw string of message
|
|
* @param n key length of RSA key
|
|
* @param hash JavaScript function to calculate raw hash value from raw string or algorithm name (ex. "SHA1")
|
|
* @param hashLen byte length of resulted hash value (ex. 20 for SHA1)
|
|
* @return {BigInteger} BigInteger object of resulted PKCS#1 OAEP padded message
|
|
* @description
|
|
* This function calculates OAEP padded message from original message.<br/>
|
|
* NOTE: Since jsrsasign 6.2.0, 'hash' argument can accept an algorithm name such as "sha1".
|
|
* @example
|
|
* oaep_pad("aaa", 128) → big integer object // SHA-1 by default
|
|
* oaep_pad("aaa", 128, function(s) {...}, 20);
|
|
* oaep_pad("aaa", 128, "sha1");
|
|
*/
|
|
function oaep_pad(s, n, hash, hashLen) {
|
|
var MD = KJUR.crypto.MessageDigest;
|
|
var Util = KJUR.crypto.Util;
|
|
var algName = null;
|
|
|
|
if (!hash) hash = "sha1";
|
|
|
|
if (typeof hash === "string") {
|
|
algName = MD.getCanonicalAlgName(hash);
|
|
hashLen = MD.getHashLength(algName);
|
|
hash = function (s) {
|
|
return hextorstr(Util.hashHex(rstrtohex(s), algName));
|
|
};
|
|
}
|
|
|
|
if (s.length + 2 * hashLen + 2 > n) {
|
|
throw "Message too long for RSA";
|
|
}
|
|
|
|
var PS = '', i;
|
|
|
|
for (i = 0; i < n - s.length - 2 * hashLen - 2; i += 1) {
|
|
PS += '\x00';
|
|
}
|
|
|
|
var DB = hash('') + PS + '\x01' + s;
|
|
var seed = new Array(hashLen);
|
|
new SecureRandom().nextBytes(seed);
|
|
|
|
var dbMask = oaep_mgf1_arr(seed, DB.length, hash);
|
|
var maskedDB = [];
|
|
|
|
for (i = 0; i < DB.length; i += 1) {
|
|
maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i);
|
|
}
|
|
|
|
var seedMask = oaep_mgf1_arr(maskedDB, seed.length, hash);
|
|
var maskedSeed = [0];
|
|
|
|
for (i = 0; i < seed.length; i += 1) {
|
|
maskedSeed[i + 1] = seed[i] ^ seedMask.charCodeAt(i);
|
|
}
|
|
|
|
return new BigInteger(maskedSeed.concat(maskedDB));
|
|
}
|
|
|
|
// "empty" RSA key constructor
|
|
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;
|
|
}
|
|
|
|
// Set the public key fields N and e from hex strings
|
|
function RSASetPublic(N, E) {
|
|
if (N != null && E != null && N.length > 0 && E.length > 0) {
|
|
this.n = parseBigInt(N, 16);
|
|
this.e = parseInt(E, 16);
|
|
} else alert('Invalid RSA public key');
|
|
}
|
|
|
|
// Perform raw public operation on "x": return x^e (mod n)
|
|
function RSADoPublic(x) {
|
|
return x.modPowInt(this.e, this.n);
|
|
}
|
|
|
|
// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
|
|
function RSAEncrypt(text) {
|
|
const m = pkcs1pad2(text, (this.n.bitLength() + 7) >> 3);
|
|
if (m == null) return null;
|
|
const c = this.doPublic(m);
|
|
if (c == null) return null;
|
|
const h = c.toString(16);
|
|
if ((h.length & 1) == 0) return h; return `0${h}`;
|
|
}
|
|
|
|
function RSAEncryptOAEP(text, hash, hashLen) {
|
|
var m = oaep_pad(text, (this.n.bitLength() + 7) >> 3, hash, hashLen);
|
|
if (m == null) return null;
|
|
var c = this.doPublic(m);
|
|
if (c == null) return null;
|
|
var h = c.toString(16);
|
|
if ((h.length & 1) == 0) return h; else return "0" + h;
|
|
}
|
|
|
|
// 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;
|
|
// }
|
|
|
|
// protected
|
|
RSAKey.prototype.doPublic = RSADoPublic;
|
|
|
|
// public
|
|
RSAKey.prototype.setPublic = RSASetPublic;
|
|
RSAKey.prototype.encrypt = RSAEncrypt;
|
|
RSAKey.prototype.encryptOAEP = RSAEncryptOAEP;
|
|
// RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
|
|
|
|
module.exports.RSAKey = RSAKey;
|