关于RSA私钥解密遇到的问题
[TOC]
生成密钥对过程遇到的问题
生成密钥
通过openssl生成密钥对的过程中,要注意编码的问题。
- 生成私钥
openssl genrsa -out private_key.pem 1024
- 生成公钥
openssl rsa -in private_key.pem -pubout -out public_key.pem
- 私钥转换编码
openssl pkcs8 -topk8 -in private_key.pem -out pkcs8_rsa_private_key.pem -nowcrypt
一般私钥在使用中,需要进行PKCS8编码。
-nocrypt 不采用任何二次加密
问题&解决
最开始,直接使用了未经转码的私钥文件,即 pkcs1 格式的私钥。由于此密钥对在Android 中进行单元测试时,是可以正常加解密的,就以此作为结果写入文档了。
后来在与Server童靴对接过程中,反应说是利用我给出的私钥串,无法生成私钥,直接报错如下:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
随后,我本地马上采用纯Java的API写了一套逻辑,试了一下,发现确实存在这个问题。
@Throws(Exception::class)
fun loadPrivateKey(privateKeyStr: String): PrivateKey {
try {
// Android API
//val buffer = Base64.decode(privateKeyStr, Base64.NO_WRAP)
// Java API
val buffer = Base64.getDecoder().decode(privateKeyStr)
val keySpec = PKCS8EncodedKeySpec(buffer)
val keyFactory = KeyFactory.getInstance(RSA)
return keyFactory.generatePrivate(keySpec)
} catch (e: NoSuchAlgorithmException) {
throw Exception("无此算法")
} catch (e: InvalidKeySpecException) {
throw Exception("私钥非法")
} catch (e: NullPointerException) {
throw Exception("私钥数据为空")
}
}
由于代码区分仅在于Base64的区分,用JavaAPI的Base64解码生成私钥报错,用Android API 解码就没问题。
下意识怀疑Base64解码的问题。
后来经过排除,并不是Base64导致的问题。查阅资料后得知 Java API默认下只能加载 PKCS8 编码格式的私钥文件。
故:通过openssl命令将私钥文件转码为了 PKCS8格式的。再次测试发现通过私钥串可以正常生产私钥了。
Java 使用PKCS1私钥
Java并非不能使用 PKCS1 格式的私钥文件,只是按照上述代码运行,默认使用的是PKCS8来加载私钥文件,所以会报错。
只是Java的API中并为PKCS1密钥提供开箱即用的封装。
PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
Object object = pemParser.readObject();
KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
PrivateKey privateKey = kp.getPrivate();
上述代码摘抄自:在JAVA中读取格式为PKCS1的RSA私钥
RSA 私钥解密中遇到的问题
私钥生成的问题解决后,以为后面就是万事大吉了,谁成想...Server童鞋那边又反应说是用这个生成的私钥无法进行解密,解密过程中抛错了。
javax.crypto.BadPaddingException: Decryption error
首先我这边给出测试文档案例,我这边都是经过加密再解密流程的,即我这边的代码肯定是可以解密这个加密内容的。然后对照文档排除掉手残粘贴错误的情况,发现只能重走老路,再次用纯Java代码实现解密的逻辑。
...
val cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, privateKey)
cipher.doFinal(encryptedData)
...
PS:代码逻辑前后省略,仅贴关键代码
自己跑一遍Java的解密逻辑,发现真是无法解密...
瞬间就是脑海里万马奔腾,***!!!
此处真的耗时良久。。。
- 排除了可能Base64的问题;
- 排除了可能密钥对的问题;
- 排除了可能粘贴复制加密串到文档中字符编码不同的问题
后来无奈之下苦思时,突然想到还有个大区别,虽然都是使用的Java API,但是运行的环境不同。
电脑上做单元测试的虚拟机与Android手机系统的dalvik虚拟机,这就会带来很大的区别。
后来带着这个问题,查阅资料,发现Android与SUN默认下对RSA算法的默认实现有区别。
即Android端用Cipher.getInstance("RSA")方法进行加密时,使用的provider是Bouncycastle Security provider,Bouncycastle Security provider默认实现的是“RSA/None/NoPadding”算法,而服务器(PC)端用Cipher.getInstance("RSA")进行解密时,使用的是Sun的security provider,实现的是“RSA/None/PKCS1Padding”算法,所以,解密时会失败。
在本地做过验证之后,发现确实如此。
val cipher = Cipher.getInstance("RSA/None/PKCS1Padding")
参考链接
关于javax.crypto.BadPaddingException: Blocktype异常的几种解决办法
在JAVA中读取格式为PKCS1的RSA私钥