Когда я шифрую строку текста с помощью Java и Openssl, я не могу расшифровать шифр (используя Javascript Crypto Subtle), полученный от Openssl, однако шифр Java успешно расшифровывается (с помощью Crypto Subtle Web API JS). Не могли бы вы посмотреть и подсказать, где ошибка?
Открытый ключ (RSA-OAEP, monulus 2048, хэш SHA-256):
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8JhmO71HZ1vb8uxKhw6MM+ZvtTmc9Tw6AFpJVgXKiTjebj1SPdnxhdhJ5Bj15RN0rGNACXhAyUdn7zsp66/tmjNuC91L+9PvQBjDbXLsx7XUV9nIGJ3aYO5/qruVNXwyemf7wlwZVDF7oeZ8JUfjABTg7a7xui2WdXDHgvhTQdvQ9TS0NkX9xWAiDSn/HWfoEBC7TLeKeVjHsT7g1JnskGxfVhFrLfxQCxYZle4ebXP7dCPsff4WNNCxzBQtHHt8iEoZ0SVKBzS5zhdLHIdAIW4ELdnYsM7iTlWZO+kfWnlV2i13lAJobhxOAqwsg4OqkTsrx0KtWfZH40bNtFzx8wIDAQAB
Закрытый ключ (те же параметры):
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDwmGY7vUdnW9vy7EqHDowz5m+1OZz1PDoAWklWBcqJON5uPVI92fGF2EnkGPXlE3SsY0AJeEDJR2fvOynrr+2aM24L3Uv70+9AGMNtcuzHtdRX2cgYndpg7n+qu5U1fDJ6Z/vCXBlUMXuh5nwlR+MAFODtrvG6LZZ1cMeC+FNB29D1NLQ2Rf3FYCINKf8dZ+gQELtMt4p5WMexPuDUmeyQbF9WEWst/FALFhmV7h5tc/t0I+x9/hY00LHMFC0ce3yIShnRJUoHNLnOF0sch0AhbgQt2diwzuJOVZk76R9aeVXaLXeUAmhuHE4CrCyDg6qROyvHQq1Z9kfjRs20XPHzAgMBAAECggEAPtCjPGyeFFu00LclfB5tt20t9CQ/GP3o7MelxvpLF0mMNT74VmKs/rNqE037ARxzxUBCa1aEn6hvd9O8DadIgw5zaFCWMoDyQYtVlqE/NaMA9hDLf7XS2qTaGyLPgX/UFAZLAkMWb9ddfncVKYybtR0+Xn/i56dYVYAk7spTvmkai5Q4Li5eEJqrRNse5fg/wiwDda/cWlEvlvfziAjU51gS/Y9ItUT8Z1g8A2NYfqgFVgBVb0qrtbP76TWPbeLZr1FXTMDB0MO+zuC+1lq48+CYqSJqi9a4GlW2gi0wkaQe3VIOsHipv82kSh20LczXiSKqF58F8m4whWqpa02rrQKBgQD6tbuFiY8fVXi44m1mFO7KDIVXFjuMca0+qXisVaUhG4q42fnlHN0kyuH2IFXNxHD+gE4MnvAUkXDkG1znaz8vM2tfQn6vSpU7OytwOt6UEMWm+112083nGMUy1Me/6rAJbBWfTAl0KXY9/Y/SuEL8u+W6Eas+eASUIe1DooI4xwKBgQD1rAfUXjuRH6WFnbITdG8B3WVZhRCx3YuQVE1UnHR8N6qfx6qKIricvKMSvtWAj0UiIbSA8DENyfMiyAbGpZ0cILxtdXZOtZFW9hfiOeLptnYKTJXOiGoju1prxpUNUKu/1kd7e0clxN75mooge8yFFA6il43wYhhwO2si+FYJdQKBgQDL+1TpX3StX9NrSf8MkXd/uRQ8OQCWUl9MnoJqZPyHpWsG34Ms4IElUFTs9n4Zfv0YdLgMGLzpXzRkw8ahG2c7NjDkPqvoX1xv5sJ++8bg3YyTQe1XoxjiMAsyQmGLSp2T7PbitvDyLFHiOg3surL2AsL00y9rEidXhwsOfohJPQKBgQCqhw4sQHjShIgNlmfMj06amcZG/FGZVPISbiH7cFp++tjp7duX5QAGc/4x/dsPUOOpDIJR2egC7UJiyzvA2aaTprmEtTs46VmIZmwvsQSsO+X1wjFeWlxqjxr1orNFudBt6dxWfzzkn6Iy2i203Jobac+61r5EtKLIDMaSUJTQHQKBgQDaNTfgakpyZBSp/Ydzu76qDKpzGuDqrLBnR/lui4DrSyUoOjNwaGlkU/B6USdaC/BGO7lI08HZU7FzutvXb1mKS+nUI7rKvSTN9NKUJF4MaBv9q40RViKkF9vbq2f1wMs2KUNc0ex2ZL+5BOBICJNHyVulKSxY54Z55L/H4FZmWw==
Вот код Java, который получает открытый ключ в виде строки Base64 и создает экземпляр PublicKey из ite. Затем я шифрую строку текста с помощью этого ключа и кодирую шифр в строку Base64.
String stringToEncrypt = "This is encrypted message!!!";
byte[] publicKeyBytes = Base64.getDecoder().decode("MIIBIjANBgkqhki....");
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new
MGF1ParameterSpec("SHA-256"),PSource.PSpecified.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey rsaPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey,oaepParams);
String cipherBytes =
Base64.getEncoder().encodeToString(cipher.doFinal(stringToEncrypt.getBytes()));
Затем строка cipherBytes Base64 успешно расшифровывается кодом JS Crypto Subtle ниже:
Код генерации первой пары ключей:
const keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
)
Импорт закрытого ключа =
return window.crypto.subtle
.importKey("pkcs8", base64StringToArrayBuffer(privateKeyInPem), {
name: "RSA-OAEP",
hash: "SHA-256",
}, true, ["decrypt"]).then(key=>{
return key;
})
А потом расшифровать шифр
const dec = new TextDecoder();
function str2ab(str: any) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
console.info('cipher: '+cipher)
return window.crypto.subtle.decrypt({
name: "RSA-OAEP",
},
privateKey,
str2ab(window.atob(cipher))
).then(result=>{
console.info('DECRYPTED MSG:'+dec.decode(result));
})
Но проблема в том, что когда я пытаюсь расшифровать шифр из openssl - не получается. Вот команда Openssl, которую я использую:
OpenSSL> rsautl -encrypt -pubin -keyform DER -inkey publicKey.der -oaep -in input.txt -out out.bin
Я использую тот же открытый ключ, только что преобразованный из строки Base64 в формат DER (преобразованный строковый ключ Base64 в DER с помощью Java - 1. Декодировать строку base64 -> байт [] -> записать в файл каждый байт)
И, наконец, последняя операция Openssl для преобразования двоичных данных из out.bin в строку Base64.
OpenSSL> enc -A -base64 -in out.bin -out base64.txt
rsautl неизменно использует SHA1 для дайджестов OAEP, см. здесь . Поскольку вы применяете SHA256 в коде WebCrypto, вы должны вместо этого использовать pkeyutl, что позволяет указывать дайджесты:
openssl pkeyutl -in plaintext.txt -out ciphertext.bin -encrypt -keyform DER -pubin -inkey spki.der -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256
Сгенерированный таким образом зашифрованный текст можно расшифровать с помощью кода WebCrypto:
(async () => {
// your private key
var privateKeyInPem = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDwmGY7vUdnW9vy7EqHDowz5m+1OZz1PDoAWklWBcqJON5uPVI92fGF2EnkGPXlE3SsY0AJeEDJR2fvOynrr+2aM24L3Uv70+9AGMNtcuzHtdRX2cgYndpg7n+qu5U1fDJ6Z/vCXBlUMXuh5nwlR+MAFODtrvG6LZZ1cMeC+FNB29D1NLQ2Rf3FYCINKf8dZ+gQELtMt4p5WMexPuDUmeyQbF9WEWst/FALFhmV7h5tc/t0I+x9/hY00LHMFC0ce3yIShnRJUoHNLnOF0sch0AhbgQt2diwzuJOVZk76R9aeVXaLXeUAmhuHE4CrCyDg6qROyvHQq1Z9kfjRs20XPHzAgMBAAECggEAPtCjPGyeFFu00LclfB5tt20t9CQ/GP3o7MelxvpLF0mMNT74VmKs/rNqE037ARxzxUBCa1aEn6hvd9O8DadIgw5zaFCWMoDyQYtVlqE/NaMA9hDLf7XS2qTaGyLPgX/UFAZLAkMWb9ddfncVKYybtR0+Xn/i56dYVYAk7spTvmkai5Q4Li5eEJqrRNse5fg/wiwDda/cWlEvlvfziAjU51gS/Y9ItUT8Z1g8A2NYfqgFVgBVb0qrtbP76TWPbeLZr1FXTMDB0MO+zuC+1lq48+CYqSJqi9a4GlW2gi0wkaQe3VIOsHipv82kSh20LczXiSKqF58F8m4whWqpa02rrQKBgQD6tbuFiY8fVXi44m1mFO7KDIVXFjuMca0+qXisVaUhG4q42fnlHN0kyuH2IFXNxHD+gE4MnvAUkXDkG1znaz8vM2tfQn6vSpU7OytwOt6UEMWm+112083nGMUy1Me/6rAJbBWfTAl0KXY9/Y/SuEL8u+W6Eas+eASUIe1DooI4xwKBgQD1rAfUXjuRH6WFnbITdG8B3WVZhRCx3YuQVE1UnHR8N6qfx6qKIricvKMSvtWAj0UiIbSA8DENyfMiyAbGpZ0cILxtdXZOtZFW9hfiOeLptnYKTJXOiGoju1prxpUNUKu/1kd7e0clxN75mooge8yFFA6il43wYhhwO2si+FYJdQKBgQDL+1TpX3StX9NrSf8MkXd/uRQ8OQCWUl9MnoJqZPyHpWsG34Ms4IElUFTs9n4Zfv0YdLgMGLzpXzRkw8ahG2c7NjDkPqvoX1xv5sJ++8bg3YyTQe1XoxjiMAsyQmGLSp2T7PbitvDyLFHiOg3surL2AsL00y9rEidXhwsOfohJPQKBgQCqhw4sQHjShIgNlmfMj06amcZG/FGZVPISbiH7cFp++tjp7duX5QAGc/4x/dsPUOOpDIJR2egC7UJiyzvA2aaTprmEtTs46VmIZmwvsQSsO+X1wjFeWlxqjxr1orNFudBt6dxWfzzkn6Iy2i203Jobac+61r5EtKLIDMaSUJTQHQKBgQDaNTfgakpyZBSp/Ydzu76qDKpzGuDqrLBnR/lui4DrSyUoOjNwaGlkU/B6USdaC/BGO7lI08HZU7FzutvXb1mKS+nUI7rKvSTN9NKUJF4MaBv9q40RViKkF9vbq2f1wMs2KUNc0ex2ZL+5BOBICJNHyVulKSxY54Z55L/H4FZmWw= = ";
var privateKey = await window.crypto.subtle.importKey("pkcs8", base64StringToArrayBuffer(privateKeyInPem), {name: "RSA-OAEP", hash: "SHA-256"}, true, ["decrypt"]);
console.info(privateKey);
// Base64 encoded ciphertext from openssl pkeyutl
var cipher = "V2QWzwoOHPAlKhGqv0fDSv+lSPytBW4tTxVVJgneyvfIqTDvllhbZJzUAszdAC0IEow+YgbIWHyIBCw9YVS+EDZ3jbuIU97nx5NxAimiUFKvUmHE8p3oP6AP/etJhGQGC+fMiTbhmGn5FQhMnH/2lVei3yJypXWbgI6ONRmncYalq73q7VGelFUSubuPWQA3bKzuIOSorpQFy9sGIMDvW+YOMLrClVmUujVrEXrdsIbvzSb6hooKHbwjOaAmN4XRw0sr+YaF3n2PwazpLSvJmuugF26GxhmJAMmNViUvvsN+ycpJZdyRKNehQGqmahpC0XXihZ9dsHEH7vIDDmPAZQ= = ";
console.info('cipher: '+cipher)
// successfull decryption
var decrypted = await window.crypto.subtle.decrypt({name: "RSA-OAEP"}, privateKey, str2ab(window.atob(cipher)));
const dec = new TextDecoder();
console.info('DECRYPTED MSG:' + dec.decode(decrypted));
})();
function base64StringToArrayBuffer(base64String){
return Uint8Array.from(window.atob(base64String), c => c.charCodeAt(0));
}
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
Для полноты: в контексте OAEP необходимо указать два дайджеста: дайджест контента и дайджест MGF1. pkeyutl позволяет установить оба независимо. Если оба дайджеста идентичны (что обычно и бывает), достаточно установить только дайджест контента с помощью rsa_oaep_md
, т. е. дайджест MGF1 неявно имеет одно и то же значение, пока никакой другой дайджест не задан явно через rsa_mgf1_md
.
...Я использую OAEP Parameters Sepc, и мне показалось, что они нужны для операции шифрования, а не для генерации открытого ключа... Так оно и есть. Точнее, OAEP — это вариант заполнения для RSA, используемый при шифровании/дешифровании, см. RFC8017, 7.1. РГАЭС-ОАЭП . Еще один — дополнение PKCS#1 v1.5, s. 7.2. RSAES-PKCS1-v1_5. Для самой генерации ключа заполнение не играет никакой роли.
Причина, по которой WebCrypto API указывает алгоритм (RSA), заполнение (OAEP) и параметры заполнения (SHA-256) в generateKey()
при создании пары ключей, заключается в том, что WebCrypto API связывает предполагаемое назначение ключа с этим ключом. т.е. сгенерированный таким образом ключ RSA впоследствии можно будет использовать только для шифрования (открытый ключ) или дешифрования (закрытый ключ) с помощью RSA и OAEP с SHA-256. Идея: никогда не используйте один и тот же ключ для нескольких целей.
Спасибо за разъяснение. Я был введен в заблуждение тонким методом generateKey() Crypto, думая, что Oaep и SHA-256 являются параметрами для ключей.
Большое спасибо! Трюк с openssl pkeyutl удался. Интересно, что глядя на код Java, когда я создаю экземпляр PublicKey - PublicKey rsaPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes)) - здесь ничего не упоминается о дайджесте контента и MGF1. Только при вызове этого метода - cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey,oaepParams) я использую параметры OAEP Sepc и мне показалось, что они нужны для операции шифрования, а не для генерации открытого ключа. Извините, может я что-то путаю, не экспорт в крипто.