最近项目用到RSA公私钥对进行加解密,遇到将der格式公钥转换为PublicKey报错的问题,异常信息如下:
cn.hutool.crypto.CryptoException: InvalidKeySpecException: encoded key spec not recognized: unknown object in getInstance: org.bouncycastle.asn1.ASN1Integer
at cn.hutool.crypto.KeyUtil.generatePublicKey(KeyUtil.java:355)
at cn.hutool.crypto.KeyUtil.generatePublicKey(KeyUtil.java:335)
at com.unionpay.trust.crypto.util.HsmUtilTest.testGenerateRsaAndPubKeyEncrypt(HsmUtilTest.java:95)
at com.unionpay.trust.crypto.util.HsmUtilTest.main(HsmUtilTest.java:61)
Caused by: java.security.spec.InvalidKeySpecException: encoded key spec not recognized: unknown object in getInstance: org.bouncycastle.asn1.ASN1Integer
at org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi.engineGeneratePublic(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(KeyFactory.java:328)
at cn.hutool.crypto.KeyUtil.generatePublicKey(KeyUtil.java:353)
使用der格式公钥转换为PublicKey的方式如下:
//这里无法正常生成publickey
// PublicKey publicKey = KeyUtil.generatePublicKey("RSA", Forms.hexStringToByte(publicKeyDerHex));
// PublicKey publicKey = KeyUtil.generatePublicKey("RSA/ECB/PKCS1Padding", Forms.hexStringToByte(publicKeyDerHex));
// PublicKey publicKey = KeyUtil.generatePublicKey("RSA/ECB/NoPadding", Forms.hexStringToByte(publicKeyDerHex));
// PublicKey publicKey = KeyUtil.generatePublicKey("RSA/None/NoPadding", Forms.hexStringToByte(publicKeyDerHex));
// PublicKey publicKey = KeyUtil.generateRSAPublicKey(Forms.hexStringToByte(publicKeyDerHex));
以上方式均无法正常生成PublicKey,报的错误都是上面的异常信息
出现错误的原因如下:
加密机产生的RSA公钥是ASN.1编码的,这里进行转换的使用需要先将ANS.1编码转换为X509编码,具体转换方式如下:
/**
* RSA 公钥 格式转换 ANS1转X509
* @param encodedKey 公钥
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public static byte[] ansOneToX509(byte[] encodedKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 调用ASN1Sequence,对ASN1的RSADER编码进行解码
ASN1Sequence asn1 = ASN1Sequence.getInstance(encodedKey);
Enumeration<?> e = asn1.getObjects();
BigInteger modulus = ASN1Integer.getInstance(e.nextElement()).getPositiveValue();
BigInteger publicExponent = ASN1Integer.getInstance(e.nextElement()).getPositiveValue();
// 重新构造一个x509对象,并获取编码后的数据
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubkey = keyFactory.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
byte[] x509Encoded = pubkey.getEncoded();
return x509Encoded;
}
经过转换之后,再讲der格式转换为PublicKey就正常了。
RSA公钥将X509编码转换为加密机可用的ASN.1编码方式如下:
/**
* 将x509的证书公钥转换为ASN1编码
* @param pkcs8PublicKeyByte
* @return
*/
public static byte[] formatPublicKeyPKCS8ToPKCS1(byte[] pkcs8PublicKeyByte) {
ASN1Sequence publicKeyASN1Object = ASN1Sequence.getInstance(pkcs8PublicKeyByte);
ASN1Encodable derBitStringASN1Encodable = publicKeyASN1Object.getObjectAt(1);
DERBitString derBitStringObject = DERBitString.getInstance(derBitStringASN1Encodable);
return derBitStringObject.getBytes();
}
public static String changeX509ToAns1(String publicKeyBase64){
try {
log.debug("changeX509ToAns1 publicKeyBase64:" + publicKeyBase64);
//公钥加密
String hexStr = Base64Util.base64Decode(publicKeyBase64);
byte[] decode = hexStringToByte(hexStr);
// //将内容转成流的方式
ByteArrayInputStream bis = new ByteArrayInputStream(decode);
CertificateFactory cf = null;
cf = CertificateFactory.getInstance("X.509");
Certificate certificate = cf.generateCertificate(bis);
//取出公钥--这里的公钥是pkcs8的那种结构型--待核实
PublicKey publicKey = certificate.getPublicKey();
byte[] data = publicKey.getEncoded();
byte[] result = formatPublicKeyPKCS8ToPKCS1(data);
String ansEncode = Forms.byteToHexString(result);
log.debug("changeX509ToAns1 data:" + ansEncode);
return ansEncode;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Base64为hutool工具类
*/
public static String base64Decode(String str) {
return byteToHexString(Base64.decode(str));
}
public static String byteToHexString(byte[] b) {
if (b == null) {
return null;
} else {
StringBuffer sb = new StringBuffer(b.length * 2);
for(int i = 0; i < b.length; ++i) {
sb.append("0123456789ABCDEF".charAt((b[i] & 240) >> 4));
sb.append("0123456789ABCDEF".charAt((b[i] & 15) >> 0));
}
return sb.toString();
}
}
public static byte[] hexStringToByte(String hex) {
if (hex == null) {
return null;
} else {
int len = hex.length() / 2;
hex = hex.toUpperCase();
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for(int i = 0; i < len; ++i) {
int pos = i * 2;
result[i] = (byte)(toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
}