javascriptauthenticationjwtrsanode-rsa

Should I use a public key or private key to generate modulus and exponent for a JWKS endpoint?


I tried searching for an answer to this question and didn't find anything. So, I'm asking and answering.

I'm trying to implement a JWKS endpoint. I found some example code here. Note that the author's intent for that repo is testing only. He is clear that he doesn't know enough about crypto to know if it's actually secure.

In the example, the private key is used to generate the modulus and exponent:

const forge = require('node-forge')
const NodeRSA = require('node-rsa')

const helperKey = new NodeRSA()
helperKey.importKey(forge.pki.privateKeyToPem(privateKey))
const { n: modulus, e: exponent } = helperKey.exportKey('components')

Since those values are then made publicly available (via the JWKS endpoint), can the modulus and exponent be used to somehow obtain the private key? Should I be using the public key to generate the modulus and exponent instead?


Solution

  • TL;DR: You should be using the public modulus and exponent

    I tried using the public key to generate the modulus and exponent, used them in the JWKS endpoint, and it worked; it verified JWTs. This is what it looks like to generate the modulus and exponent using the public key:

    const forge = require('node-forge')
    const NodeRSA = require('node-rsa')
    
    const helperKey = new NodeRSA()
    helperKey.importKey(forge.pki.publicKeyToPem(publicKey))
    const { n: modulus, e: exponent } = helperKey.exportKey('components-public')
    

    Since the verification works using both, it made me suspect the modulus and exponent are the same when using the public or the private key. I compared the modulus and exponent generated using both keys and they are the same.

    Looking into this a bit more, public and private keys share the same modulus. So it makes sense that the modulus is the same in both cases. Also, the impression I get from the node-rsa documentation is that e is the public exponent. So components and components-public will return the same value for e. Furthermore, this value will always be 65537 because, according to RSA:

    The signing algorithm SHOULD use a public exponent of 65537.

    So, the conclusion is that you should be using the public modulus and exponent. But if you're using node-rsa, exportKey will return the shared modulus and public exponent in either case.