Генерация открытого ключа из JWK

Предположим, у меня есть следующий JWK в качестве десериализованного тела некоторого JWS (RFC7515), где модуль n частично опущен для отображения

{
   "kty": "RSA",
   "e": "AQAB",
   "kid": "KAgNpWbRyy9Mf2rikl498LThMrvkbZWHVSQOBC4VHU4",
   "n": "llWmHF8XA2KNLdmxOP3kxD9OY76p0Sr37j..."
}

Заголовок JWS определяет поля alg и kid, необходимые для проверки подписи.

Как мне создать открытый ключ RSA из этого JWK, чтобы я мог проверить подпись? Посмотрев на некоторые связанные questions, у меня есть следующая реализация Java, которая пытается создать открытый ключ RSA из n и e поля в JWK

public void someMethod(){
    String exjws ="eyJhbGciOiJSUzI1NiIsImtpZCI6IktBZ05wV2JSeXk5TWYycmlrbDQ5OExUaE1ydmtiWldIVlNRT0JDNFZIVTQiL"
        + "CJodG0iOiJwb3N0IiwiaHR1IjoiL3R4IiwidHMiOjE2MDM4MDA3ODN9.eyJjYXBhYmlsaXRpZXMiOltdLCJjbGllbnQiOnsia2V5Ijp7Imp3ayI6eyJrdHkiOiJSU0EiLCJ"
        + "hbGciOiJSUzI1NiIsImUiOiJBUUFCIiwia2lkIjoiS0FnTnBXYlJ5eTlNZjJyaW"
        + "tsNDk4TFRoTXJ2a2JaV0hWU1FPQkM0VkhVNCIsIm4iOiJsbFdtSEY4WEEyS05MZG14T1Aza3hEOU9ZNzZwMFNyMzdqZmh6OTRhOTN4bTJGTnFvU1BjUlpBUGQwbHFEUzhO"
        + "M1VpYTUzZEIyM1o1OU93WTRicE1fVmY4R0p2dnB0TFdueG8xUHlobVByIC0gZWNkU0NSUWRUY19aY01GNGhSVjQ4cXFsdnVEMG1xdGNEYklrU0JEdmNjSm1aSHdmVHBESG"
        + "luVDh0dHZjVlA4VmtBTUFxNGtWYXp4T3BNb0lSc295RXBfZUNlNXBTd3FIbzBkYUNXTktSI"
        + "C0gRXBLbTZOaU90ZWRGNE91bXQ4TkxLVFZqZllnRkhlQkRk"
        + "Q2JyckVUZDR2Qk13RHRBbmpQcjNDVkN3d3gyYkFRVDZTbHhGSjNmajJoaHlJcHE3cGM4clppYjVqTnlYS3dmQnVrVFZZWm96a3NodCAtIExvaHlBU2FLcFlUcDhMdE5aIC0gdyAifSw"
        + "icHJvb2YiOiJqd3MifSwibmFtZSI6Ik15IEZpcnN0IENsaWVu"
        + "dCIsInVyaSI6Imh0dHA6XC9cL2xvY2FsaG9zdFwvY2xpZW50XC9jbGllbnRJRCJ9LCJpbnRlcmFjdCI6eyJzdGFydCI6WyJyZWRpcmVjdCJ"
        + "dLCJmaW5pc2giOnsibWV0aG9kIjoicmVkaXJlY3QiLCJub25jZSI6ImQ5MDIxMzg4NGI4NDA5MjA1MzhiNWM1MSIsInVyaSI6Imh0dHA6XC9cL2xvY2FsaG9zdFwvY2xpZW50"
        + "XC9yZXF1ZXN0LWRvbmUifX0sImFjY2Vzc190b2tlbiI6eyJhY2Nlc3MiOlt7ImFjdGlvbnMiOlsicmVhZCIsInByaW50Il0sImxvY2F0aW9ucyI6WyJodHRwOlwvXC9sb2Nhb"
        + "Ghvc3RcL3Bob3RvcyJdLCJkYXRhdHlwZXMiOlsibWV0YWRhdGEiLCJpbWFnZXMiXSwidHlwZSI6InBob3RvLWFwaSJ9XX0sInN1YmplY3QiOnsic3ViX2lkcyI6WyJpc3Nfc3"
        + "ViIiwiZW1haWwiXX19.LUyZ8_fERmxbYARq8kBYMwzcd8GnCAKAlo2ZSYLRRNAYWPrp2XGLJOvg97WK1idf_LB08OJmLVsCXxCvn9mgaAkYNL_ZjHcusBvY1mNo0E1sdTEr31"
        + "CVKfC-6WrZCscb8YqE4Ayhh0Te8kzSng3OkLdy7xN4xeKuHzpF7yGsM52JZ0cBcTo6WrYEfGdr08AWQJ59ht72n3jTsmYNy9A6I4Wrvfgj3TNxmwYojpBAi"
        + "cfjnzA1UVcNm9F_xiSz1_y2tdH7j5rVqBMQife-k9Ewk95vr3lurthenliYSNiUinVfoW1ybnaIBcTtP1_YCxg_h1y-B5uZEvYNGCuoCqa6IQ";

    String[] parts = exjws.split("\\.");
    String payload = new Base64URL(parts[1]).decodeToString();
    JsonObject jwk  = JsonParser.parseString(payload).getAsJsonObject().get("client")
            .getAsJsonObject().get("key").getAsJsonObject().get("jwk").getAsJsonObject();

    BigInteger modulus = new BigInteger(1, new Base64URL(jwk.get("n").getAsString()).decode());  
    BigInteger exponent = new BigInteger(1, new Base64URL(jwk.get("e").getAsString()).decode());
    byte[] signingInfo = String.join(".",parts[0],parts[1]).getBytes(StandardCharsets.UTF_8);
    byte[] b64DecodedSig = new Base64(parts[2]).decode();
    
    PublicKey pub = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
    
    Signature verifier = Signature.getInstance("SHA256withRSA");    
    verifier.initVerify(pub);
    verifier.update(signingInfo);
    boolean okay = verifier.verify(b64DecodedSig);
    System.out.println(okay);
}

Результат verify() в настоящее время возвращает false.

Я пробовал сгенерировать пары ключей RSA, подписать и проверить, используя генерируемые ключи и которые работали. Я подозреваю, что моя проблема в том, что построенный ключ RSA в приведенном выше коде каким-то образом неверен. Любая помощь приветствуется.

ИЗМЕНИТЬ

Библиотека JSON Gson

JWK библиотека - это nismus-jose-jwt, которая предоставляет Base64 и Base64URl


person Scott    schedule 26.02.2021    source источник
comment
Код трудно проверить, потому что ссылки на Base64, Base64URL и вашу библиотеку JWK / JSON отсутствуют, а также parts не объяснены. При условии правильного декодирования Base64url n и e будет сгенерирован правильный ключ. Предположительно parts[0], parts[1] и parts[2] содержат заголовок, полезную нагрузку и подпись, каждый из которых закодирован в Base64url. Подпись должна быть декодирована Base64 url для вызова verify(), но, похоже, декодируется только Base64, так что, возможно, это проблема (хотя я ожидал бы здесь исключения). Пожалуйста, добавьте недостающую информацию.   -  person user 9014097    schedule 26.02.2021
comment
@Topaco объясняет в вопросе, что parts - это String[], состоящий из заголовка, полезной нагрузки и подписи. Обновлен вопрос, чтобы включить библиотеки.   -  person Scott    schedule 26.02.2021
comment
Вы пробовали расшифровать подпись, т.е. parts[2] с помощью Base64url вместо Base64?   -  person user 9014097    schedule 26.02.2021
comment
Да, получается false из verify()   -  person Scott    schedule 26.02.2021
comment
Вы можете опубликовать полный пример (JWK и JWT)?   -  person user 9014097    schedule 26.02.2021
comment
Я обновлю вопрос, чтобы включить строку JWS   -  person Scott    schedule 26.02.2021
comment
Для репродукции открытый ключ все еще отсутствует.   -  person user 9014097    schedule 26.02.2021
comment
n и e указаны в JWK, так что я могу получить ключ из этого, не так ли?   -  person Scott    schedule 26.02.2021
comment
Модуль и показатель степени, определенные в коде, соответствуют таковым в токене, и PublicKey также правильно выводится из них. Просто кажется, что открытый ключ в JWT не тот, который нужен для проверки. Также на jwt.io проверка не выполняется (вы можете сгенерировать требуемый ключ X.509 из своего ключа здесь 8gwifi.org/jwkconvertfunctions.jsp).   -  person user 9014097    schedule 26.02.2021


Ответы (2)


Из обсуждения с @Topaco в комментариях:

Код в вопросе успешно создает открытый ключ RSA из модуля n и экспоненты e.

Однако вызов Signature.verify() возвращает false, потому что открытый ключ, указанный в JWK в запросе, не соответствует ключу, используемому для подписи запроса.

person Scott    schedule 12.03.2021

Я использую библиотеку JWT для этой задачи (я знаю, что это немного излишне, но она работает ...): https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/

Они предоставляют простой в использовании интерфейс, и ниже вы найдете образец кода, который генерирует пару ключей RSA и распечатывает открытый ключ, затем преобразует этот открытый ключ в формат JWK (также распечатывает) с последующим окончательным преобразованием из JWK. в формат Java RSAPublicKey - исходный открытый ключ идентичен двойному преобразованному новому открытому ключу:

RSA converting between Java keys and JWK keys
rsaPublicKey:
Sun RSA public key, 2048 bits
  params: null
  modulus: 26357316308141920246706187189832816184464669247574329274151600014987833498613081305553907167489639882497901020042668019684731733203493602029515963993706600847721534104752032126130361576446376774646308346723928903197131878300000630951097323650413651136361398382777541880437222482333326912353931641531474275115618239345544686220478026629436520040030688170796270228708165193211856330191604982765859609032534442818720461696078063893165568447273933782242398761845509532495844704423556107073518195030616464416564865911759432179943444938978123330642161124144169230685337930276039065398676755273689018037129036026967769360801
  public exponent: 65537

jwkRsaPublicKey (JWK-Format)
{"kty":"RSA","e":"AQAB","n":"0MpIJE0koFXx5sZOOI-XsEMMQfvwHkizj1jaYGATZEz0YTdf-WUDrO2JeELP1UvwHRbD5Mt0y0IvSYEjG4btVoZWjoJwEIz-bT7rtJNnZ9bjY8vMYloCUM81nTLve0sVRqkjw3S7IFXsTXx05vkY7oV25Z9YeZH2f5b1ph3JGcTrQF8d3XZy6XAM_KaWWOPTwzoNtr3JQQzUJ2vS_BGCJyiVU1cEB0RlRu1Gd9EPqDcMGAN2nMoHUuQw0qNTd-ms0Du0RGnktRDpcm3SXLsUt2J4adbPp02eXjn-TDTISzR6FywC0sAL6ED0EqWhOgqEf7EftctSJGGdgLOkmL4poQ"}

rsaPublicKeyFromJwk:
Sun RSA public key, 2048 bits
  params: null
  modulus: 26357316308141920246706187189832816184464669247574329274151600014987833498613081305553907167489639882497901020042668019684731733203493602029515963993706600847721534104752032126130361576446376774646308346723928903197131878300000630951097323650413651136361398382777541880437222482333326912353931641531474275115618239345544686220478026629436520040030688170796270228708165193211856330191604982765859609032534442818720461696078063893165568447273933782242398761845509532495844704423556107073518195030616464416564865911759432179943444938978123330642161124144169230685337930276039065398676755273689018037129036026967769360801
  public exponent: 65537

Этот код не имеет обработки исключений и предназначен только для образовательных целей:

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.RSAKey;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;

public class ConvertRsaKeysJavaJwk {
    public static void main(String[] args) throws NoSuchAlgorithmException, JOSEException {
        System.out.println("RSA converting between Java keys and JWK keys");
        // generate a RSA key pair
        KeyPair rsaKeyPair = generateRsaKeyPair(2048);
        RSAPublicKey rsaPublicKey = (RSAPublicKey) rsaKeyPair.getPublic();
        System.out.println("rsaPublicKey:\n" + rsaPublicKey);
        // import the ecdsaPublicKey to JWK
        // usage of nimbus-jose-jwt
        // https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/
        RSAKey jwkRsaPublicKey = new RSAKey.Builder(rsaPublicKey).build();
        System.out.println("\njwkRsaPublicKey (JWK-Format)\n" + jwkRsaPublicKey);
        // convert jwk to java
        RSAPublicKey rsaPublicKeyFromJwk = jwkRsaPublicKey.toRSAPublicKey();
        System.out.println("\nrsaPublicKeyFromJwk:\n" + rsaPublicKeyFromJwk);
    }

    public static KeyPair generateRsaKeyPair(int keylengthInt) throws NoSuchAlgorithmException {
        KeyPairGenerator keypairGenerator = KeyPairGenerator.getInstance("RSA");
        keypairGenerator.initialize(keylengthInt, new SecureRandom());
        return keypairGenerator.generateKeyPair();
    }
}
person Michael Fehr    schedule 26.02.2021
comment
Это не ответ на мой вопрос - person Scott; 28.02.2021
comment
Мне жаль, что я неправильно понял ваш вопрос Как мне создать открытый ключ RSA из этого JWK, чтобы я мог проверить подпись? Ответ должен генерировать новый JWK, поскольку ваш пример сокращен, берет JWK и преобразует его в открытый ключ RSA ?? - person Michael Fehr; 28.02.2021
comment
Из вопроса и комментариев вы увидите, что я спрашивал не только о программном построении ключей RSA из JWK, но, в частности, почему Signature.verify() вернул false для моего конкретного сценария. Ваш ответ основан исключительно на заголовке вопроса - person Scott; 12.03.2021