UNPKG

jsencrypt

Version:

A Javascript library to perform OpenSSL RSA Encryption, Decryption, and Key Generation.

501 lines (455 loc) 15.6 kB
/** * Retrieve the hexadecimal value (as a string) of the current ASN.1 element * @returns {string} * @public */ 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 */ 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.decode(pem) : Base64.unarmor(pem); var asn1 = 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 = 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 = parseBigInt(private_exponent, 16); var prime1 = asn1.sub[4].getHexStringValue(); //bigint this.p = parseBigInt(prime1, 16); var prime2 = asn1.sub[5].getHexStringValue(); //bigint this.q = parseBigInt(prime2, 16); var exponent1 = asn1.sub[6].getHexStringValue(); //bigint this.dmp1 = parseBigInt(exponent1, 16); var exponent2 = asn1.sub[7].getHexStringValue(); //bigint this.dmq1 = parseBigInt(exponent2, 16); var coefficient = asn1.sub[8].getHexStringValue(); //bigint this.coeff = 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 = 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 */ RSAKey.prototype.getPrivateBaseKey = function () { var options = { 'array': [ new KJUR.asn1.DERInteger({'int': 0}), new KJUR.asn1.DERInteger({'bigint': this.n}), new KJUR.asn1.DERInteger({'int': this.e}), new KJUR.asn1.DERInteger({'bigint': this.d}), new KJUR.asn1.DERInteger({'bigint': this.p}), new KJUR.asn1.DERInteger({'bigint': this.q}), new KJUR.asn1.DERInteger({'bigint': this.dmp1}), new KJUR.asn1.DERInteger({'bigint': this.dmq1}), new KJUR.asn1.DERInteger({'bigint': this.coeff}) ] }; var seq = new 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 */ RSAKey.prototype.getPrivateBaseKeyB64 = function () { return 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 */ RSAKey.prototype.getPublicBaseKey = function () { var options = { 'array': [ new KJUR.asn1.DERObjectIdentifier({'oid': '1.2.840.113549.1.1.1'}), //RSA Encryption pkcs #1 oid new KJUR.asn1.DERNull() ] }; var first_sequence = new KJUR.asn1.DERSequence(options); options = { 'array': [ new KJUR.asn1.DERInteger({'bigint': this.n}), new KJUR.asn1.DERInteger({'int': this.e}) ] }; var second_sequence = new KJUR.asn1.DERSequence(options); options = { 'hex': '00' + second_sequence.getEncodedHex() }; var bit_string = new KJUR.asn1.DERBitString(options); options = { 'array': [ first_sequence, bit_string ] }; var seq = new 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 */ RSAKey.prototype.getPublicBaseKeyB64 = function () { return 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 (key) { // Call the super constructor. RSAKey.call(this); // If a key key was provided. 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); } } }; // Derive from RSAKey. JSEncryptRSAKey.prototype = new RSAKey(); // Reset the contructor. JSEncryptRSAKey.prototype.constructor = JSEncryptRSAKey; /** * * @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 = function (options) { 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(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 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(); };