Search code examples
jwtrsagoogle-signinpublic-keyverify

Generic way to create Google sign in's public key using modulus and exponent


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.


Solution

  • 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();
        }
    
    }