Skip to content

Commit b9b671f

Browse files
committed
added browser support for signing
fixed tests
1 parent fe203d6 commit b9b671f

File tree

6 files changed

+271
-92
lines changed

6 files changed

+271
-92
lines changed

README.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ npm install node-rsa
2222
npm test
2323
```
2424

25+
## Work environment
26+
27+
This library developed and tested primary for Node.js, but it still can work in browsers with [browserify](http://browserify.org/).
28+
2529
## Usage
2630

2731
### Create instance
@@ -32,7 +36,8 @@ var key = new NodeRSA([key], [options]);
3236
```
3337
**key** - parameters of a generated key or the key in PEM format.<br/>
3438
**options** - additional settings
35-
* **signingAlgorithm** - algorithm used for signing and verifying. Default *'RSA-SHA256'*
39+
* **environment** - working environment, `'browser'` or `'node'`. Default autodetect.
40+
* **signingAlgorithm** - hash algorithm used for signing and verifying. Can by `'sha1'`, `'sha256'`, `'md5'`. Default `'sha256'`.
3641

3742
#### "Empty" key
3843
```js
@@ -78,23 +83,23 @@ key.getPublicPEM();
7883
key.isPrivate();
7984
key.isPublic([strict]);
8085
```
81-
**strict** - if true method will return false if key pair have private exponent. Default *false*.
86+
**strict** - if true method will return false if key pair have private exponent. Default `false`.
8287

8388
### Encrypting/decrypting
8489
```js
8590
key.encrypt(buffer, [encoding], [source_encoding]);
8691
```
8792
Return encrypted data.<br/>
8893
**buffer** - data for encrypting, may be string, Buffer, or any object/array. Arrays and objects will encoded to JSON string first.<br/>
89-
**encoding** - encoding for output result, may be 'buffer', 'binary', 'hex' or 'base64'. Default *buffer*.
90-
**source_encoding** - source encoding, works only with string buffer. Can take standard Node.js Buffer encodings (hex, utf8, base64, etc). *'utf8'* by default.<br/>
94+
**encoding** - encoding for output result, may be `'buffer'`, `'binary'`, `'hex'` or 'base64'. Default `'buffer'`.
95+
**source_encoding** - source encoding, works only with string buffer. Can take standard Node.js Buffer encodings (hex, utf8, base64, etc). `'utf8'` by default.<br/>
9196

9297
```js
9398
key.decrypt(buffer, [encoding]);
9499
```
95100
Return decrypted data.<br/>
96101
**buffer** - data for decrypting. Takes Buffer object or base64 encoded string.<br/>
97-
**encoding** - encoding for result string. Can also take 'buffer' for raw Buffer object, or 'json' for automatic JSON.parse result. Default *'buffer'*.
102+
**encoding** - encoding for result string. Can also take `'buffer'` for raw Buffer object, or `'json'` for automatic JSON.parse result. Default `'buffer'`.
98103

99104
### Signing/Verifying
100105
```js
@@ -105,16 +110,29 @@ Return signature for buffer. All the arguments are the same as for `encrypt` met
105110
```js
106111
key.verify(buffer, signature, [source_encoding], [signature_encoding])
107112
```
108-
Return result of check, _true_ or _false_.<br/>
113+
Return result of check, `true` or `false`.<br/>
109114
**buffer** - data for check, same as `encrypt` method.<br/>
110115
**signature** - signature for check, result of `sign` method.<br/>
111116
**source_encoding** - same as for `encrypt` method.<br/>
112-
**signature_encoding** - encoding of given signature. May be 'buffer', 'binary', 'hex' or 'base64'. Default *'buffer'*.
117+
**signature_encoding** - encoding of given signature. May be `'buffer'`, `'binary'`, `'hex'` or `'base64'`. Default `'buffer'`.
113118

114119
## Contributing
115120

116121
Questions, comments, bug reports, and pull requests are all welcome.
117122

123+
## Changelog
124+
125+
### 0.1.50
126+
* Implemented native js signing and verifying for browsers
127+
* `.loadFromPublicPEM` and `.loadFromPrivatePEM` methods marked as private
128+
129+
### 0.1.40
130+
* Added signing/verifying
131+
132+
### 0.1.30
133+
* Added long message support
134+
135+
118136
## License for NodeRSA.js
119137

120138
Copyright (c) 2014 rzcoder<br/>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-rsa",
3-
"version": "0.1.41",
3+
"version": "0.1.50",
44
"description": "Node.js RSA library",
55
"main": "src/NodeRSA.js",
66
"scripts": {

src/NodeRSA.js

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ module.exports = (function() {
2525
this.$cache = {};
2626

2727
this.options = _.merge({
28-
signingAlgorithm: 'RSA-SHA256'
28+
signingAlgorithm: 'sha256',
29+
environment: utils.detectEnvironment()
2930
}, options || {});
3031

3132
if (_.isObject(key)) {
@@ -57,9 +58,9 @@ module.exports = (function() {
5758
*/
5859
NodeRSA.prototype.loadFromPEM = function(pem) {
5960
if (/^-----BEGIN RSA PRIVATE KEY-----\s([A-Za-z0-9+/=]+\s)+-----END RSA PRIVATE KEY-----$/g.test(pem)) {
60-
this.loadFromPrivatePEM(pem, 'base64');
61+
this.$loadFromPrivatePEM(pem, 'base64');
6162
} else if (/^-----BEGIN PUBLIC KEY-----\s([A-Za-z0-9+/=]+\s)+-----END PUBLIC KEY-----$/g.test(pem)) {
62-
this.loadFromPublicPEM(pem, 'base64');
63+
this.$loadFromPublicPEM(pem, 'base64');
6364
} else
6465
throw Error('Invalid PEM format');
6566

@@ -69,13 +70,13 @@ module.exports = (function() {
6970
/**
7071
* Make key form private PEM string
7172
*
72-
* @param publicPEM {string}
73+
* @param privatePEM {string}
7374
*/
74-
NodeRSA.prototype.loadFromPrivatePEM = function(privatePEM, encoding) {
75+
NodeRSA.prototype.$loadFromPrivatePEM = function(privatePEM, encoding) {
7576
var pem = privatePEM
7677
.replace('-----BEGIN RSA PRIVATE KEY-----','')
7778
.replace('-----END RSA PRIVATE KEY-----','')
78-
.replace(/\s+|\n|\r$/gm, '');
79+
.replace(/\s+|\n\r|\n|\r$/gm, '');
7980
var reader = new ber.Reader(new Buffer(pem, 'base64'));
8081

8182
reader.readSequence();
@@ -96,13 +97,13 @@ module.exports = (function() {
9697
/**
9798
* Make key form public PEM string
9899
*
99-
* @param privatePEM {string}
100+
* @param publicPEM {string}
100101
*/
101-
NodeRSA.prototype.loadFromPublicPEM = function(publicPEM, encoding) {
102+
NodeRSA.prototype.$loadFromPublicPEM = function(publicPEM, encoding) {
102103
var pem = publicPEM
103104
.replace('-----BEGIN PUBLIC KEY-----','')
104105
.replace('-----END PUBLIC KEY-----','')
105-
.replace(/\s+|\n|\r$/gm, '');
106+
.replace(/\s+|\n\r|\n|\r$/gm, '');
106107
var reader = new ber.Reader(new Buffer(pem, 'base64'));
107108

108109
reader.readSequence();
@@ -178,10 +179,19 @@ module.exports = (function() {
178179
throw Error("It is not private key");
179180
}
180181

181-
encoding = (!encoding || encoding == 'buffer' ? null : encoding);
182-
var signer = crypt.createSign(this.options.signingAlgorithm);
183-
signer.update(this.$getDataForEcrypt(buffer, source_encoding));
184-
return signer.sign(this.getPrivatePEM(), encoding);
182+
if (this.options.environment == 'browser') {
183+
var res = this.keyPair.sign(this.$getDataForEcrypt(buffer, source_encoding), this.options.signingAlgorithm.toLowerCase());
184+
if (encoding && encoding != 'buffer') {
185+
return res.toString(encoding);
186+
} else {
187+
return res;
188+
}
189+
} else {
190+
encoding = (!encoding || encoding == 'buffer' ? null : encoding);
191+
var signer = crypt.createSign('RSA-' + this.options.signingAlgorithm.toUpperCase());
192+
signer.update(this.$getDataForEcrypt(buffer, source_encoding));
193+
return signer.sign(this.getPrivatePEM(), encoding);
194+
}
185195
};
186196

187197
/**
@@ -194,10 +204,19 @@ module.exports = (function() {
194204
* @returns {*}
195205
*/
196206
NodeRSA.prototype.verify = function(buffer, signature, source_encoding, signature_encoding) {
207+
if (!this.isPublic()) {
208+
throw Error("It is not public key");
209+
}
210+
197211
signature_encoding = (!signature_encoding || signature_encoding == 'buffer' ? null : signature_encoding);
198-
var verifier = crypt.createVerify(this.options.signingAlgorithm);
199-
verifier.update(this.$getDataForEcrypt(buffer, source_encoding));
200-
return verifier.verify(this.getPublicPEM(), signature, signature_encoding);
212+
213+
if (this.options.environment == 'browser') {
214+
return this.keyPair.verify(this.$getDataForEcrypt(buffer, source_encoding), signature, signature_encoding, this.options.signingAlgorithm.toLowerCase());
215+
} else {
216+
var verifier = crypt.createVerify('RSA-' + this.options.signingAlgorithm.toUpperCase());
217+
verifier.update(this.$getDataForEcrypt(buffer, source_encoding));
218+
return verifier.verify(this.getPublicPEM(), signature, signature_encoding);
219+
}
201220
};
202221

203222
NodeRSA.prototype.getPrivatePEM = function () {
@@ -253,7 +272,6 @@ module.exports = (function() {
253272
}
254273
};
255274

256-
257275
/**
258276
* private
259277
* Recalculating properties

src/libs/rsa.js

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,23 @@
3232
*/
3333

3434
/*
35-
* Node.js adaptation, long message support implementation
35+
* Node.js adaptation
36+
* long message support implementation
37+
* signing/verifying
38+
*
3639
* 2014 rzcoder
3740
*/
3841

3942
var crypt = require('crypto');
4043
var BigInteger = require("./jsbn.js");
4144
var utils = require('../utils.js');
45+
var _ = require('lodash');
46+
47+
var SIGNINFOHEAD = {
48+
sha1: new Buffer('3021300906052b0e03021a05000414','hex'),
49+
sha256: new Buffer('3031300d060960864801650304020105000420','hex'),
50+
md5: new Buffer('3020300c06082a864886f70d020505000410','hex')
51+
};
4252

4353
exports.BigInteger = BigInteger;
4454
module.exports.Key = (function() {
@@ -192,8 +202,8 @@ module.exports.Key = (function() {
192202
var results = [];
193203

194204
var bufferSize = buffer.length;
195-
var buffersCount = Math.ceil(bufferSize / this.maxMessageLength); // total buffers count for encrypt
196-
var dividedSize = Math.ceil(bufferSize / buffersCount); // each buffer size
205+
var buffersCount = Math.ceil(bufferSize / this.maxMessageLength) || 1; // total buffers count for encrypt
206+
var dividedSize = Math.ceil(bufferSize / buffersCount || 1); // each buffer size
197207

198208
if ( buffersCount == 1) {
199209
buffers.push(buffer);
@@ -206,7 +216,7 @@ module.exports.Key = (function() {
206216
for(var i in buffers) {
207217
var buf = buffers[i];
208218

209-
var m = this.$$pkcs1pad2(buf, this.encryptedDataLength);
219+
var m = this.$$pkcs1pad2(buf);
210220

211221
if (m === null) {
212222
return null;
@@ -246,23 +256,53 @@ module.exports.Key = (function() {
246256

247257
var buffersCount = buffer.length / this.encryptedDataLength;
248258

249-
250259
for (var i = 0; i < buffersCount; i++) {
251260
offset = i * this.encryptedDataLength;
252261
length = offset + this.encryptedDataLength;
253262

254263
var c = new BigInteger(buffer.slice(offset, Math.min(length, buffer.length)));
264+
255265
var m = this.$doPrivate(c);
266+
256267
if (m === null) {
257268
return null;
258269
}
259270

260-
result.push(this.$$pkcs1unpad2(m, this.encryptedDataLength));
271+
result.push(this.$$pkcs1unpad2(m));
261272
}
262273

263274
return Buffer.concat(result);
264275
};
265276

277+
RSAKey.prototype.sign = function (buffer, hashAlgorithm) {
278+
var hasher = crypt.createHash(hashAlgorithm);
279+
hasher.update(buffer);
280+
var hash = this.$$pkcs1(hasher.digest(), hashAlgorithm);
281+
var encryptedBuffer = this.$doPrivate(new BigInteger(hash)).toBuffer(true);
282+
283+
while (encryptedBuffer.length < this.encryptedDataLength) {
284+
encryptedBuffer = Buffer.concat([new Buffer([0]), encryptedBuffer]);
285+
}
286+
287+
return encryptedBuffer;
288+
289+
};
290+
291+
RSAKey.prototype.verify = function (buffer, signature, signature_encoding, hashAlgorithm) {
292+
293+
if (signature_encoding) {
294+
signature = new Buffer(signature, signature_encoding);
295+
}
296+
297+
var hasher = crypt.createHash(hashAlgorithm);
298+
hasher.update(buffer);
299+
300+
var hash = this.$$pkcs1(hasher.digest(), hashAlgorithm);
301+
var m = this.$doPublic(new BigInteger(signature));
302+
303+
return m.toBuffer().toString('hex') == hash.toString('hex');
304+
};
305+
266306
Object.defineProperty(RSAKey.prototype, 'encryptedDataLength', {
267307
get: function() { return this.cache.keyByteLength; }
268308
});
@@ -282,26 +322,53 @@ module.exports.Key = (function() {
282322
};
283323

284324
/**
285-
* PKCS#1 (type 2, random) pad input buffer to n bytes, and return a bigint
286-
* @param buffer
325+
* PKCS#1 pad input buffer to max data length
326+
* @param hashBuf
327+
* @param hashAlgorithm
287328
* @param n
288329
* @returns {*}
289330
*/
290-
RSAKey.prototype.$$pkcs1pad2 = function (buffer, n) {
291-
if (n < buffer.length + 11) {
292-
throw new Error("Message too long for RSA (n=" + n + ", l=" + buffer.length + ")");
331+
RSAKey.prototype.$$pkcs1 = function (hashBuf, hashAlgorithm, n) {
332+
if(!SIGNINFOHEAD[hashAlgorithm])
333+
throw Error('Unsupported hash algorithm');
334+
335+
var data = Buffer.concat([SIGNINFOHEAD[hashAlgorithm], hashBuf]);
336+
337+
if (data.length + 10 > this.encryptedDataLength) {
338+
throw Error('Key is too short for signing algorithm (' + hashAlgorithm + ')');
339+
}
340+
341+
var filled = new Buffer(this.encryptedDataLength - data.length - 1);
342+
filled.fill(0xff, 0, filled.length - 1);
343+
filled[0] = 1;
344+
filled[filled.length - 1] = 0;
345+
346+
var res = Buffer.concat([filled, data]);
347+
348+
return res;
349+
};
350+
351+
/**
352+
* PKCS#1 (type 2, random) pad input buffer to encryptedDataLength bytes, and return a bigint
353+
* @param buffer
354+
* @returns {*}
355+
*/
356+
RSAKey.prototype.$$pkcs1pad2 = function (buffer) {
357+
if (buffer.length > this.maxMessageLength) {
358+
throw new Error("Message too long for RSA (n=" + this.encryptedDataLength + ", l=" + buffer.length + ")");
293359
}
294360

295361
// TO-DO: make n-length buffer
296362
var ba = Array.prototype.slice.call(buffer, 0);
297363

298364
// random padding
299365
ba.unshift(0);
300-
var rand = crypt.randomBytes(n - ba.length - 2);
366+
var rand = crypt.randomBytes(this.encryptedDataLength - ba.length - 2);
301367
for(var i = 0; i < rand.length; i++) {
302368
var r = rand[i];
303-
while (r === 0) // non-zero only
369+
while (r === 0) { // non-zero only
304370
r = crypt.randomBytes(1)[0];
371+
}
305372
ba.unshift(r);
306373
}
307374
ba.unshift(2);
@@ -313,17 +380,16 @@ module.exports.Key = (function() {
313380
/**
314381
* Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext
315382
* @param d
316-
* @param n
317383
* @returns {Buffer}
318384
*/
319-
RSAKey.prototype.$$pkcs1unpad2 = function (d, n) {
385+
RSAKey.prototype.$$pkcs1unpad2 = function (d) {
320386
var b = d.toByteArray();
321387
var i = 0;
322388
while (i < b.length && b[i] === 0) {
323389
++i;
324390
}
325391

326-
if (b.length - i != n - 1 || b[i] != 2) {
392+
if (b.length - i != this.encryptedDataLength - 1 || b[i] != 2) {
327393
return null;
328394
}
329395

src/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ module.exports.linebrk = function (str, maxLen) {
1919
return res + str.substring(i, str.length);
2020
};
2121

22+
module.exports.detectEnvironment = function() {
23+
if (process && process.title != 'browser') {
24+
return 'node';
25+
} else if (window) {
26+
return 'browser';
27+
}
28+
29+
return 'node';
30+
};
31+
2232
/**
2333
* Trying get a 32-bit unsigned integer from the partial buffer
2434
* @param buffer

0 commit comments

Comments
 (0)