Browse files

Support old-style padding for public-en/priv.-de.

This adds support for the older RSA_PKCS1_PADDING style
for public encryption and private decryption. This makes ursa more
compatible with other crypto systems.
  • Loading branch information...
1 parent 807fd8e commit 5aa7b5d100e6a38dbd3e71443d31232f77c903ba @tylerneylon committed Aug 22, 2012
Showing with 104 additions and 38 deletions.
  1. +22 −8 README.md
  2. +24 −22 lib/ursa.js
  3. +1 −1 package.json
  4. +22 −3 src/ursaNative.cc
  5. +10 −0 test/fixture.js
  6. +24 −3 test/native.js
  7. +1 −1 test/test.js
View
30 README.md
@@ -56,11 +56,13 @@ The library knows how to read and output PEM format files for both
public and private keys, and it can generate new private keys (aka
keypairs).
-The usual public-encryption / private-decryption operations are always
-done using padding mode `RSA_PKCS1_OAEP_PADDING`, which is the recommended
+The usual public-encryption / private-decryption operations by default
+use padding mode `RSA_PKCS1_OAEP_PADDING`, which is the recommended
mode for all new applications (as of this writing). Note that this mode
builds-in a random element into every encryption operation, making it
unnecessary to waste time or effort adding randomness in at a higher layer.
+This default may be overridden to use the older mode `RSA_PKCS1_PADDING`
+if needed.
The less well-understood private-encryption / public-decryption operations
(used for building signature mechanisms) are always done using padding
@@ -220,7 +222,7 @@ These are all the methods available on public keys. These methods are
*also* available on private keys (since private keys have all the
underlying data necessary to perform the public-side operations).
-### encrypt(buf, bufEncoding, outEncoding)
+### encrypt(buf, bufEncoding, outEncoding, padding)
This performs the "public encrypt" operation on the given buffer. The
result is always a byte sequence that is the same size as the key
@@ -230,8 +232,9 @@ then the result of this operation will be 2048 bits, aka 256 bytes.)
The input buffer is limited to be no larger than the key size
minus 41 bytes.
-This operation is always performed using padding mode
-`RSA_PKCS1_OAEP_PADDING`.
+If no padding mode is specified, the default, and recommended, mode
+is `ursa.RSA_PKCS1_OAEP_PADDING`. The mode
+`ursa.RSA_PKCS1_PADDING` is also supported.
### getExponent(encoding)
@@ -321,16 +324,17 @@ Private Key Methods
These are the methods available on private keys, above and beyond
what is available for public keys.
-### decrypt(buf, bufEncoding, outEncoding)
+### decrypt(buf, bufEncoding, outEncoding, padding)
This performs the "private decrypt" operation on the given buffer. The
result is always a byte sequence that is no more than the size of the
key associated with the instance. (For example, if the key is 2048
bits, then the result of this operation will be no more than 2048
bits, aka 256 bytes.)
-This operation is always performed using padding mode
-`RSA_PKCS1_OAEP_PADDING`.
+If no padding mode is specified, the default, and recommended, mode
+is `ursa.RSA_PKCS1_OAEP_PADDING`. The mode
+`ursa.RSA_PKCS1_PADDING` is also supported.
### hashAndSign(algorithm, buf, bufEncoding, outEncoding)
@@ -411,6 +415,16 @@ structurally) yet doesn't match. This throws an exception in all
other cases.
+Constants
+---------
+
+Allowed padding modes for public encryption and
+private decryption:
+
+* `ursa.RSA_PKCS1_PADDING`
+* `ursa.RSA_PKCS1_OAEP_PADDING`
+
+
Contributing
------------
View
46 lib/ursa.js
@@ -229,9 +229,9 @@ function PublicKey(rsa) {
return sshFingerprint(createSshPublicKey(rsa), undefined, encoding);
}
- function encrypt(buf, bufEncoding, outEncoding) {
+ function encrypt(buf, bufEncoding, outEncoding, padding) {
@danfuzz
danfuzz added a note Aug 22, 2012

Rather than do default logic at the low layer, I'd prefer to do it up at the JS level, a la:

padding = padding || ursaNative. RSA_PKCS1_OAEP_PADDING;

Pretty much, if something can be done in JS I'll aim to actually do it there, at least vaguely for reasons along the lines of consistency, code safety, and conciseness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
buf = decodeString(buf, bufEncoding);
- return encodeBuffer(rsa.publicEncrypt(buf), outEncoding);
+ return encodeBuffer(rsa.publicEncrypt(buf, padding), outEncoding);
}
function publicDecrypt(buf, bufEncoding, outEncoding) {
@@ -284,9 +284,9 @@ function PrivateKey(rsa) {
return encodeBuffer(rsa.getPrivateKeyPem(), encoding);
}
- function decrypt(buf, bufEncoding, outEncoding) {
+ function decrypt(buf, bufEncoding, outEncoding, padding) {
@danfuzz
danfuzz added a note Aug 22, 2012

See above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
buf = decodeString(buf, bufEncoding);
- return encodeBuffer(rsa.privateDecrypt(buf), outEncoding);
+ return encodeBuffer(rsa.privateDecrypt(buf, padding), outEncoding);
}
function privateEncrypt(buf, bufEncoding, outEncoding) {
@@ -612,22 +612,24 @@ function createVerifier(algorithm) {
*/
module.exports = {
- assertKey: assertKey,
- assertPrivateKey: assertPrivateKey,
- assertPublicKey: assertPublicKey,
- coerceKey: coerceKey,
- coercePrivateKey: coercePrivateKey,
- coercePublicKey: coercePublicKey,
- createKey: createKey,
- createPrivateKey: createPrivateKey,
- createPublicKey: createPublicKey,
- createSigner: createSigner,
- createVerifier: createVerifier,
- equalKeys: equalKeys,
- generatePrivateKey: generatePrivateKey,
- isKey: isKey,
- isPrivateKey: isPrivateKey,
- isPublicKey: isPublicKey,
- matchingPublicKeys: matchingPublicKeys,
- sshFingerprint: sshFingerprint
+ assertKey: assertKey,
+ assertPrivateKey: assertPrivateKey,
+ assertPublicKey: assertPublicKey,
+ coerceKey: coerceKey,
+ coercePrivateKey: coercePrivateKey,
+ coercePublicKey: coercePublicKey,
+ createKey: createKey,
+ createPrivateKey: createPrivateKey,
+ createPublicKey: createPublicKey,
+ createSigner: createSigner,
+ createVerifier: createVerifier,
+ equalKeys: equalKeys,
+ generatePrivateKey: generatePrivateKey,
+ isKey: isKey,
+ isPrivateKey: isPrivateKey,
+ isPublicKey: isPublicKey,
+ matchingPublicKeys: matchingPublicKeys,
+ sshFingerprint: sshFingerprint,
+ RSA_PKCS1_PADDING: ursaNative.RSA_PKCS1_PADDING,
+ RSA_PKCS1_OAEP_PADDING: ursaNative.RSA_PKCS1_OAEP_PADDING,
};
View
2 package.json
@@ -1,6 +1,6 @@
{
"name": "ursa",
- "version": "0.6.8",
+ "version": "0.6.9",
"keywords": [
"crypto", "key", "openssl", "private", "public", "rsa", "sign",
"signature", "verify", "verification", "hash", "digest"
View
25 src/ursaNative.cc
@@ -27,6 +27,8 @@ using namespace v8;
* Top-level initialization function.
*/
void init(Handle<Object> target) {
+ NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PADDING);
+ NODE_DEFINE_CONSTANT(target, RSA_PKCS1_OAEP_PADDING);
BIND(target, textToNid, TextToNid);
RsaWrap::InitClass(target);
}
@@ -245,7 +247,7 @@ static char *getArgString(const Arguments& args, int index) {
str->WriteUtf8(result, length + 1);
if (result[length] != '\0') {
- char *message = "String conversion failed.";
+ const char *message = "String conversion failed.";
ThrowException(Exception::Error(String::New(message)));
free(result);
return NULL;
@@ -276,6 +278,17 @@ static bool getArgInt(const Arguments& args, int index, int *resultPtr) {
return true;
}
+/**
@danfuzz
danfuzz added a note Aug 22, 2012

See above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * If an int is present at the given index, store it in the given pointer;
+ * if no arg is present or it's undefined, do nothing. Return true in either
+ * case. If it is present and is neither an int nor undefined, schedule an
+ * exception and return false.
+ */
+static bool getArgIntIfExists(const Arguments& args, int index, int *resultPtr) {
+ if (args.Length() < index || args[index]->IsUndefined()) { return true; }
+ return getArgInt(args, index, resultPtr);
+}
+
/*
* Utility function implementation
@@ -560,8 +573,11 @@ Handle<Value> RsaWrap::PrivateDecrypt(const Arguments& args) {
int rsaLength = RSA_size(obj->rsa);
unsigned char buf[rsaLength];
+ int padding = RSA_PKCS1_OAEP_PADDING;
+ if (!getArgIntIfExists(args, 1, &padding)) { return Undefined(); }
+
int bufLength = RSA_private_decrypt(length, (unsigned char *) data,
- buf, obj->rsa, RSA_PKCS1_OAEP_PADDING);
+ buf, obj->rsa, padding);
if (bufLength < 0) {
scheduleSslException();
@@ -673,9 +689,12 @@ Handle<Value> RsaWrap::PublicEncrypt(const Arguments& args) {
return Undefined();
}
+ int padding = RSA_PKCS1_OAEP_PADDING;
+ if (!getArgIntIfExists(args, 1, &padding)) { return Undefined(); }
+
int ret = RSA_public_encrypt(length, (unsigned char *) data,
(unsigned char *) node::Buffer::Data(result),
- obj->rsa, RSA_PKCS1_OAEP_PADDING);
+ obj->rsa, padding);
if (ret < 0) {
// TODO: Will this leak the result buffer? Is it going to be gc'ed?
View
10 test/fixture.js
@@ -57,6 +57,15 @@ var PRIVATE_CIPHERTEXT_HEX =
"a0f0969804d4968fb2e6220db5cf02e2c2200ff9d0a5a5037ac859a55c005ecc" +
"52ce194a6a9624c71547c96cf90d911caa4097f9cdfded71d23c9f8f5551188c" +
"8326357d54224ab25b9f29c1efdbc960a0968e4c9027cd507ffadd8dff93256c";
+var PRIVATE_OLD_PAD_CIPHER_HEX =
+ "69d1c385929fc00f89aa98ae9cd8529afe884b581505acdcd4ceaa10bfda9adc" +
+ "79c472dd7e35bcc94f1146459c6a8d96e572116c7a62f1da5dd18cdb8f81e72b" +
+ "4a4649f40470e88c11b04fdf72e48c6adb44c41edc0c4c56074a041c03017f72" +
+ "f66a000066a4dbe888119c83f79e7cb8f667f0af1af41cf4adf21320fada9355" +
+ "6d056a2fdb1f5a9f5708e096a7408a115efa14f0e2f94feaa32322aa4af9c97a" +
+ "438d205f62317020e657c5057227a3d7e60a6a6658781cf41b0820988a4f9e8e" +
+ "b947c424248d231c3e43c711b0c4a4342a0fa484d0e3ded231a695250f4dafcf" +
+ "f9e94d02e3f74d4c509cfae24b8615e619805c9cdc9e85faed7d706dd6891383";
var PUBLIC_CIPHERTEXT_HEX =
"16b5e95a02db09e95bb5419998b3c5f450571578be271602828740242236e6aa" +
"0bce325d6b9a681038c864e0877a3e68e20329a3602829128385f182a20f06c7" +
@@ -116,6 +125,7 @@ module.exports = {
PLAINTEXT_SHA256: PLAINTEXT_SHA256,
PLAINTEXT_SHA256_SIGNATURE: PLAINTEXT_SHA256_SIGNATURE,
PRIVATE_CIPHERTEXT_HEX: PRIVATE_CIPHERTEXT_HEX,
+ PRIVATE_OLD_PAD_CIPHER_HEX: PRIVATE_OLD_PAD_CIPHER_HEX,
PRIVATE_KEY: PRIVATE_KEY,
PRIVATE_KEY_2: PRIVATE_KEY_2,
PUBLIC_CIPHERTEXT_HEX: PUBLIC_CIPHERTEXT_HEX,
View
27 test/native.js
@@ -12,9 +12,10 @@
var assert = require("assert");
-var fixture = require("./fixture");
-var RsaWrap = fixture.RsaWrap;
-var textToNid = fixture.ursaNative.textToNid;
+var fixture = require("./fixture");
+var RsaWrap = fixture.RsaWrap;
+var ursaNative = fixture.ursaNative;
+var textToNid = ursaNative.textToNid;
/*
@@ -200,6 +201,10 @@ function test_privateDecrypt() {
var encoded = new Buffer(fixture.PRIVATE_CIPHERTEXT_HEX, fixture.HEX);
var decoded = rsa.privateDecrypt(encoded).toString(fixture.UTF8);
assert.equal(decoded, fixture.PLAINTEXT);
+
+ var encoded = new Buffer(fixture.PRIVATE_OLD_PAD_CIPHER_HEX, fixture.HEX);
+ var decoded = rsa.privateDecrypt(encoded, ursaNative.RSA_PKCS1_PADDING).toString(fixture.UTF8);
+ assert.equal(decoded, fixture.PLAINTEXT);
}
function test_fail_privateDecrypt() {
@@ -225,6 +230,11 @@ function test_fail_privateDecrypt() {
rsa.privateDecrypt(new Buffer("x"));
}
assert.throws(f3, /decoding error/);
+
+ function f4() {
+ rsa.privateDecrypt(new Buffer("x"), "str");
+ }
+ assert.throws(f4, /Expected a 32-bit integer/);
}
function test_publicEncrypt() {
@@ -242,6 +252,12 @@ function test_publicEncrypt() {
encoded = priv.publicEncrypt(plainBuf);
decoded = priv.privateDecrypt(encoded).toString(fixture.UTF8);
assert.equal(decoded, fixture.PLAINTEXT);
+
+ // Test with old-style padding.
+ var encoded = rsa.publicEncrypt(plainBuf, ursaNative.RSA_PKCS1_PADDING);
+ var decoded = priv.privateDecrypt(encoded, ursaNative.RSA_PKCS1_PADDING);
+ decoded = decoded.toString(fixture.UTF8);
+ assert.equal(decoded, fixture.PLAINTEXT);
}
function test_fail_publicEncrypt() {
@@ -265,6 +281,11 @@ function test_fail_publicEncrypt() {
rsa.publicEncrypt(new Buffer(2048));
}
assert.throws(f3, /too large/);
+
+ function f4() {
+ rsa.publicEncrypt(new Buffer("x"), "str");
+ }
+ assert.throws(f4, /Expected a 32-bit integer/);
}
function test_privateEncrypt() {
View
2 test/test.js
@@ -531,4 +531,4 @@ test_matchingPublicKeys();
testSigner();
testVerifier();
-console.log("All tests pass!");
+console.log("All tests pass!");

1 comment on commit 5aa7b5d

@danfuzz

Other than the one comment, LGTM.

Please sign in to comment.