Java 使用 NodeJS 加密解密 <RSA_PKCS1_OAEP_PADDING> 填充和 <sha256> oaepHash
Java Decrypt with NodeJS Encrypt <RSA_PKCS1_OAEP_PADDING> padding and <sha256> oaepHash
我需要解密来自使用 NodeJS 构建的外部服务的一些信息。
此服务要求使用 base64 中 pem 格式的 RSA (2048) public 密钥,以加密信息。
我正在 Java 中创建密钥对:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
PublicKey pubkey = kp.getPublic();
PrivateKey privkey = kp.getPrivate();
String pemPublicString = "-----BEGIN PUBLIC KEY-----\n";
pemPublicString = pemPublicString+Base64.getEncoder().encodeToString(pubkey.getEncoded())+"\n";
pemPublicString = pemPublicString+"-----END PUBLIC KEY-----\n";
String pemPrivateString = "-----BEGIN RSA PRIVATE KEY-----\n";
pemPrivateString = pemPrivateString+Base64.getEncoder().encodeToString(privkey.getEncoded())+"\n";
pemPrivateString = pemPrivateString+"-----END RSA PRIVATE KEY-----\n";
//Send to node js service
String base64publickey = Base64.getEncoder().encodeToString(pemPublicString.getBytes());
//Store for decrypting
String base64privatekey = Base64.getEncoder().encodeToString(pemPrivateString.getBytes());
外部服务正在按如下方式加密信息并返回字节:
crypto.publicEncrypt(
{
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
},
dataToEncrypt
);
我正在尝试解密Java中的信息如下:
public String decrypt(String payload, String privateKey){
byte [] ciphertext = payload.getBytes(StandardCharsets.UTF_8);
Cipher oaepFromInit = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new
MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
oaepFromInit.init(Cipher.DECRYPT_MODE, getRSAPrivateKeyFrom(privateKey), oaepParams);
byte[] pt = oaepFromInit.doFinal(ciphertext);
return new String(pt, StandardCharsets.UTF_8);
}
private PrivateKey getRSAPrivateKeyFrom(String content) {
byte[] decodedBytes = Base64.getDecoder().decode(content);
String decodedString = new String(decodedBytes);
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new StringReader(decodedString));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
Object object = pemParser.readObject();
PrivateKey k = converter.getPrivateKey((PrivateKeyInfo) object);
return k;
}
现在我收到 BadPadding 异常,知道可能是什么问题吗?
提前致谢。
发布的代码没有显示 NodeJS 代码如何导出密文。 Java 代码的 decrypt()
中的以下行:
byte[] ciphertext = payload.getBytes(StandardCharsets.UTF_8);
表示您使用了 Utf-8 编码。这是破坏密文的常见错误(见here). Instead of Utf-8, apply a binary-to-text enncoding,如Base64.
密文的导出将在 NodeJS 中完成:
var chiphertextBase64 = ciphertext.toString('base64');
并在 Java 中导入:
import java.util.Base64;
...
byte[] ciphertext = Base64.getDecoder().decode(payload);
在 NodeJS 代码中,OAEP(RSAES-OAEP) is specified as padding. crypto.publicEncrypt()
应用参数 oaepHash
相同 摘要,OAEP 和 MGF1 摘要。oaepHash: "sha256"
因此为 both 摘要指定 SHA256。
相反,Java 允许单独(和不同)规范 OAEP 和 MGF1 摘要。虽然 SHA256 用于 decrypt()
中的 OAEP 摘要,但 SHA1 用于 MGF1 摘要。后者与NodeJS代码不一致,需要改成SHA256:
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
Maarten Bodewes 在第一条评论中已经怀疑存在此错误。
在发布的代码中,PEM 编码的 pemPublicString
和 pemPrivateString
密钥分别被 Base64 编码为 base64publickey
和 base64privatekey
。这不是必需的,因为 PEM 编码密钥的 body 已经是 Base64 编码并且 header 和页脚由 ASCII 字符组成。因此,第二个 Base64 编码没有带来任何优势,但会带来关键数据被不必要地放大的缺点(Base64 开销:33%,参见 here)。另一方面,如果服务需要双重 Base64 编码的 public 密钥,您必须遵守。
生成密钥时,发布的 Java 代码隐式使用 PKCS#8 格式作为私钥。您可以使用 privkey.getFormat()
验证这一点,这将输出 PKCS#8
。 PKCS#8 的关联 header 是 -----BEGIN PRIVATE KEY-----
和页脚 -----END PRIVATE KEY-----
。您当前使用的 header 和页脚都属于 PEM 编码的 PKCS#1 私钥,因此与密钥数据不一致。 Maarten Bodewes 在第二条评论中已经解决了这个问题。
顺便说一下,无需 BouncyCastle 使用 PKCS8EncodedKeySpec
. For this only header, footer and line breaks have to be removed and the rest has to be Base64 decoded (DER encoding), s. e.g. here.
即可轻松导入 PKCS#8 密钥
我需要解密来自使用 NodeJS 构建的外部服务的一些信息。 此服务要求使用 base64 中 pem 格式的 RSA (2048) public 密钥,以加密信息。
我正在 Java 中创建密钥对:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
PublicKey pubkey = kp.getPublic();
PrivateKey privkey = kp.getPrivate();
String pemPublicString = "-----BEGIN PUBLIC KEY-----\n";
pemPublicString = pemPublicString+Base64.getEncoder().encodeToString(pubkey.getEncoded())+"\n";
pemPublicString = pemPublicString+"-----END PUBLIC KEY-----\n";
String pemPrivateString = "-----BEGIN RSA PRIVATE KEY-----\n";
pemPrivateString = pemPrivateString+Base64.getEncoder().encodeToString(privkey.getEncoded())+"\n";
pemPrivateString = pemPrivateString+"-----END RSA PRIVATE KEY-----\n";
//Send to node js service
String base64publickey = Base64.getEncoder().encodeToString(pemPublicString.getBytes());
//Store for decrypting
String base64privatekey = Base64.getEncoder().encodeToString(pemPrivateString.getBytes());
外部服务正在按如下方式加密信息并返回字节:
crypto.publicEncrypt(
{
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
},
dataToEncrypt
);
我正在尝试解密Java中的信息如下:
public String decrypt(String payload, String privateKey){
byte [] ciphertext = payload.getBytes(StandardCharsets.UTF_8);
Cipher oaepFromInit = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new
MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
oaepFromInit.init(Cipher.DECRYPT_MODE, getRSAPrivateKeyFrom(privateKey), oaepParams);
byte[] pt = oaepFromInit.doFinal(ciphertext);
return new String(pt, StandardCharsets.UTF_8);
}
private PrivateKey getRSAPrivateKeyFrom(String content) {
byte[] decodedBytes = Base64.getDecoder().decode(content);
String decodedString = new String(decodedBytes);
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new StringReader(decodedString));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
Object object = pemParser.readObject();
PrivateKey k = converter.getPrivateKey((PrivateKeyInfo) object);
return k;
}
现在我收到 BadPadding 异常,知道可能是什么问题吗? 提前致谢。
发布的代码没有显示 NodeJS 代码如何导出密文。 Java 代码的 decrypt()
中的以下行:
byte[] ciphertext = payload.getBytes(StandardCharsets.UTF_8);
表示您使用了 Utf-8 编码。这是破坏密文的常见错误(见here). Instead of Utf-8, apply a binary-to-text enncoding,如Base64.
密文的导出将在 NodeJS 中完成:
var chiphertextBase64 = ciphertext.toString('base64');
并在 Java 中导入:
import java.util.Base64;
...
byte[] ciphertext = Base64.getDecoder().decode(payload);
在 NodeJS 代码中,OAEP(RSAES-OAEP) is specified as padding. crypto.publicEncrypt()
应用参数 oaepHash
相同 摘要,OAEP 和 MGF1 摘要。oaepHash: "sha256"
因此为 both 摘要指定 SHA256。
相反,Java 允许单独(和不同)规范 OAEP 和 MGF1 摘要。虽然 SHA256 用于 decrypt()
中的 OAEP 摘要,但 SHA1 用于 MGF1 摘要。后者与NodeJS代码不一致,需要改成SHA256:
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
Maarten Bodewes 在第一条评论中已经怀疑存在此错误。
在发布的代码中,PEM 编码的 pemPublicString
和 pemPrivateString
密钥分别被 Base64 编码为 base64publickey
和 base64privatekey
。这不是必需的,因为 PEM 编码密钥的 body 已经是 Base64 编码并且 header 和页脚由 ASCII 字符组成。因此,第二个 Base64 编码没有带来任何优势,但会带来关键数据被不必要地放大的缺点(Base64 开销:33%,参见 here)。另一方面,如果服务需要双重 Base64 编码的 public 密钥,您必须遵守。
生成密钥时,发布的 Java 代码隐式使用 PKCS#8 格式作为私钥。您可以使用 privkey.getFormat()
验证这一点,这将输出 PKCS#8
。 PKCS#8 的关联 header 是 -----BEGIN PRIVATE KEY-----
和页脚 -----END PRIVATE KEY-----
。您当前使用的 header 和页脚都属于 PEM 编码的 PKCS#1 私钥,因此与密钥数据不一致。 Maarten Bodewes 在第二条评论中已经解决了这个问题。
顺便说一下,无需 BouncyCastle 使用 PKCS8EncodedKeySpec
. For this only header, footer and line breaks have to be removed and the rest has to be Base64 decoded (DER encoding), s. e.g. here.