Skip to content

Commit 0449fcb

Browse files
committed
Merge branch 'BAM5-master' into dev
2 parents 2efa5c1 + 6ddb480 commit 0449fcb

File tree

5 files changed

+997
-332
lines changed

5 files changed

+997
-332
lines changed

README.md

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ var NodeRSA = require('node-rsa');
4949
var key = new NodeRSA([key], [options]);
5050
```
5151
**key** - parameters of a generated key or the key in PEM format.<br/>
52-
**options** - additional settings
53-
* **environment** - working environment, `'browser'` or `'node'`. Default autodetect.
54-
* **signingAlgorithm** - hash algorithm used for signing and verifying. Can be `'sha1'`, `'sha256'`, `'md5'`. Default `'sha256'`.
5552

5653
#### "Empty" key
5754
```javascript
@@ -119,6 +116,13 @@ Return max data size for encrypt in bytes.
119116

120117
### Encrypting/decrypting
121118

119+
*As of v0.1.55 the default encryption scheme is RSAES-OAEP using sha1 and mgf1.
120+
PKCS1 is still available with the following configuring*
121+
122+
```javascript
123+
key.schemeEncryption = NodeRSA.RSA.PKCS1.Default;
124+
```
125+
122126
```javascript
123127
key.encrypt(buffer, [encoding], [source_encoding]);
124128
```
@@ -135,6 +139,14 @@ Return decrypted data.<br/>
135139
**encoding** - encoding for result string. Can also take `'buffer'` for raw Buffer object, or `'json'` for automatic JSON.parse result. Default `'buffer'`.
136140

137141
### Signing/Verifying
142+
143+
*As of v0.1.55 the default signature scheme is RSASSA-PSS using sha1.
144+
PKCS1 is still available with the following configuring*
145+
146+
```javascript
147+
key.schemeSignature = NodeRSA.RSA.PKCS1.Default;
148+
```
149+
138150
```javascript
139151
key.sign(buffer, [encoding], [source_encoding]);
140152
```
@@ -149,12 +161,45 @@ Return result of check, `true` or `false`.<br/>
149161
**source_encoding** - same as for `encrypt` method.<br/>
150162
**signature_encoding** - encoding of given signature. May be `'buffer'`, `'binary'`, `'hex'` or `'base64'`. Default `'buffer'`.
151163

164+
### Changing Encryption/Signature schemes.
165+
166+
Schemes are the way RSA packs up it's data before encrypting/decrypting/signing/verifying and there are a few ways that it can do it. The most common are RSAES-OAEP, RSASSA-PSS, RSAES-PKCS1-v1_5(encryption/decryption), and RSASSA-PKCS1-v1_5(signing/verifying).
167+
As of v0.1.55 these 4 mentioned schemes are included in the package with the ability to easily add more later and by users. See below for how to configure NodeRSA to use these different schemes.
168+
169+
*Note: The default encryption / signature schemes have been changed from PKCS1 to the more secure OAEP(encryption) and PSS(signature) schemes as per the recommendations of the spec, this can be breaking.*
170+
171+
```javascript
172+
key.schemeSignature = NodeRSA.RSA.PSS.Default; // This is an object that has been automatically instantiated and has the default settings for PSS signing.
173+
key.schemeSignature = NodeRSA.RSA.PKCS1.Default; // This is an object that has been automatically instantiated and has the default settings for PKCS1 signing/encrypting.
174+
175+
key.schemeEncryption = NodeRSA.RSA.OAEP.Default; // This is an object that has been automatically instantiated and has the default settings for OAEP encrypting.
176+
key.schemeEncryption = NodeRSA.RSA.PKCS1.Default; // This is an object that has been automatically instantiated and has the default settings for PKCS1 signing/encrypting.
177+
```
178+
To change schemes between the various defaults, each provided scheme class has a property named "Default" that provides a scheme object with the default settings for that scheme.
179+
180+
```javascript
181+
var pssMD5Scheme = new NodeRSA.RSA.PSS({
182+
// The options a scheme accepts should be documented in the scheme definition.
183+
hash: "md5", // Use a different type of hashing function instead of sha1
184+
mgf: customMaskGenerationFunction // Use a custom mask generation function that accepts 3 parameters (seed, maskLength, hashFunction)
185+
});
186+
key.schemeSignature = pssMD5Scheme;
187+
key2.schemeSignature = pssMD5Scheme;
188+
```
189+
Schemes can also be initialized with custom options and can be shared between keys.
190+
152191
## Contributing
153192

154193
Questions, comments, bug reports, and pull requests are all welcome.
155194

156195
## Changelog
157196

197+
### 0.1.55
198+
* **The default schemes used to encrypt and sign data have changed from PKCS1 to the recommended OAEP and PSS standards**
199+
* Overhauled the rsa.js library to allow for schemes and to allow for easy addition of schemes in the future and custom schemes.
200+
* Modified NodeRSA functions to work with new rsa.js file.
201+
* All changes should be backwards compatible
202+
158203
### 0.1.54
159204
* Added support for loading PEM key from Buffer (`fs.readFileSync()` output)
160205
* Added `isEmpty()` method

src/NodeRSA.js

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
var rsa = require('./libs/rsa.js');
11-
var crypt = require('crypto');
1211
var ber = require('asn1').Ber;
1312
var _ = require('lodash');
1413
var utils = require('./utils');
@@ -21,24 +20,30 @@ module.exports = (function() {
2120
* @constructor
2221
*/
2322
function NodeRSA(key, options) {
24-
if (! this instanceof NodeRSA) {
23+
if (! this instanceof NodeRSA)
2524
return new NodeRSA(key, options);
26-
}
2725

2826
this.keyPair = new rsa.Key();
2927
this.$cache = {};
30-
31-
this.options = _.merge({
32-
signingAlgorithm: 'sha256',
33-
environment: utils.detectEnvironment()
34-
}, options || {});
28+
29+
if(options){
30+
console.warn("There are no more options and the parameter is deprecated. It may be removed in the future.");
31+
if(options.signingAlgorithm){
32+
console.warn("options.signingAlgorithm has been removed. In order to change the signature hashing algorithm create a signature scheme with the appropriate options and set NodeRSA.schemeSignature to that object. Schemes are defined in src/libs/rsa.js Default is RSASSA-PSS (RSA.PSS) with the hashing function sha1");
33+
this.schemeSignature = new rsa.PKCS1({hash: options.signingAlgorithm});
34+
}
35+
if(options.environment)
36+
console.warn("options.environment has been removed since all encryption is now done through the rsa.js library");
37+
}
3538

3639
if (Buffer.isBuffer(key) || _.isString(key)) {
3740
this.loadFromPEM(key);
3841
} else if (_.isObject(key)) {
3942
this.generateKeyPair(key.b, key.e);
4043
}
4144
}
45+
46+
NodeRSA.RSA = rsa;
4247

4348
/**
4449
* Generate private/public keys pair
@@ -155,6 +160,25 @@ module.exports = (function() {
155160
return !(this.keyPair.n || this.keyPair.e || this.keyPair.d);
156161
};
157162

163+
/*
164+
* In order to use a different encryption/signing/padding scheme then the default (OAEP for encrypting and PSS for signing)
165+
* create a new scheme object with desired options and set the appropriate variable to that scheme object.
166+
* Scheme classes are located in rsa.js and new ones can be easily added.
167+
*/
168+
Object.defineProperties(NodeRSA.prototype, {
169+
schemeEncryption: {
170+
enumerable: true,
171+
get: function(){ return this.keyPair.schemeEncryption; },
172+
set: function(scheme){ this.keyPair.schemeEncryption = scheme; }
173+
},
174+
175+
schemeSignature: {
176+
enumerable: true,
177+
get: function(){ return this.keyPair.schemeSignature; },
178+
set: function(scheme){ this.keyPair.schemeSignature = scheme; }
179+
}
180+
});
181+
158182
/**
159183
* Encrypting data method
160184
*
@@ -163,8 +187,14 @@ module.exports = (function() {
163187
* @param source_encoding {string} - optional. Encoding for given string. Default utf8.
164188
* @returns {string|Buffer}
165189
*/
166-
NodeRSA.prototype.encrypt = function(buffer, encoding, source_encoding) {
167-
var res = this.keyPair.encrypt(this.$getDataForEcrypt(buffer, source_encoding));
190+
NodeRSA.prototype.encrypt = function(buffer, encoding, source_encoding){
191+
buffer = this.$getDataForEcrypt(buffer, source_encoding);
192+
try{
193+
var res = this.keyPair.encrypt(buffer);
194+
} catch(e){
195+
console.warn(e.message);
196+
return null;
197+
}
168198

169199
if (encoding == 'buffer' || !encoding) {
170200
return res;
@@ -176,13 +206,20 @@ module.exports = (function() {
176206
/**
177207
* Decrypting data method
178208
*
179-
* @param buffer {Buffer} - buffer for decrypting
180-
* @param encoding - encoding for result string, can also take 'json' or 'buffer' for the automatic conversion of this type
181-
* @returns {Buffer|object|string}
209+
* @param buffer {Buffer} - buffer for decrypting, or base64 string
210+
* @param encoding - encoding for result string, can also take 'json' or 'buffer' for the automatic conversion of this type. Default "utf8"
211+
* @returns {string|Buffer|object}
182212
*/
183-
NodeRSA.prototype.decrypt = function(buffer, encoding) {
213+
NodeRSA.prototype.decrypt = function(buffer, encoding){
184214
buffer = _.isString(buffer) ? new Buffer(buffer, 'base64') : buffer;
185-
return this.$getDecryptedData(this.keyPair.decrypt(buffer), encoding);
215+
var clone = new Buffer(buffer.length);
216+
buffer.copy(clone);
217+
try{
218+
return this.$getDecryptedData(this.keyPair.decrypt(clone), encoding);
219+
} catch(e){
220+
console.warn(e.message);
221+
return null;
222+
}
186223
};
187224

188225
/**
@@ -193,24 +230,21 @@ module.exports = (function() {
193230
* @param source_encoding {string} - optional. Encoding for given string. Default utf8.
194231
* @returns {string|Buffer}
195232
*/
196-
NodeRSA.prototype.sign = function(buffer, encoding, source_encoding) {
197-
if (!this.isPrivate()) {
198-
throw Error("It is not private key");
199-
}
200-
201-
if (this.options.environment == 'browser') {
202-
var res = this.keyPair.sign(this.$getDataForEcrypt(buffer, source_encoding), this.options.signingAlgorithm.toLowerCase());
203-
if (encoding && encoding != 'buffer') {
204-
return res.toString(encoding);
205-
} else {
206-
return res;
207-
}
208-
} else {
209-
encoding = (!encoding || encoding == 'buffer' ? null : encoding);
210-
var signer = crypt.createSign('RSA-' + this.options.signingAlgorithm.toUpperCase());
211-
signer.update(this.$getDataForEcrypt(buffer, source_encoding));
212-
return signer.sign(this.getPrivatePEM(), encoding);
213-
}
233+
NodeRSA.prototype.sign = function(buffer, encoding, source_encoding){
234+
if(!this.isPrivate()) throw Error("It is not private key");
235+
236+
// Move all the lifting to rsa.js module due to its wider options.
237+
try{
238+
var res = this.keyPair.sign(this.$getDataForEcrypt(buffer, source_encoding));
239+
} catch(e){
240+
console.warn(e.message);
241+
return null;
242+
}
243+
244+
if(encoding && encoding != 'buffer')
245+
return res.toString(encoding);
246+
247+
return res;
214248
};
215249

216250
/**
@@ -222,20 +256,16 @@ module.exports = (function() {
222256
* @param signature_encoding - optional. Encoding of given signature. May be 'buffer', 'binary', 'hex' or 'base64'. Default 'buffer'.
223257
* @returns {*}
224258
*/
225-
NodeRSA.prototype.verify = function(buffer, signature, source_encoding, signature_encoding) {
226-
if (!this.isPublic()) {
227-
throw Error("It is not public key");
228-
}
229-
230-
signature_encoding = (!signature_encoding || signature_encoding == 'buffer' ? null : signature_encoding);
231-
232-
if (this.options.environment == 'browser') {
233-
return this.keyPair.verify(this.$getDataForEcrypt(buffer, source_encoding), signature, signature_encoding, this.options.signingAlgorithm.toLowerCase());
234-
} else {
235-
var verifier = crypt.createVerify('RSA-' + this.options.signingAlgorithm.toUpperCase());
236-
verifier.update(this.$getDataForEcrypt(buffer, source_encoding));
237-
return verifier.verify(this.getPublicPEM(), signature, signature_encoding);
238-
}
259+
NodeRSA.prototype.verify = function(buffer, signature, source_encoding, signature_encoding){
260+
if(!this.isPublic()) throw Error("It is not public key");
261+
262+
signature_encoding = (!signature_encoding || signature_encoding == 'buffer' ? null : signature_encoding);
263+
try{
264+
return this.keyPair.verify(this.$getDataForEcrypt(buffer, source_encoding), signature, signature_encoding);
265+
} catch(e){
266+
console.warn(e.message);
267+
return false;
268+
}
239269
};
240270

241271
NodeRSA.prototype.getPrivatePEM = function () {

src/libs/jsbn.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
/*
3535
* Added Node.js Buffers support
3636
* 2014 rzcoder
37+
*
38+
* Extended bnToBuffer to allow length specifying.
39+
* 2014 BAM5
3740
*/
3841

3942
var crypt = require('crypto');
@@ -812,12 +815,26 @@ function bnToByteArray() {
812815

813816
/**
814817
* return Buffer object
815-
* @param trim {boolean} slice buffer if first element == 0
818+
* @param trimOrSize {boolean | uint} slice buffer if first element == 0
816819
* @returns {Buffer}
817820
*/
818-
function bnToBuffer(trim) {
819-
var res = new Buffer(this.toByteArray());
820-
return trim && res[0] === 0 ? res.slice(1) : res;
821+
function bnToBuffer(trimOrSize) {
822+
if(trimOrSize === true || trimOrSize === undefined){
823+
var res = new Buffer(this.toByteArray());
824+
return trimOrSize && res[0] === 0 ? res.slice(1) : res;
825+
} else{
826+
var res = new Buffer(this.toByteArray());
827+
if(res.length > trimOrSize){
828+
for(var i=0; i<res.length - trimOrSize; i++) if(res[i] !== 0) return null;
829+
return res.slice(res.length - trimOrSize);
830+
} else if(res.length < trimOrSize){
831+
var padded = new Buffer(trimOrSize);
832+
padded.fill(0, 0, trimOrSize - res.length);
833+
res.copy(padded, trimOrSize - res.length);
834+
return padded;
835+
}
836+
return res;
837+
}
821838
}
822839

823840
function bnEquals(a) {

0 commit comments

Comments
 (0)