js-encrypt
Version:
A Javascript library to perform OpenSSL RSA Encryption, Decryption, and Key Generation.
495 lines (434 loc) • 17.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JSEncrypt = undefined;
var _asn = require("./lib/asn1js/asn1");
var _rsa = require("./lib/jsbn/rsa2");
var _base = require("./lib/asn1js/base64");
var _hex = require("./lib/asn1js/hex");
var _rsa2 = require("./lib/jsbn/rsa");
var _asn2 = require("./lib/jsrsasign/asn1-1.0");
var _base2 = require("./lib/jsbn/base64");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* Retrieve the hexadecimal value (as a string) of the current ASN.1 element
* @returns {string}
* @public
*/
_asn.ASN1.prototype.getHexStringValue = function () {
var hexString = this.toHexString();
var offset = this.header * 2;
var length = this.length * 2;
return hexString.substr(offset, length);
};
/**
* Method to parse a pem encoded string containing both a public or private key.
* The method will translate the pem encoded string in a der encoded string and
* will parse private key and public key parameters. This method accepts public key
* in the rsaencryption pkcs #1 format (oid: 1.2.840.113549.1.1.1).
*
* @todo Check how many rsa formats use the same format of pkcs #1.
*
* The format is defined as:
* PublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* PublicKey BIT STRING
* }
* Where AlgorithmIdentifier is:
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER, the OID of the enc algorithm
* parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1)
* }
* and PublicKey is a SEQUENCE encapsulated in a BIT STRING
* RSAPublicKey ::= SEQUENCE {
* modulus INTEGER, -- n
* publicExponent INTEGER -- e
* }
* it's possible to examine the structure of the keys obtained from openssl using
* an asn.1 dumper as the one used here to parse the components: http://lapo.it/asn1js/
* @argument {string} pem the pem encoded string, can include the BEGIN/END header/footer
* @private
*/
_rsa.RSAKey.prototype.parseKey = function (pem) {
try {
var modulus = 0;
var public_exponent = 0;
var reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/;
var der = reHex.test(pem) ? _hex.Hex.decode(pem) : _base.Base64.unarmor(pem);
var asn1 = _asn.ASN1.decode(der);
//Fixes a bug with OpenSSL 1.0+ private keys
if (asn1.sub.length === 3) {
asn1 = asn1.sub[2].sub[0];
}
if (asn1.sub.length === 9) {
// Parse the private key.
modulus = asn1.sub[1].getHexStringValue(); //bigint
this.n = (0, _rsa2.parseBigInt)(modulus, 16);
public_exponent = asn1.sub[2].getHexStringValue(); //int
this.e = parseInt(public_exponent, 16);
var private_exponent = asn1.sub[3].getHexStringValue(); //bigint
this.d = (0, _rsa2.parseBigInt)(private_exponent, 16);
var prime1 = asn1.sub[4].getHexStringValue(); //bigint
this.p = (0, _rsa2.parseBigInt)(prime1, 16);
var prime2 = asn1.sub[5].getHexStringValue(); //bigint
this.q = (0, _rsa2.parseBigInt)(prime2, 16);
var exponent1 = asn1.sub[6].getHexStringValue(); //bigint
this.dmp1 = (0, _rsa2.parseBigInt)(exponent1, 16);
var exponent2 = asn1.sub[7].getHexStringValue(); //bigint
this.dmq1 = (0, _rsa2.parseBigInt)(exponent2, 16);
var coefficient = asn1.sub[8].getHexStringValue(); //bigint
this.coeff = (0, _rsa2.parseBigInt)(coefficient, 16);
} else if (asn1.sub.length === 2) {
// Parse the public key.
var bit_string = asn1.sub[1];
var sequence = bit_string.sub[0];
modulus = sequence.sub[0].getHexStringValue();
this.n = (0, _rsa2.parseBigInt)(modulus, 16);
public_exponent = sequence.sub[1].getHexStringValue();
this.e = parseInt(public_exponent, 16);
} else {
return false;
}
return true;
} catch (ex) {
return false;
}
};
/**
* Translate rsa parameters in a hex encoded string representing the rsa key.
*
* The translation follow the ASN.1 notation :
* RSAPrivateKey ::= SEQUENCE {
* version Version,
* modulus INTEGER, -- n
* publicExponent INTEGER, -- e
* privateExponent INTEGER, -- d
* prime1 INTEGER, -- p
* prime2 INTEGER, -- q
* exponent1 INTEGER, -- d mod (p1)
* exponent2 INTEGER, -- d mod (q-1)
* coefficient INTEGER, -- (inverse of q) mod p
* }
* @returns {string} DER Encoded String representing the rsa private key
* @private
*/
_rsa.RSAKey.prototype.getPrivateBaseKey = function () {
var options = {
'array': [new _asn2.KJUR.asn1.DERInteger({ 'int': 0 }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.n }), new _asn2.KJUR.asn1.DERInteger({ 'int': this.e }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.d }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.p }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.q }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.dmp1 }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.dmq1 }), new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.coeff })]
};
var seq = new _asn2.KJUR.asn1.DERSequence(options);
return seq.getEncodedHex();
};
/**
* base64 (pem) encoded version of the DER encoded representation
* @returns {string} pem encoded representation without header and footer
* @public
*/
_rsa.RSAKey.prototype.getPrivateBaseKeyB64 = function () {
return (0, _base2.hex2b64)(this.getPrivateBaseKey());
};
/**
* Translate rsa parameters in a hex encoded string representing the rsa public key.
* The representation follow the ASN.1 notation :
* PublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* PublicKey BIT STRING
* }
* Where AlgorithmIdentifier is:
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER, the OID of the enc algorithm
* parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1)
* }
* and PublicKey is a SEQUENCE encapsulated in a BIT STRING
* RSAPublicKey ::= SEQUENCE {
* modulus INTEGER, -- n
* publicExponent INTEGER -- e
* }
* @returns {string} DER Encoded String representing the rsa public key
* @private
*/
_rsa.RSAKey.prototype.getPublicBaseKey = function () {
var options = {
'array': [new _asn2.KJUR.asn1.DERObjectIdentifier({ 'oid': '1.2.840.113549.1.1.1' }), //RSA Encryption pkcs #1 oid
new _asn2.KJUR.asn1.DERNull()]
};
var first_sequence = new _asn2.KJUR.asn1.DERSequence(options);
options = {
'array': [new _asn2.KJUR.asn1.DERInteger({ 'bigint': this.n }), new _asn2.KJUR.asn1.DERInteger({ 'int': this.e })]
};
var second_sequence = new _asn2.KJUR.asn1.DERSequence(options);
options = {
'hex': '00' + second_sequence.getEncodedHex()
};
var bit_string = new _asn2.KJUR.asn1.DERBitString(options);
options = {
'array': [first_sequence, bit_string]
};
var seq = new _asn2.KJUR.asn1.DERSequence(options);
return seq.getEncodedHex();
};
/**
* base64 (pem) encoded version of the DER encoded representation
* @returns {string} pem encoded representation without header and footer
* @public
*/
_rsa.RSAKey.prototype.getPublicBaseKeyB64 = function () {
return (0, _base2.hex2b64)(this.getPublicBaseKey());
};
/**
* wrap the string in block of width chars. The default value for rsa keys is 64
* characters.
* @param {string} str the pem encoded string without header and footer
* @param {Number} [width=64] - the length the string has to be wrapped at
* @returns {string}
* @private
*/
_rsa.RSAKey.prototype.wordwrap = function (str, width) {
width = width || 64;
if (!str) {
return str;
}
var regex = '(.{1,' + width + '})( +|$\n?)|(.{1,' + width + '})';
return str.match(RegExp(regex, 'g')).join('\n');
};
/**
* Retrieve the pem encoded private key
* @returns {string} the pem encoded private key with header/footer
* @public
*/
_rsa.RSAKey.prototype.getPrivateKey = function () {
var key = "-----BEGIN RSA PRIVATE KEY-----\n";
key += this.wordwrap(this.getPrivateBaseKeyB64()) + "\n";
key += "-----END RSA PRIVATE KEY-----";
return key;
};
/**
* Retrieve the pem encoded public key
* @returns {string} the pem encoded public key with header/footer
* @public
*/
_rsa.RSAKey.prototype.getPublicKey = function () {
var key = "-----BEGIN PUBLIC KEY-----\n";
key += this.wordwrap(this.getPublicBaseKeyB64()) + "\n";
key += "-----END PUBLIC KEY-----";
return key;
};
/**
* Check if the object contains the necessary parameters to populate the rsa modulus
* and public exponent parameters.
* @param {Object} [obj={}] - An object that may contain the two public key
* parameters
* @returns {boolean} true if the object contains both the modulus and the public exponent
* properties (n and e)
* @todo check for types of n and e. N should be a parseable bigInt object, E should
* be a parseable integer number
* @private
*/
_rsa.RSAKey.prototype.hasPublicKeyProperty = function (obj) {
obj = obj || {};
return obj.hasOwnProperty('n') && obj.hasOwnProperty('e');
};
/**
* Check if the object contains ALL the parameters of an RSA key.
* @param {Object} [obj={}] - An object that may contain nine rsa key
* parameters
* @returns {boolean} true if the object contains all the parameters needed
* @todo check for types of the parameters all the parameters but the public exponent
* should be parseable bigint objects, the public exponent should be a parseable integer number
* @private
*/
_rsa.RSAKey.prototype.hasPrivateKeyProperty = function (obj) {
obj = obj || {};
return obj.hasOwnProperty('n') && obj.hasOwnProperty('e') && obj.hasOwnProperty('d') && obj.hasOwnProperty('p') && obj.hasOwnProperty('q') && obj.hasOwnProperty('dmp1') && obj.hasOwnProperty('dmq1') && obj.hasOwnProperty('coeff');
};
/**
* Parse the properties of obj in the current rsa object. Obj should AT LEAST
* include the modulus and public exponent (n, e) parameters.
* @param {Object} obj - the object containing rsa parameters
* @private
*/
_rsa.RSAKey.prototype.parsePropertiesFrom = function (obj) {
this.n = obj.n;
this.e = obj.e;
if (obj.hasOwnProperty('d')) {
this.d = obj.d;
this.p = obj.p;
this.q = obj.q;
this.dmp1 = obj.dmp1;
this.dmq1 = obj.dmq1;
this.coeff = obj.coeff;
}
};
/**
* Create a new JSEncryptRSAKey that extends Tom Wu's RSA key object.
* This object is just a decorator for parsing the key parameter
* @param {string|Object} key - The key in string format, or an object containing
* the parameters needed to build a RSAKey object.
* @constructor
*/
var JSEncryptRSAKey = function (_RSAKey) {
_inherits(JSEncryptRSAKey, _RSAKey);
function JSEncryptRSAKey(key) {
_classCallCheck(this, JSEncryptRSAKey);
// If a key key was provided.
var _this = _possibleConstructorReturn(this, (JSEncryptRSAKey.__proto__ || Object.getPrototypeOf(JSEncryptRSAKey)).call(this));
// Call the super constructor.
if (key) {
// If this is a string...
if (typeof key === 'string') {
_this.parseKey(key);
} else if (_this.hasPrivateKeyProperty(key) || _this.hasPublicKeyProperty(key)) {
// Set the values for the key.
_this.parsePropertiesFrom(key);
}
}
return _this;
}
return JSEncryptRSAKey;
}(_rsa.RSAKey);
/**
*
* @param {Object} [options = {}] - An object to customize JSEncrypt behaviour
* possible parameters are:
* - default_key_size {number} default: 1024 the key size in bit
* - default_public_exponent {string} default: '010001' the hexadecimal representation of the public exponent
* - log {boolean} default: false whether log warn/error or not
* @constructor
*/
var JSEncrypt = exports.JSEncrypt = function JSEncrypt(options) {
_classCallCheck(this, JSEncrypt);
options = options || {};
this.default_key_size = parseInt(options.default_key_size) || 1024;
this.default_public_exponent = options.default_public_exponent || '010001'; //65537 default openssl public exponent for rsa key type
this.log = options.log || false;
// The private and public key.
this.key = null;
};
/**
* Method to set the rsa key parameter (one method is enough to set both the public
* and the private key, since the private key contains the public key paramenters)
* Log a warning if logs are enabled
* @param {Object|string} key the pem encoded string or an object (with or without header/footer)
* @public
*/
JSEncrypt.prototype.setKey = function (key) {
if (this.log && this.key) {
console.warn('A key was already set, overriding existing.');
}
this.key = new JSEncryptRSAKey(key);
};
/**
* Proxy method for setKey, for api compatibility
* @see setKey
* @public
*/
JSEncrypt.prototype.setPrivateKey = function (privkey) {
// Create the key.
this.setKey(privkey);
};
/**
* Proxy method for setKey, for api compatibility
* @see setKey
* @public
*/
JSEncrypt.prototype.setPublicKey = function (pubkey) {
// Sets the public key.
this.setKey(pubkey);
};
/**
* Proxy method for RSAKey object's decrypt, decrypt the string using the private
* components of the rsa key object. Note that if the object was not set will be created
* on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor
* @param {string} string base64 encoded crypted string to decrypt
* @return {string} the decrypted string
* @public
*/
JSEncrypt.prototype.decrypt = function (string) {
// Return the decrypted string.
try {
return this.getKey().decrypt((0, _base2.b64tohex)(string));
} catch (ex) {
return false;
}
};
/**
* Proxy method for RSAKey object's encrypt, encrypt the string using the public
* components of the rsa key object. Note that if the object was not set will be created
* on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor
* @param {string} string the string to encrypt
* @return {string} the encrypted string encoded in base64
* @public
*/
JSEncrypt.prototype.encrypt = function (string) {
// Return the encrypted string.
try {
return (0, _base2.hex2b64)(this.getKey().encrypt(string));
} catch (ex) {
return false;
}
};
/**
* Getter for the current JSEncryptRSAKey object. If it doesn't exists a new object
* will be created and returned
* @param {callback} [cb] the callback to be called if we want the key to be generated
* in an async fashion
* @returns {JSEncryptRSAKey} the JSEncryptRSAKey object
* @public
*/
JSEncrypt.prototype.getKey = function (cb) {
// Only create new if it does not exist.
if (!this.key) {
// Get a new private key.
this.key = new JSEncryptRSAKey();
if (cb && {}.toString.call(cb) === '[object Function]') {
this.key.generateAsync(this.default_key_size, this.default_public_exponent, cb);
return;
}
// Generate the key.
this.key.generate(this.default_key_size, this.default_public_exponent);
}
return this.key;
};
/**
* Returns the pem encoded representation of the private key
* If the key doesn't exists a new key will be created
* @returns {string} pem encoded representation of the private key WITH header and footer
* @public
*/
JSEncrypt.prototype.getPrivateKey = function () {
// Return the private representation of this key.
return this.getKey().getPrivateKey();
};
/**
* Returns the pem encoded representation of the private key
* If the key doesn't exists a new key will be created
* @returns {string} pem encoded representation of the private key WITHOUT header and footer
* @public
*/
JSEncrypt.prototype.getPrivateKeyB64 = function () {
// Return the private representation of this key.
return this.getKey().getPrivateBaseKeyB64();
};
/**
* Returns the pem encoded representation of the public key
* If the key doesn't exists a new key will be created
* @returns {string} pem encoded representation of the public key WITH header and footer
* @public
*/
JSEncrypt.prototype.getPublicKey = function () {
// Return the private representation of this key.
return this.getKey().getPublicKey();
};
/**
* Returns the pem encoded representation of the public key
* If the key doesn't exists a new key will be created
* @returns {string} pem encoded representation of the public key WITHOUT header and footer
* @public
*/
JSEncrypt.prototype.getPublicKeyB64 = function () {
// Return the private representation of this key.
return this.getKey().getPublicBaseKeyB64();
};