/* * RSA Encryption / Decryption with PKCS1 v2 Padding. * * Copyright (c) 2003-2005 Tom Wu * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * In addition, the following condition applies: * * All redistributions must retain an intact copy of this copyright notice * and disclaimer. */ /* * Node.js adaptation * long message support implementation * signing/verifying * * 2014 rzcoder */ var _ = require('../utils')._; var crypt = require('crypto'); var BigInteger = require('./jsbn.js'); var utils = require('../utils.js'); var schemes = require('../schemes/schemes.js'); var encryptEngines = require('../encryptEngines/encryptEngines.js'); exports.BigInteger = BigInteger; module.exports.Key = (function () { /** * RSA key constructor * * n - modulus * e - publicExponent * d - privateExponent * p - prime1 * q - prime2 * dmp1 - exponent1 -- d mod (p1) * dmq1 - exponent2 -- d mod (q-1) * coeff - coefficient -- (inverse of q) mod p */ 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; } RSAKey.prototype.setOptions = function (options) { var signingSchemeProvider = schemes[options.signingScheme]; var encryptionSchemeProvider = schemes[options.encryptionScheme]; if (signingSchemeProvider === encryptionSchemeProvider) { this.signingScheme = this.encryptionScheme = encryptionSchemeProvider.makeScheme(this, options); } else { this.encryptionScheme = encryptionSchemeProvider.makeScheme(this, options); this.signingScheme = signingSchemeProvider.makeScheme(this, options); } this.encryptEngine = encryptEngines.getEngine(this, options); }; /** * Generate a new random private key B bits long, using public expt E * @param B * @param E */ RSAKey.prototype.generate = function (B, E) { var qs = B >> 1; this.e = parseInt(E, 16); var ee = new BigInteger(E, 16); while (true) { while (true) { this.p = new BigInteger(B - qs, 1); if (this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) === 0 && this.p.isProbablePrime(10)) break; } while (true) { this.q = new BigInteger(qs, 1); 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); if (this.n.bitLength() < B) { continue; } 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; } } this.$$recalculateCache(); }; /** * Set the private key fields N, e, d and CRT params from buffers * * @param N * @param E * @param D * @param P * @param Q * @param DP * @param DQ * @param C */ RSAKey.prototype.setPrivate = function (N, E, D, P, Q, DP, DQ, C) { if (N && E && D && N.length > 0 && (_.isNumber(E) || E.length > 0) && D.length > 0) { this.n = new BigInteger(N); this.e = _.isNumber(E) ? E : utils.get32IntFromBuffer(E, 0); this.d = new BigInteger(D); if (P && Q && DP && DQ && C) { this.p = new BigInteger(P); this.q = new BigInteger(Q); this.dmp1 = new BigInteger(DP); this.dmq1 = new BigInteger(DQ); this.coeff = new BigInteger(C); } else { // TODO: re-calculate any missing CRT params } this.$$recalculateCache(); } else { throw Error("Invalid RSA private key"); } }; /** * Set the public key fields N and e from hex strings * @param N * @param E */ RSAKey.prototype.setPublic = function (N, E) { if (N && E && N.length > 0 && (_.isNumber(E) || E.length > 0)) { this.n = new BigInteger(N); this.e = _.isNumber(E) ? E : utils.get32IntFromBuffer(E, 0); this.$$recalculateCache(); } else { throw Error("Invalid RSA public key"); } }; /** * private * Perform raw private operation on "x": return x^d (mod n) * * @param x * @returns {*} */ RSAKey.prototype.$doPrivate = function (x) { if (this.p || this.q) { 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); }; /** * private * Perform raw public operation on "x": return x^e (mod n) * * @param x * @returns {*} */ RSAKey.prototype.$doPublic = function (x) { return x.modPowInt(this.e, this.n); }; /** * Return the PKCS#1 RSA encryption of buffer * @param buffer {Buffer} * @returns {Buffer} */ RSAKey.prototype.encrypt = function (buffer, usePrivate) { var buffers = []; var results = []; var bufferSize = buffer.length; var buffersCount = Math.ceil(bufferSize / this.maxMessageLength) || 1; // total buffers count for encrypt var dividedSize = Math.ceil(bufferSize / buffersCount || 1); // each buffer size if (buffersCount == 1) { buffers.push(buffer); } else { for (var bufNum = 0; bufNum < buffersCount; bufNum++) { buffers.push(buffer.slice(bufNum * dividedSize, (bufNum + 1) * dividedSize)); } } for (var i = 0; i < buffers.length; i++) { results.push(this.encryptEngine.encrypt(buffers[i], usePrivate)); } return Buffer.concat(results); }; /** * Return the PKCS#1 RSA decryption of buffer * @param buffer {Buffer} * @returns {Buffer} */ RSAKey.prototype.decrypt = function (buffer, usePublic) { if (buffer.length % this.encryptedDataLength > 0) { throw Error('Incorrect data or key'); } var result = []; var offset = 0; var length = 0; var buffersCount = buffer.length / this.encryptedDataLength; for (var i = 0; i < buffersCount; i++) { offset = i * this.encryptedDataLength; length = offset + this.encryptedDataLength; result.push(this.encryptEngine.decrypt(buffer.slice(offset, Math.min(length, buffer.length)), usePublic)); } return Buffer.concat(result); }; RSAKey.prototype.sign = function (buffer) { return this.signingScheme.sign.apply(this.signingScheme, arguments); }; RSAKey.prototype.verify = function (buffer, signature, signature_encoding) { return this.signingScheme.verify.apply(this.signingScheme, arguments); }; /** * Check if key pair contains private key */ RSAKey.prototype.isPrivate = function () { return this.n && this.e && this.d || false; }; /** * Check if key pair contains public key * @param strict {boolean} - public key only, return false if have private exponent */ RSAKey.prototype.isPublic = function (strict) { return this.n && this.e && !(strict && this.d) || false; }; Object.defineProperty(RSAKey.prototype, 'keySize', { get: function () { return this.cache.keyBitLength; } }); Object.defineProperty(RSAKey.prototype, 'encryptedDataLength', { get: function () { return this.cache.keyByteLength; } }); Object.defineProperty(RSAKey.prototype, 'maxMessageLength', { get: function () { return this.encryptionScheme.maxMessageLength(); } }); /** * Caching key data */ RSAKey.prototype.$$recalculateCache = function () { this.cache = this.cache || {}; // Bit & byte length this.cache.keyBitLength = this.n.bitLength(); this.cache.keyByteLength = (this.cache.keyBitLength + 6) >> 3; }; return RSAKey; })();