Ключи подписи/проверки Java и проверка Javascript WebCrypto не удались

Я пытаюсь

  1. генерировать ключи подписи/проверки (RSA)
  2. подписать значение (используя эти ключи) в веб-приложении Java (позволяет вызывать на стороне сервера)
  3. для проверки веб-клиентом — открытый ключ импортируется как RSASSA-PKCS1-v1_5 + SHA-256 (в браузере с использованием WebCrypto API / на стороне клиента)

У меня возникают проблемы с проверкой подписанного значения (подписанного на стороне сервера Java), хотя открытый ключ подписи/проверки успешно импортирован как JWK на стороне клиента.

Мне интересно, есть ли какие-либо проблемы совместимости алгоритмов на любом из шагов (OpenSSL, Java или Javascript), с которыми я могу столкнуться.

Команды OpenSSL, используемые для генерации ключей

openssl genrsa -out privatekey.pem 2048
openssl rsa -in privatekey.pem -pubout > publickey.pub
openssl pkcs8 -topk8 -inform PEM -outform DER -in privatekey.pem -out privatekey-pkcs8.pem

Импорт ключей с помощью Java (на стороне сервера)

public static KeyPair generateSignKeyPair() throws ... {
    byte[] privBytes = b64ToByteArray(PRIVATE_KEY_PEM_VALUE);
    byte[] pubBytes = b64ToByteArray(PUBLIC_KEY_PEM_VALUE);

    // private key
    KeySpec keySpec = new PKCS8EncodedKeySpec(privBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

    // public key (javaPubSignKey)
    X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(pubBytes);
    PublicKey publicKey = keyFactory.generatePublic(X509publicKey);

    return new KeyPair(publicKey, privateKey);
}

Подписание значения с помощью Java (на стороне сервера)

 public static byte[] generateSignature(PrivateKey signPrivateKey, byte[] data) throws ... {
    Signature dsa = Signature.getInstance("SHA256withRSA");
    dsa.initSign(signPrivateKey);
    dsa.update(data);
    return dsa.sign();
}

Отправьте их в веб-приложение для API WebCrypto для проверки в качестве клиента/браузера (клиент знает о публичном ключе, сгенерированном на первом этапе).

// Import public sign/verify key (javaPubSignVerifyKey)
var signatureAlgorithm = {
    name: 'RSASSA-PKCS1-v1_5',
    modulusLength: 2048,
    publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
    hash: {
      name: 'SHA-256'
   }
};
// JWK format (1)
crypto.subtle.importKey(
    'jwk', javaPubSignVerifyKey, signatureAlgorithm, false, ['verify']
).then(success, error);

function success(key) {
    signatureVerifyPublicKey = key;
}

Примечание (1): на стороне Java я использую com.nimbusds.jose.jwk.JWK для экспорта publicKey в формат JWK.

Ключ подписи успешно импортирован WebCrypto. Но когда дело доходит до проверки, она терпит неудачу (логическое значение проверки равно false).

crypto.subtle.verify(
      signatureAlgorithm,
      signatureVerifyPublicKey,
      signature,               // bytes in Int8Array format (2)
      data                     // bytes in Int8Array format
    ).then(
       function (valid) {
           // valid === false
       }
    )

Примечание (2): также обратите внимание, что каждый пример, который я нашел в WebCrypto, использовал Uint8Array для представления байтовых массивов, но поскольку Java генерирует подписанные массивы байтов, мне нужно использовать Int8Array, чтобы значения подписи не были загрязнены ( может и в этом проблема)

EDIT: для справки, это оказалось еще одной несвязанной проблемой - я дважды преобразовывал ожидаемые данные из base64 в Javascript, не замечая этого; естественно проверка не удалась.


person nuno    schedule 29.11.2016    source источник
comment
java byte[] хранит значения от 0 до 255. Я думаю, вам нужно использовать Uint8Array и ArrayBuffer. Не могли бы вы попробовать использовать функцию stringToArrayBuffer, описанную здесь stackoverflow.com/questions/36018233/ для представления data и signature?   -  person pedrofb    schedule 29.11.2016
comment
Спасибо, все еще не работает. Возможно, мне нужно изменить основы реализации: подумать об импорте сертификата X509 в Java и использовании pki.js + asn1.js для извлечения из него публичного ключа (сертификата) в Javascript для проверки WebCrypto (пример здесь)   -  person nuno    schedule 29.11.2016
comment
Я предполагаю, что это не проблема с открытым ключом, потому что он вызвал исключение, когда вы импортировали JWK. Но, чтобы убедиться, вы можете импортировать его в формате spki (двоичный ключ RSA, преобразованный в ArrayBuffer)   -  person pedrofb    schedule 29.11.2016
comment
При импорте spki, сгенерированном с помощью Base64.encode(pubKey.getEncoded()) в Java и импортированном с помощью str2ab(atob(spkiBase64)) в Javascript симптом тот же: проверка не удалась.   -  person nuno    schedule 29.11.2016


Ответы (1)


Пожалуйста, проверьте этот простой код на основе вашего, чтобы импортировать открытый ключ RSA (spki) и проверить подпись. Я сгенерировал ключи и подпись, используя аналогичный код Java.

var publicKeyB64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVdZDEs6htb3oxWstz7q+e5IwIRcptMNJiyemuoNyyjtiOy+0tEodjgo7RVoyUcGU3MysEivqvKdswQZ4KfwQCBLAR8DRzp3biAge5utZcKsQoQaC1rCEplfmzEo5ovIlBcMq5x1BxnrnlwEPRmM7MefRa+OeAOQJcstHcrJFO7QIDAQAB";
var dataB64 = "aGVsbG8=";
var signatureB64 = "aEOmUA7YC5gvF6QgH+TMg0erY5pzr83nykZGFtyGOOe+6ld+MC4/Qdb608XiNud+pBpzh0wqd6aajOtJim5XEfCH8vUPsv45aSPtukUIQTX00Oc1frIFDQI6jGJ4Q8dQYIwpqsyE2rkGwTDzt1fTTGiw54pLsJXjtL/D5hUEKL8=";
var signatureAlgorithm = {name: 'RSASSA-PKCS1-v1_5',modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]),hash: { name: 'SHA-256'  }};

//convert public key, data and signature to ArrayBuffer. 
var publicKey = str2ab(atob(publicKeyB64)); 
var data = str2ab(atob(dataB64));
var signature = str2ab(atob(signatureB64));            

crypto.subtle.importKey("spki", publicKey, signatureAlgorithm, false,["verify"]).
    then(function(key){
        console.log(key);
        return crypto.subtle.verify( signatureAlgorithm, key, signature, data);                    
}).then( function (valid) {
    console.log("Signature valid: "+valid);
}).catch(function(err) {
    alert("Verification failed " + err );
});

Я не мог точно воспроизвести проблему. Используя служебную функцию str2ab, которую вы связали, код работает отлично.

//Utility function
function str2ab(str) {
  var arrBuff = new ArrayBuffer(str.length);
  var bytes = new Uint8Array(arrBuff);
  for (var iii = 0; iii < str.length; iii++) {
    bytes[iii] = str.charCodeAt(iii);
  }
  return bytes;
}

Я предлагаю сравнить оба кода, чтобы найти различия

person pedrofb    schedule 29.11.2016
comment
Привет @pedrofb, спасибо за ваш отзыв - это оказалась еще одна несвязанная проблема: я дважды преобразовывал ожидаемые данные из base64 в Javascript, не замечая этого; естественно проверка не удалась. Для справки, использование Uint8Array не вызвало никаких проблем, как я изначально подозревал. - person nuno; 30.11.2016
comment
Кроме того, методы str2ab и ab2str, которые я использовал, похожи на этот - person nuno; 30.11.2016
comment
Возможно, у вас возникли проблемы с использованием Uint16Array? - person nuno; 30.11.2016
comment
Я проверил код, используя предоставленную вами функцию str2ab, и он работает хорошо. На самом деле содержимое функции — это тот же код, который я использовал в stringToArrayBuffer. Я думаю, что проблема с предыдущим str2ab была Uint16Array. Я обновил ответ соответствующими ссылками - person pedrofb; 30.11.2016