3
$\begingroup$

Is it possible to use an ES512 key pair for RS512 signatures? Is it possible to use an RS512 key pair for ES512 signatures? I've also posted an issue in the NodeJS jwa package.

I created a key pair for EC512 with the following commands:

openssl ecparam -genkey -name secp521r1 -noout -out ecdsa-p521-private.pem
openssl ec -in ecdsa-p521-private.pem -pubout -out ecdsa-p521-public.pem

Then, instead of using ECDSA as I should, I used RSA:

jwa = require('jwa');
rsa = jwa('RS512');
signature = rsa.sign('hello', fs.readFileSync('ecdsa-p521-private.pem').toString());
result = rsa.verify('hello', signature, fs.readFileSync('ecdsa-p521-public.pem').toString());

And the result was true!

Is this an expected behaviour? Is it possible to use ECDSA keys for RSA and vice versa?

Thanks

--- EDIT ---

Here's a list of possible algorithms to choose from (for my json-web-token implementation), so we will have common terminology:

  • HS256: HMAC using SHA-256 hash algorithm
  • HS384: HMAC using SHA-384 hash algorithm
  • HS512: HMAC using SHA-512 hash algorithm
  • RS256: RSASSA using SHA-256 hash algorithm
  • RS384: RSASSA using SHA-384 hash algorithm
  • RS512: RSASSA using SHA-512 hash algorithm
  • ES256: ECDSA using P-256 curve and SHA-256 hash algorithm
  • ES384: ECDSA using P-384 curve and SHA-384 hash algorithm
  • ES512: ECDSA using P-521 curve and SHA-512 hash algorithm

--- EDIT ---

Following Squeamish Ossifrage's suggestion to use ed25519, here is my Node.JS code for signing and verifying "nonstandard ed25519 json-web-tokens".

const jwt = require('jsonwebtoken');
const base64url = require('base64url');
const ed25519 = require('ed25519');

const ed25519Header1 = base64url.encode(Buffer.from('{"alg":"Ed25519","typ":"JWT"}'));
const ed25519Header2 = base64url.encode(Buffer.from('{"typ":"JWT","alg":"Ed25519"}'));
const noneHeader = base64url.encode(Buffer.from('{"alg":"none","typ":"JWT"}'));

function sign(obj, privateKey, options) {
  const noneToken = jwt.sign(obj, '', { ...options, algorithm: "none" });
  if (jwt.decode(noneToken, {complete: true}).header.typ !== 'JWT') {
    throw new Error("Invalid object to sign");
  }
  const [, payload] = noneToken.split('.');
  const signature = base64url.encode(ed25519.Sign(
    Buffer.from(`${ed25519Header1}.${payload}`),
    { privateKey } // passing an object makes sure we want to sign with private-key, not seed.
  ));
  return `${ed25519Header1}.${payload}.${signature}`;
}


function verify(token, publicKey, options) {
  const [tokenHeader, payload, signature, ...leftover] = token.split('.');
  if ((tokenHeader !== ed25519Header1) && (tokenHeader !== ed25519Header2)) {
    throw new Error("ed25519 jwt malformed - unexpected header");
  }
  const signatureBuffer = base64url.toBuffer(signature || '');
  if (base64url.encode(signatureBuffer) !== signature) {
    throw new Error("ed25519 jwt malformed - invalid signature format");
  }
  // signature is a string, therefore payload is a string (not undefined)
  if (!ed25519.Verify(
    Buffer.from(`${tokenHeader}.${payload}`),
    base64url.toBuffer(signature),
    publicKey
  )) {
    throw new Error("Invalid signature");
  }
  return jwt.verify([noneHeader, payload, '', ...leftover].join('.'), '', {
    ...options,
    algorithms: ['none']
  });
}

a pair of publicKey and privateKey can be generate with the MakeKeyPair(...) function of the ed25519 package (privateKey should be 64 bytes long and publicKey should be 32 bytes long).

The code allows to enjoy both ed25519 signing, and the features of the jsonwebtoken package.

For example:

var token = sign({hello:"world"}, privateKey, {expiresIn:'1 minute'})
console.log(verify(t, publicKey)); // prints { hello: 'world', iat: 1526078738, exp: 1526078798 }
// After 1 minute:
verify(t, publicKey); // Throws "TokenExpiredError: jwt expired"
$\endgroup$
4
  • 1
    $\begingroup$ This shouldn't work. Maybe the library internally does some remapping from RSA to ECDSA? Also RSA-512 is very badly broken, to the point were any private person can just break any keypair given less thn 100USD, so don't use it in production! $\endgroup$
    – SEJPM
    May 11, 2018 at 18:19
  • $\begingroup$ What should I use instead (for asymetric signature)? How to generate the keys? Added a list of options. $\endgroup$
    – Ozo
    May 11, 2018 at 18:52
  • $\begingroup$ Please let me know what you think of my nonstandard ed25519-jwt code. Do you see any reason not to publish it to npm? $\endgroup$
    – Ozo
    May 11, 2018 at 23:46
  • $\begingroup$ This isn't really a discussion forum—best to keep the question as it was originally, and if you want to ask another question, make a separate question. Best to focus on the general procedure—like what to do with a key designated for Ed25519, and whether to blindly apply whatever algorithm the metadata on the forgery says to apply (answer: NOOOO!)—than on particulars of nodejs code review. $\endgroup$ May 12, 2018 at 13:39

2 Answers 2

13
$\begingroup$

From the source code:

function createKeySigner(bits) {
 return function sign(thing, privateKey) {
    if (!bufferOrString(privateKey) && !(typeof privateKey === 'object'))
      throw typeError(MSG_INVALID_SIGNER_KEY);
    thing = normalizeInput(thing);
    // Even though we are specifying "RSA" here, this works with ECDSA
    // keys as well.
    var signer = crypto.createSign('RSA-SHA' + bits);
    var sig = (signer.update(thing), signer.sign(privateKey, 'base64'));
    return base64url.fromBase64(sig);
  }
}

See in particular the comment. What happens here is that even though you specified "RSA with SHA-512" as signature algorithm (the 'RS512' parameter, in that library's weird terminology), the code perfectly knows that the private key is for ECDSA, not RSA (look at the contents of the private key file, it starts with "BEGIN EC PRIVATE KEY"), and so it "helpfully" uses ECDSA instead of RSA, transparently.

No mystery here then, just some Node.js code doing Node.js stuff (i.e. doing what it can instead of what you asked for, and hoping for the best -- a rather dubious methodology for all things related to security, in particular cryptography).

$\endgroup$
3
  • $\begingroup$ Just posted a link to your answer on the issue comments. I think this should technically be the right answer to my question as it solves the mystery - even though @squeamish-ossifrage 's answer was also very helpful. $\endgroup$
    – Ozo
    May 11, 2018 at 19:30
  • $\begingroup$ I just love that last section of the answer. It seems paramount to everything ECMAScript, and it completely leaves everybody guessing. And it is impossible to base any security proof or review on guesswork. $\endgroup$
    – Maarten Bodewes
    May 12, 2018 at 2:00
  • $\begingroup$ The library's terminology follows the JSON Web standard; where that came from I don't know. $\endgroup$ May 13, 2018 at 9:23
5
$\begingroup$

NOOOOOOOOOOOOOOOOOOOOOOOOOOOOO!

You are aiming a gun at your foot and your finger is on the trigger and you're getting ready to pull it! Don't do it, buddy! Your foot's got a lot to live for! Also the library you're using is probably broken.

  1. ECDSA and RSA are completely unrelated families of cryptosystems. They are in no way compatible whatsoever.
  2. JWT is a catastrophe waiting to happen. It's designed to let an adversary control what algorithms use to verify things, including using symmetric-key algorithms with public-key verification, which enables trivial forgery. Just say no to JWT!
  3. If RS512 means RSA with a 512-bit modulus, it is completely broken. (If it doesn't mean that, then it's not very well-named.)
  4. If EC512 means ECDSA with curve secp521r1 (which is a really bad choice of nomenclature that indicates the authors of the library you're using aren't interested in citing serious crypto standards or literature under the standard well-recognized names), it's a slow but reasonable choice if you are going to use ECDSA at all. secp256r1 would be a more conventional choice at a reasonable 128-bit security level, as would secp256k1, as Bitcoin uses.
  5. ECDSA is an archaic slow signature scheme that is full of sharp edges: if your RNG is wedged any time you make a signature (not just when you generate the key), then you may leak the private key. It's possible your ECDSA implementation uses RFC 6979 to avoid this, but are you confident it does? I'm not!
  6. Please just use Ed25519 and don't let the document you're verifying dictate what signature scheme you use like JWT naively does.
$\endgroup$
9
  • $\begingroup$ 7. Avoid using two keys for different purposes. Especially asymmetric keys. $\endgroup$ May 11, 2018 at 18:26
  • $\begingroup$ a. Just for the record, the node-jwa has over 1M downloads per week on npm: npmjs.com/package/jwa . The very popular jsonwebtoken package uses it. It is not "my broken library", it is "everybody's broken library". b. Which of the listed algorithm should I use for asymetric json-web-token in 2018? What command should I run to generate the keys? I've added the list of algorithms to the question. $\endgroup$
    – Ozo
    May 11, 2018 at 18:44
  • 2
    $\begingroup$ OK, just found this: auth0.com/blog/… (tldr; all of the listed asymetric algorithms are broken). I will start googling for a NodeJS package for signing and verifying json-web-tokens that supports Ed25519. FML :( $\endgroup$
    – Ozo
    May 11, 2018 at 18:59
  • $\begingroup$ Ed25519 is not really in the json-web-token standard, right? $\endgroup$
    – Ozo
    May 11, 2018 at 19:09
  • 1
    $\begingroup$ I took your advice and published jsonwebtoken-ed25519. $\endgroup$
    – Ozo
    May 23, 2018 at 13:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.