Я пытаюсь зашифровать строку с помощью RSA-OAEP-SHA-512 в API WebCrypto (на стороне клиента), а затем хочу снова расшифровать строку с помощью Java (на стороне сервера). Однако расшифровка на стороне сервера завершается с ошибкой «Ошибка: ошибка заполнения» при расшифровке.
Я генерирую ключи RSA в JavaScript, используя:
async function generateRSAKeyPair() {
const rsaKeyPair = await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 4096,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
hash: { name: 'SHA-512' },
},
true,
['encrypt', 'decrypt']
);
return rsaKeyPair;
}
Я шифрую строку JSON, используя:
sync function encryptWithRSA(jsonString, jsonRSAPublicKey) {
// Parse JSON keys
const rsaPublicKey = JSON.parse(jsonRSAPublicKey);
// Import RSA public key
const importedRSAPublicKey = await window.crypto.subtle.importKey(
'jwk',
rsaPublicKey,
{ name: 'RSA-OAEP', hash: { name: 'SHA-512' } },
true,
['encrypt']
);
// Convert JSON String to ArrayBuffer
const aesKeyBuffer = new TextEncoder().encode(jsonString);
// Encrypt AES key using RSA public key
const encryptedAesKeyBuffer = await window.crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
importedRSAPublicKey,
aesKeyBuffer
);
// Convert encrypted AES key to base64
const encryptedAesKeyBase64 = window.btoa(String.fromCharCode(...new Uint8Array(encryptedAesKeyBuffer)));
return encryptedAesKeyBase64;
}
Я пытаюсь расшифровать его на Java, используя:
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.math.BigInteger;
import java.util.Base64;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
public void decrypt() {
try {
PrivateKey privateKey = loadPrivateKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] encryptedBytes = Base64.getDecoder().decode(cipherText);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
this.clearText = new String(decryptedBytes);
System.out.println("Decrypted clear text: " + clearText);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Decryption failed: " + e.getMessage());
}
}
private PrivateKey loadPrivateKey() throws Exception {
// Parse the JWK JSON string
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jwk = mapper.readValue(rsaKey, Map.class);
System.out.println("JWK parsed: " + jwk);
// Extract the key components from the JWK
String nBase64 = (String) jwk.get("n");
String dBase64 = (String) jwk.get("d");
String pBase64 = (String) jwk.get("p");
String qBase64 = (String) jwk.get("q");
String dpBase64 = (String) jwk.get("dp");
String dqBase64 = (String) jwk.get("dq");
String qiBase64 = (String) jwk.get("qi");
String eBase64 = (String) jwk.get("e");
// Decode the Base64 URL-encoded components
BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(nBase64));
BigInteger privateExponent = new BigInteger(1, Base64.getUrlDecoder().decode(dBase64));
BigInteger publicExponent = new BigInteger(1, Base64.getUrlDecoder().decode(eBase64));
BigInteger primeP = new BigInteger(1, Base64.getUrlDecoder().decode(pBase64));
BigInteger primeQ = new BigInteger(1, Base64.getUrlDecoder().decode(qBase64));
BigInteger primeExponentP = new BigInteger(1, Base64.getUrlDecoder().decode(dpBase64));
BigInteger primeExponentQ = new BigInteger(1, Base64.getUrlDecoder().decode(dqBase64));
BigInteger crtCoefficient = new BigInteger(1, Base64.getUrlDecoder().decode(qiBase64));
// Create the RSA private key specification
RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);
// Generate the private key
KeyFactory keyFactory = KeyFactory.getInstance(AppConfig.CRYPTO_RSA);
return keyFactory.generatePrivate(keySpec);
}
Это дает следующий результат:
javax.crypto.BadPaddingException: Padding error in decryption
at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:383)
at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:419)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2244)
at zkgitclientcli.crypto.RsaEncryptionHandler2.decrypt(RsaEncryptionHandler2.java:48)
at zkgitclientcli.commands.login.DecryptAesKey.execute(DecryptAesKey.java:23)
at zkgitclientcli.commands.CommandManager.executeCommand(CommandManager.java:44)
at zkgitclientcli.ZkGit.main(ZkGit.java:12)
at org.codehaus.mojo.exec.ExecJavaMojo.doMain(ExecJavaMojo.java:385)
at org.codehaus.mojo.exec.ExecJavaMojo.doExec(ExecJavaMojo.java:374)
at org.codehaus.mojo.exec.ExecJavaMojo.lambda$execute$0(ExecJavaMojo.java:296)
at java.base/java.lang.Thread.run(Thread.java:1583)
Decryption failed: Padding error in decryption
Импорт закрытого ключа, скорее всего, работает, поскольку строки, зашифрованные в Java с использованием соответствующего открытого ключа, успешно расшифровываются с помощью этого метода. Это не удается только при использовании в качестве входных данных зашифрованной строки WebCrypto.
Кроме того, я могу написать метод JavaScript, который успешно расшифровывает зашифрованную строку, поэтому зашифрованную строку в кодировке Base64 можно расшифровать, но не в Java...
Я пробовал следующие настройки, но безуспешно: SHA-1: RSA/ECB/OAEPWithSHA-1AndMGF1Padding SHA-224: RSA/ECB/OAEPWithSHA-224AndMGF1Padding SHA-256: RSA/ECB/OAEPWithSHA-256AndMGF1Padding SHA-384: RSA/ECB/OAEPWithSHA-384AndMGF1Padding
Работает как шарм, спасибо!
Вот рабочий код, основанный на приведенном выше комментарии:
public void decrypt() {
try {
PrivateKey privateKey = loadPrivateKey();
// Create OAEPParameterSpec
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
"SHA-512",
"MGF1",
MGF1ParameterSpec.SHA512,
PSource.PSpecified.DEFAULT
);
Cipher cipher = Cipher.getInstance(AppConfig.CRYPTO_RSA_OAEP);
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
byte[] encryptedBytes = Base64.getDecoder().decode(cipherText);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
clearText = new String(decryptedBytes);
} catch (Exception e) {
clearText = e.getMessage();
}
}
Вероятно, проблема связана с разными дайджестами MGF1: если
RSA/ECB/OAEPWithSHA-512AndMGF1Padding
провайдер SunJCE указывает дайджест OAEP как SHA-512, дайджест MGF1 по умолчанию имеет значение SHA1. Напротив, WebCrypto использует один и тот же дайджест для OAEP и MGF1, то есть SHA-512. Попробуйте явно указать оба дайджеста на стороне Java, используяOAEPParameterSpec
(как SHA-512).