1

I want to manually generate the Public key from Google-Sign-in's JWK data here; https://www.googleapis.com/oauth2/v3/certs

{
  "n": "3aOynmXd2aSH0ZOd0TIYd5RRaNXhLW306dlYw26nMp6QPGaJuOeMhTO3BO8Zt_ncRs4gdry4mEaUOetCKTUOyCCpIM2JAn0laN_iHfGKTYsNkjr16FiHWYJmvNJ1Q1-XXjWqNNKMFIKHKtMrsP2XPVD6ufp-lNQmt4Dl0g0qXJ4_Y_CKuP-uSlFWZuJ_0_2ukUgevvKtOZNcbth0iOiFalBRDr-2i1eNSJWOknEphy7GRs-JGPboTdHC7A3b-0dVFGMEMJFhxcEJHJgLCsQGdYdkphLJ5f21gCNdhp3g16H3Cqts2KTXgO4Rr8uhwZx5yiUjTuizD9wc7uDso4UJ7Q",
  "use": "sig",
  "kty": "RSA",
  "kid": "b6f8d55da534ea91cb2cb00e1af4e8e0cdeca93d",
  "alg": "RS256",
  "e": "AQAB"
},

How should I go about creating the key using the modulus and exponent?

I tried following steps in this question; Generate RSA Public Key from Modulus and Exponent But my lang is saying that the given modulus is not a hex string. Also there is some missing info like what is the berData function there.

All solutions to similar problems I have seen here seem to be language/framework specific. I would very much prefer some pseudocode that I can follow.

Rishav Sharan
  • 2,763
  • 8
  • 39
  • 55
  • The format looks like a JWK (Json Web key) and there are a lot of libraries available that can consume it, see https://jwt.io. What language Do you prefer? – Michael Fehr Jul 09 '21 at 19:16
  • I would actually prefer a language agnostic pseudocode. The lang that i am using has too tiny an ecosystem for any such libs and I would like to add one if I can. – Rishav Sharan Jul 10 '21 at 07:35
  • The question is not clear to me. Google Sign-in and the first link are related to OAuth2. The posted public key is used by OAuth2 to validate access tokens. The second link is independent of this and deals with the conversion to a PEM encoded key. Why do you need this conversion for OAuth2, or does OAuth2 not matter at all and it's just about key conversion? And what format should the key have (PKCS#8, PKCS#1,...)? Can you please elaborate on that a little bit. – Topaco Jul 10 '21 at 08:41
  • so i am trying to add a google sign in button to my site which is built in the Crystal language. Crytal has a jwt library which can verify tokens as long as the ppublic key is provided. However, google's public key as provided are in certificate or jwk format. So i need to extract the public key from either of the given formats. from what i have read so far, i can generate the public key from a jwk if i have the exponent and the modulus, but no answer goes into how it is done. all use a library or tool to do so, which are not available to me in Crystal. – Rishav Sharan Jul 10 '21 at 08:45
  • And what should the target format be, a PEM-encoded public key in X.509/SPKI format? That should be clear from the Crystal specification (there are different formats e.g. also PKCS#1). – Topaco Jul 10 '21 at 08:57
  • I fear I am not aware of the spec names but here is an example of the public key which is supported; ``` public_key = "-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5+5+xnWggxNnnmCSNbIwTQFjc\n" + "yawcvmPupeXs10sfhUAHUxtmT5zH3AI46JrRZN7KV5Ac5bQWzF9ZMPeHqmq5FBdY\n" + "ooIF8W7lVtYx23OQX5vjFRN0LRY8hyOKL07Us+aUeMwDXX7M6o58XO4bqOh8pGOq\n" + "FLscCAkdAP9lDgeDGwIDAQAB\n" + "-----END PUBLIC KEY-----\n" ``` – Rishav Sharan Jul 10 '21 at 09:01
  • This is a PEM encoded key in X.509/SPKI format. There are also online converters for this, e.g.: https://8gwifi.org/jwkconvertfunctions.jsp – Topaco Jul 10 '21 at 09:05
  • Thanks. I want to handle this on the server side natively. – Rishav Sharan Jul 10 '21 at 09:15
  • A programmatic conversion is also possible (e.g. the second link shows a possible realization in Java). More generally, the keys are stored in ASN.1. For a pseudo code it is therefore probably helpful to orient on a corresponding ASN.1 definition of the X.509 format. You can get an idea of this by viewing your key in an ASN.1 parser, such as https://lapo.it/asn1js. – Topaco Jul 10 '21 at 09:20

2 Answers2

1

It turns out that this question already has an answer on SO to a large extent - https://stackoverflow.com/a/29707204/2862341.

It does a much better job than I could have imagined at this point.

To provide you with the rest of the details for what I believe you'll need to complete the task - the JWT encoding is base64-url-encoded (https://stackoverflow.com/a/55389212/2862341).

Also for completeness here is a sample java code

package com.example.so.q68285091;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;


/*

------------------------------------------
https://www.googleapis.com/oauth2/v3/certs
------------------------------------------

{
  "keys": [
    {
      "kid": "1bf8a84d3ecd77e9f2ad5f06ffd260701dd06d90",
      "use": "sig",
      "n": "zw_voGnxIrSEnta0BxsS4Gmr0t106iCjmIK6nciUuppcUrDTKT3t3sZLsZ5Hl-WFzkGpo9u6jWj3ul64hcpgFTRJk5xIUnbKZ20za_IRfWDTZqhZ3LqeHqBQaw0BekUzICsw6Ip1-3kg3QoQyoU973Os6MAfpOP7SafcHp_tTRoMFAc7R3AWZdCmlMmw-xYLTT3UdI2hycXbTYOitFkCTYO4pL62IBt5inlOup4dk0HwazBp0zMYxyb49y1gqh_rfpswTSA7lVzySyU9SoXAWFHZW4rQrhiXwLOPz8cynr67lE5_D5t0XhLTfn5R2eV4k8bIpfoSDtMPHm-azuQnuQ",
      "e": "AQAB",
      "kty": "RSA",
      "alg": "RS256"
    },
    {
      "kty": "RSA",
      "n": "nQPB_WqGG18pjGSFGQwRLcZkcRojHHweN27mV1oTNeeH2quq5NvWibLEheiukVP60nXcGNpkP_PaycYahEfvAnJGLX_IscGAOJ67WWFs4M8wXHH6g2mTnalcAYgmpN1QDMVgz4NcWISXNTR-8FZfWgFN4LDZgK4f0wXOaJlh_Bzh-plPLJQUXyY7mZTEVsH8X3wg2fvV0Hxj_HudjgFlYPdDri1Oi4vI0wiKV4nJCRZ-INH3OIvPl-05WVjZ-XTSXdNjLNx35NM2Npcrr9VpZ8Xeg7pr0wjamqd_07xfEAdtFxsN6Ay6Ecz3k0onQP-6SLRCGLrMAxifziivmmafCQ",
      "e": "AQAB",
      "use": "sig",
      "kid": "7f548f6708690c21120b0ab668caa079acbc2b2f",
      "alg": "RS256"
    }
  ]
}

------------------------------------------

Output:
-------

X509 Public Key :
-----BEGIN PUBLIC KEY-----
MIIBejANBgkqhkiG9w0BAQEFAAOCAWcAMIIBYgKCAVh6dy92b0dueElyU0VudGEw
QnhzUzRHbXIwdDEwNmlDam1JSzZuY2lVdXBwY1VyRFRLVDN0M3NaTHNaNUhsK1dG
emtHcG85dTZqV2ozdWw2NGhjcGdGVFJKazV4SVVuYktaMjB6YS9JUmZXRFRacWha
M0xxZUhxQlFhdzBCZWtVeklDc3c2SXAxKzNrZzNRb1F5b1U5NzNPczZNQWZwT1A3
U2FmY0hwL3RUUm9NRkFjN1IzQVdaZENtbE1tdyt4WUxUVDNVZEkyaHljWGJUWU9p
dEZrQ1RZTzRwTDYySUJ0NWlubE91cDRkazBId2F6QnAwek1ZeHliNDl5MWdxaC9y
ZnBzd1RTQTdsVnp5U3lVOVNvWEFXRkhaVzRyUXJoaVh3TE9QejhjeW5yNjdsRTUv
RDV0MFhoTFRmbjVSMmVWNGs4YklwZm9TRHRNUEhtK2F6dVFudVE9PQIEQVFBQg==
-----END PUBLIC KEY-----


 */
public class RSAPublicKeyFromModulus {

    public static void main(String[] args) {
        
        
        String modulusStr = "zw_voGnxIrSEnta0BxsS4Gmr0t106iCjmIK6nciUuppcUrDTKT3t3sZLsZ5Hl-WFzkGpo9u6jWj3ul64hcpgFTRJk5xIUnbKZ20za_IRfWDTZqhZ3LqeHqBQaw0BekUzICsw6Ip1-3kg3QoQyoU973Os6MAfpOP7SafcHp_tTRoMFAc7R3AWZdCmlMmw-xYLTT3UdI2hycXbTYOitFkCTYO4pL62IBt5inlOup4dk0HwazBp0zMYxyb49y1gqh_rfpswTSA7lVzySyU9SoXAWFHZW4rQrhiXwLOPz8cynr67lE5_D5t0XhLTfn5R2eV4k8bIpfoSDtMPHm-azuQnuQ";
        String exponentStr = "AQAB";
        
        byte[] modulusBytes = base64URLDecode(modulusStr);
        byte[] exponentBytes = base64URLDecode(exponentStr);
        
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            
            PublicKey publicKey = keyFactory.generatePublic(new RSAPublicKeySpec(new BigInteger(modulusBytes), new BigInteger(exponentBytes)));
            
            X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
            
            String x509EncodedKeyStr = new String(Base64.getEncoder().encode(encodedKeySpec.getEncoded()));
            String prefix = "-----BEGIN PUBLIC KEY-----";
            String newline =  System.getProperty("line.separator") ;// "\r\n";
            String suffix = "-----END PUBLIC KEY-----";
            
            StringBuilder result =  new StringBuilder();
            // prefix + newline + x509EncodedKeyStr + newline + suffix
            result.append(prefix);
            result.append(newline);
            for(int i=0;i<x509EncodedKeyStr.length();) {
                String temp = x509EncodedKeyStr.substring(i,i+64);
                result.append(temp);
                result.append(newline);
                i = i+64;
            }
            result.append(suffix);
            
            System.out.println("X509 Public Key :");
            System.out.println(result);
            
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        
        

    }
    
    
    /**
     * <p> https://datatracker.ietf.org/doc/html/rfc7515#appendix-C </p>
     * @param base64URlEncodedString
     * @return
     */
    private static String base64URLEncode(byte[] base64URlEncodedString) {

        String tempResult = new String(base64URlEncodedString);
        tempResult = tempResult.split("=")[0];
        tempResult = tempResult.replace("+", "-");
        tempResult = tempResult.replace("_", "/");

        return tempResult;

    }
    
    
    /**
     * <p> https://datatracker.ietf.org/doc/html/rfc7515#appendix-C </p>
     * 
     * @param base64URLEncodedString
     * @return
     */
    private static byte[] base64URLDecode(String base64URLEncodedString) {

        int size = base64URLEncodedString.length();
        String tempResult = base64URLEncodedString;
        tempResult = tempResult.replace("-", "+");
        tempResult = tempResult.replace("_", "/");

        int padding = size % 4;
        switch (padding) {
        case 0:
            break;
        case 2:
            tempResult = tempResult.concat("==");
            break;
        case 3:
            tempResult = tempResult.concat("=");
            break;

        default:
            throw new IllegalArgumentException("Invalid base64urlencoded string");
        }

        return tempResult.getBytes();
    }

}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Ravindra HV
  • 2,558
  • 1
  • 17
  • 26
1

Here is a very reductive implementation in Crystal-lang

require "base64"
 
pp! jwk = {
  "n": "3aOynmXd2aSH0ZOd0TIYd5RRaNXhLW306dlYw26nMp6QPGaJuOeMhTO3BO8Zt_ncRs4gdry4mEaUOetCKTUOyCCpIM2JAn0laN_iHfGKTYsNkjr16FiHWYJmvNJ1Q1-XXjWqNNKMFIKHKtMrsP2XPVD6ufp-lNQmt4Dl0g0qXJ4_Y_CKuP-uSlFWZuJ_0_2ukUgevvKtOZNcbth0iOiFalBRDr-2i1eNSJWOknEphy7GRs-JGPboTdHC7A3b-0dVFGMEMJFhxcEJHJgLCsQGdYdkphLJ5f21gCNdhp3g16H3Cqts2KTXgO4Rr8uhwZx5yiUjTuizD9wc7uDso4UJ7Q",
  "use": "sig",
  "kty": "RSA",
  "kid": "b6f8d55da534ea91cb2cb00e1af4e8e0cdeca93d",
  "alg": "RS256",
  "e": "AQAB"
}
 
mod_hex = Base64.decode(jwk["n"]).hexstring
exp_hex = Base64.decode(jwk["e"]).hexstring
 
pub_key_hex = "30820122300D06092A864886F70D01010105000382010F003082010A0282010100" + mod_hex + "0203" + exp_hex
 
pp! pub_key = Base64.encode(String.new(pub_key_hex.hexbytes))
Rishav Sharan
  • 2,763
  • 8
  • 39
  • 55