Search code examples
phpjwtpublic-keyjwk

How to reconstruct an RSA public key from JWK


I am working in a restricted environment and don't have the ability to utilise any third party systems that would make this simple, such as Firebase/php-jwt

I have received a JWT I would like to verify. I have a JWK from which I need to reconstruct the public key, to complete the verification.

The JWK is as follows

{
"kty": "RSA",
"use": "enc",
"n": "ozmvkuGzWNHs9cEcC5PWwbG-dmSjPcoQFxEbqH_fBjkj_nfTTKshdiSq5ciulWEa_rrqQ2qwcSADNxtTzRR1qfud-NvsM8VltT7xDuVVqPTZoWLKa0BWXgQQ-1mCm1KdGltYWccB0R1LoF-rb3DEEZySsHvqErYzYt4M_rqjEiK5Y9y1h3k1h5Yk4zGLWchko3jiDS-pVevvWsQsN-Y3KuB19485G9P_MXLtfJWQ4wC4jlo9etdD_hgDfxX-hQy3wuwHfHifLdxvxiB8X5Is4m6DuY4_7hS5RwXAjO1QSd-zUYZNT_2yWVR56_jyiZEiOdgIm9QtLPZCTKzqsXoqZQ",
"e": "AQAB"
}

The code I am using to generate the public key is as follows

$jwk = json_decode($jwk);

$rsaPublicKey = "-----BEGIN PUBLIC KEY-----\n";
$rsaPublicKey .= "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA{$jwk->modulus}";
$rsaPublicKey .= "{$jwk->exponent}\n-----END PUBLIC KEY-----";

$isSignatureValid = openssl_verify($dataToVerify, $signature, $rsaPublicKey, OPENSSL_ALGO_SHA256);

This results in an error openssl_verify(): supplied key param cannot be coerced into a public key

I feel like I'm missing something obvious and I am somehow generating the key incorrectly. I tried asking ChatGPT for help but it gave me many different solutions, all of which led back to the same error. Further reading has led me to some ideas similar to the AI suggestions, but again all led to the same error

These included:

  • Trying various different whitespacing, from none at all, to chunking by 64 chars
  • Encoding the modulus and exponent in different ways (I didn't understand these suggestions at all)
  • Prepending the fixed string MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA to the modulus, this seems to be something to do with RSA keys, but I don't really understand what this is

I'm really not sure what else I can do here, this feels like it should be such a simple thing, but I'm just not getting it and I've run out of angles to approach


Solution

  • I thought it might just be a simple formatting issue some simple code could fix, so I wrote the following to try to produce a valid pem. The code runs with your JWT and the example pem shown without emitting the '...cannot be coerced into a public key' error. That doesn't mean it is correct. Anyway, you can play with this. It might be useful, or not.

    <?php
      
      $json = array();  
      $json['kty'] = 'RSA';
      $json['use'] = 'enc';
      $json['n'] = 'ozmvkuGzWNHs9cEcC5PWwbG-dmSjPcoQFxEbqH_fBjkj_nfTTKshdiSq5ciulWEa_rrqQ2qwcSADNxtTzRR1qfud-NvsM8VltT7xDuVVqPTZoWLKa0BWXgQQ-1mCm1KdGltYWccB0R1LoF-rb3DEEZySsHvqErYzYt4M_rqjEiK5Y9y1h3k1h5Yk4zGLWchko3jiDS-pVevvWsQsN-Y3KuB19485G9P_MXLtfJWQ4wC4jlo9etdD_hgDfxX-hQy3wuwHfHifLdxvxiB8X5Is4m6DuY4_7hS5RwXAjO1QSd-zUYZNT_2yWVR56_jyiZEiOdgIm9QtLPZCTKzqsXoqZQ';
      $json['e'] = 'AQAB';
      $rsaPublicHeader = "-----BEGIN PUBLIC KEY-----\n";
      $rsaPublicBody = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA".strtr($json['n'].'ID'.$json['e'], '-_', '+/');
      $rsaPublicFooter = "-----END PUBLIC KEY-----";
      $pem = $rsaPublicHeader;
      $rsaPublicBodyLen = strlen($rsaPublicBody);
      for ($i = 0; $i<$rsaPublicBodyLen; $i+=64) {
        $pem .= substr($rsaPublicBody, $i, 64)."\n";
      }
      $pem .= $rsaPublicFooter;
      $dataToVerify = "";
      $signature = "";
      /*
      // Example pem taken from https://github.com/Strobotti/php-jwk
      $pem = "-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dGQ7bQK8LgILOdLsYzf
    ZjkEAoQeVC/aqyc8GC6RX7dq/KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdD
    Nq1n52TpxQwI2EqxSk7I9fKPKhRt4F8+2yETlYvye+2s6NeWJim0KBtOVrk0gWvE
    Dgd6WOqJl/yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X+Tip84wqwyRpU
    lq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll+p/Dg8vAXxJLIJ4SNLcqgFeZe
    4OfHLgdzMvxXZJnPp/VgmkcpUdRotazKZumj6dBPcXI/XID4Z4Z3OM1KrZPJNdUh
    xwIDAQAB
    -----END PUBLIC KEY-----";
    */
      echo $pem."\n";
      $isSignatureValid = openssl_verify($dataToVerify, $signature, $pem, OPENSSL_ALGO_SHA256);
      if ($isSignatureValid) {
        echo ("Valid\n");
      } else {
        echo ("Invalid\n");
      }
    ?>
    

    Output

    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAozmvkuGzWNHs9cEcC5PW
    wbG+dmSjPcoQFxEbqH/fBjkj/nfTTKshdiSq5ciulWEa/rrqQ2qwcSADNxtTzRR1
    qfud+NvsM8VltT7xDuVVqPTZoWLKa0BWXgQQ+1mCm1KdGltYWccB0R1LoF+rb3DE
    EZySsHvqErYzYt4M/rqjEiK5Y9y1h3k1h5Yk4zGLWchko3jiDS+pVevvWsQsN+Y3
    KuB19485G9P/MXLtfJWQ4wC4jlo9etdD/hgDfxX+hQy3wuwHfHifLdxvxiB8X5Is
    4m6DuY4/7hS5RwXAjO1QSd+zUYZNT/2yWVR56/jyiZEiOdgIm9QtLPZCTKzqsXoq
    ZQIDAQAB
    -----END PUBLIC KEY-----
    Invalid