rsa-compat.js
| Sponsored by ppl.
JavaScript RSA utils that work on Windows, Mac, and Linux with or without C compiler
In order to provide a module that "just works" everywhere, we mix and match methods
from node.js
core, ursa
, forge
, and others.
This is useful for certbot and letsencrypt.
(in the future we'd like to provide the same API to the browser)
Install
node.js
npm install --save rsa-compat
For more efficient RSA key generation:
(I dropped ursa
as an "optional dependency" because the non-fatal error messages on unsupported platforms and node versions were confusing people, but I still recommend installing it)
npm install --save ursa
Node < v6 support:
npm install --save buffer-v6-polyfill
CLI
npm install --global rsa-compat
Usage
CLI
You can generate keypairs on Windows, Mac, and Linux using rsa-keygen-js:
rsa-keypiar-js
Examples
Generate an RSA Keypair:
var RSA = require('rsa-compat').RSA;
var options = { bitlen: 2048, exp: 65537, public: true, pem: true, internal: true };
RSA.generateKeypair(options, function (err, keypair) {
console.log(keypair);
});
Here's what the object might look like:
console.log(keypair)
:
{ publicKeyPem: '-----BEGIN RSA PUBLIC KEY-----\n/*base64 pem-encoded string*/'
, privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\n/*base64 pem-encoded string*/'
, privateKeyJwk: {
kty: "RSA"
, n: '/*base64 modulus n = pq*/'
, e: '/*base64 exponent (usually 65537)*/'
, d: '/*base64 private exponent (d = e^−1 (mod ϕ(n))/'
, p: '/*base64 first prime*/'
, q: '/*base64 second prime*/'
, dp: '/*base64 first exponent for Chinese remainder theorem (dP = d (mod p−1))*/'
, dq: '/*base64 Second exponent, used for CRT (dQ = d (mod q−1))/'
, qi: '/*base64 Coefficient, used for CRT (qinv = q^−1 (mod p))*/'
}
, publicKeyJwk: {
kty: "RSA"
, n: '/*base64 modulus n = pq*/'
, e: '/*base64 exponent (usually 65537)*/'
}
, _ursa: '/*undefined or intermediate ursa object*/'
, _ursaPublic: '/*undefined or intermediate ursa object*/'
, _forge: '/*undefined or intermediate forge object*/'
, _forgePublic: '/*undefined or intermediate forge object*/'
}
NOTE: this object is JSON safe as _ursa and _forge will be ignored
See http://crypto.stackexchange.com/questions/6593/what-data-is-saved-in-rsa-private-key to learn a little more about the meaning of the specific fields in the JWK.
Security and Compatibility
TL;DR: Use the default values 2048 and 65537 unless you have a really, really good reason to do otherwise.
Various platforms require these values.
Most security experts agree that 4096-bit is no more "secure" than 2048-bit -
a fundamental vulnerability in the RSA algorithm which causes 2048 to be broken
will most likely also cause 4096 to be broken
(i.e. if someone can prove mathematically prove P=NP or a way to predict prime numbers).
Also, many platforms
only support 2048 bit keys due to the insecurity of 1024-bit keys (which are not 1/2 secure
but rather 1/(2^1028) less secure) and the excess computational
cost of 4096-bit keys (it's not a 2x increase, it's more like a 2^2048 increase).
As to why 65537 is even optional as a prime exponent or why it matters... no idea,
but it does matter.
API
RSA.generateKeypair(options, cb)
- (deprecated
RSA.generateKeypair(bitlen, exp, options, cb)
)
RSA.import(options)
- (deprecated
RSA.import(keypair, options)
)
RSA.exportPrivatePem(keypair)
RSA.exportPublicPem(keypair)
RSA.exportPrivateJwk(keypair)
RSA.exportPublicJwk(keypair)
RSA.signJws(keypair, header, protect, payload)
- (deprecated
RSA.signJws(keypair, payload, nonce)
)
RSA.generateCsrPem(keypair, names)
RSA.generateCsrDerWeb64(keypair, names)
keypair
can be any object with any of these keys publicKeyPem, privateKeyPem, publicKeyJwk, privateKeyJwk
RSA.generateKeypair(options, cb)
Create a private keypair and export it as PEM, JWK, and/or internal formats
RSA.generateKeypair(null, function (keypair) { });
RSA.generateKeypair({
bitlen: 2048, exp: 65537, pem: false, public: false, internal: false
}, function (keypair) { });
options
:
{ public: false
, pem: false
, jwk: true
, internal: false
, thumbprint: false
, fingerprint: false
}
RSA.import(options)
Imports keypair as JWKs and internal values _ursa
and _forge
.
var keypair = RSA.import({ type: 'RSA', privateKeyPem: '...' });
console.log(keypair);
{ privateKeyPem: ..., privateKeyJwk: ..., _ursa: ..., _forge: ... }
RSA.export*(keypair)
You put in an object like { privateKeyPem: '...' }
or { publicKeyJwk: {} }
and you get back the keys in the format you requested.
Note:
- Private keys can be used to export both private and public keys
- Public keys can NOT be used to generate private keys
Example:
var keypair = { privateKeyPem: '...' };
keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
console.log(keypair);
RSA.signJws(keypair, payload, nonce)
Generates a signature in JWS format (necessary for certbot/letsencrypt).
var message = "Hello, World!"
var nonce = crypto.randomBytes(16).toString('hex');
var jws = RSA.signJws(keypair, message, nonce);
console.log(jws);
The result looks like this:
{ "header": {
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9",
"e": "AQAB"
}
},
"protected": "eyJub25jZSI6IjhlZjU2MjRmNWVjOWQzZWYifQ",
"payload": "JLzF1NBNCV3kfbJ5sFaFyX94fJuL2H-IzaoBN-ciiHk",
"signature": "Wb2al5SDyh5gjmkV79MK9m3sfNBBPjntSKor-34BBoGwr6n8qEnBmqB1Y4zbo-5rmvsoPmJsnRlP_hRiUY86zSAQyfbisTGrGBl0IQ7ditpkfYVm0rBWJ8WnYNqYNp8K3qcD7NW72tsy-XoWEjNlz4lWJeRdEG2Nt4CJgnREH4Y"
}
RSA.generateCsr*(keypair, names)
You can generate the CSR in human-readable or binary / base64 formats:
RSA.generateCsrPem(keypair, names)
:
var pem = RSA.generateCsrPem(keypair, [ 'example.com', 'www.example.com' ]);
console.log(pem);
web-safe base64 for certbot/letsencrypt:
RSA.generateCsrDerWeb64(keypair, names)
:
var web64 = RSA.generateCsrDerWeb64(keypair, [ 'example.com', 'www.example.com' ]);
console.log(web64);
ChangeLog:
- v1.4.0
- remove ursa as dependency (just causes confusion), but note in docs
- drop node < v6 support