如何从字符串中加载 RSA public 密钥以在 Java 中进行签名验证?
How to load RSA public key from String for signature verification in Java?
我有以下 public 键,它作为文本存储在数据库 (PostgresSQL) 中。这是一个字符串,在 java:
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA1ht0OqZpP7d/05373OE7pB7yCVGNGzkUEuCneyfOzps6iA03NbvI
1ZL0Jpp/N3AW73lGdhaoa3X3JE4GsI/bsToVLQwTKmIOC4yjTvBctmFEoyhhTfxW
s1UHZKl4XZ/7THbRlKHhRaTKyfDAbikkMAxNT/qutLAPjnN1qOwjb1oRq52NP6FJ
KWTTikz4UeOHroX+Xthn2fJSJDlQ4YMdBbgrZVx5JcHKNuPTKRf5gI8QQKMSA9Q9
QJRE5OGp7b6dG14ZmOUnUxb00Mp20LgcaGPcuWU+oFsbQaF6W4G4bdkSZRJJXhSg
d4Q7mahpar94/gnztJmth0GzqTWUYyZIWNqIFoMwuOgeaiDV43zb3uLsRVpRKYYy
esmzcOy/jTScVLRCD8QRyu9B2wgCkNAVztQOXPCOOa4O1LlVQWaecIs4WPhOqDhi
KTBhyVkpC1TrrBkp+QMqMqWll1OyVb6k/7uV0qE/i6rHJtjo5v9bcIgYzswyx9CD
9PKl2Q0L0Jg7TMG+yLDIrLfGeuSeEc4XYJzN7bJcCeiizzu5iU9dQUkrncOrq9jn
Ub2pM/+A+JqIsoPK3IY/pJKqH4JYpGKhO1iPQF6iXIZT1r3ZgJUSQtzSeyYqhkla
2uR2BsbPbDqebCuXm3lAsY5w+dujijcn96PKwYha1LsK5sACHuJ79AMCAwEAAQ==
-----END RSA PUBLIC KEY-----
我不知道这个密钥是如何生成的,抱歉。我被告知使用此密钥并验证另一个字符串的签名,我将调用 "object"。有人告诉我,我必须用来验证 "object" 的算法是 SHA256withRSA。
所以,我写了下面的java读取key的方法
private PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
publicKey = publicKey.replaceAll("\n", "");
publicKey = publicKey.replace("-----BEGIN RSA PUBLIC KEY-----", "");
publicKey = publicKey.replace("-----END RSA PUBLIC KEY-----", "");
publicKey = publicKey.trim();
byte[] keyDecoded = Base64.getDecoder().decode(publicKey.getBytes());
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(keyDecoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(publicSpec);
return pubKey;
}
关键是我得到以下异常:
java.security.InvalidKeyException: IOException: algid parse error, not
a sequence
我在 Whosebug 中阅读了很多问题。其他用户编写的代码与我的非常相似(有时相同)。所以我绝对不明白为什么它对我不起作用。其他开发人员(同事)也在 php 中做同样的事情并且效果很好,所以我会放弃错误的 public 键的假设。也许我没有清楚地了解这个过程?请问有什么线索吗?
我也尝试按照建议 here 使用 BouncyCastle 库来解决这个问题,但我遇到了同样的异常。以下是我写的代码:
private static PublicKey getPublicKey(String publicKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new BouncyCastleProvider());
PemReader pp = new PemReader(new StringReader(publicKey));
PemObject pem = pp.readPemObject();
byte[] content = pem.getContent();
pp.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(content);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
您无法使用 X509EncodedKeySpec 加载该密钥。根据它的 JavaDoc documentation 它需要以下格式:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
相反,您的密钥看起来不一样了。我使用了您 post 中的数据,将其转换为十六进制数据并 post 将其编辑到在线 ASN.1 decoder。
输出是这样的:
SEQUENCE (2 elem)
INTEGER (4096 bit) 873481340827968071893572683200799871431146795599597693981565010037737…
INTEGER 65537
您可能已经意识到您的密钥不包含 AlgorithmIdentifier
,因此无法使用 X509EncodedKeySpec
加载它。
我的建议是使用 BouncyCastle 库,它 PEMParser
class 用于加载此密钥:
File pemFile = new File("test.pem");
try (PEMParser pp = new PEMParser(new InputStreamReader(new FileInputStream(pemFile)))) {
SubjectPublicKeyInfo subjPubKeyInfo = (SubjectPublicKeyInfo) pp.readObject();
RSAKeyParameters rsa = (RSAKeyParameters) PublicKeyFactory.createKey(subjPubKeyInfo);
RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
java.security.PublicKey publicKey = kf.generatePublic(rsaSpec);
System.out.println(publicKey);
}
或者您通过 openssl 手动将密钥转换为 PKCS#8 格式。
我有以下 public 键,它作为文本存储在数据库 (PostgresSQL) 中。这是一个字符串,在 java:
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA1ht0OqZpP7d/05373OE7pB7yCVGNGzkUEuCneyfOzps6iA03NbvI
1ZL0Jpp/N3AW73lGdhaoa3X3JE4GsI/bsToVLQwTKmIOC4yjTvBctmFEoyhhTfxW
s1UHZKl4XZ/7THbRlKHhRaTKyfDAbikkMAxNT/qutLAPjnN1qOwjb1oRq52NP6FJ
KWTTikz4UeOHroX+Xthn2fJSJDlQ4YMdBbgrZVx5JcHKNuPTKRf5gI8QQKMSA9Q9
QJRE5OGp7b6dG14ZmOUnUxb00Mp20LgcaGPcuWU+oFsbQaF6W4G4bdkSZRJJXhSg
d4Q7mahpar94/gnztJmth0GzqTWUYyZIWNqIFoMwuOgeaiDV43zb3uLsRVpRKYYy
esmzcOy/jTScVLRCD8QRyu9B2wgCkNAVztQOXPCOOa4O1LlVQWaecIs4WPhOqDhi
KTBhyVkpC1TrrBkp+QMqMqWll1OyVb6k/7uV0qE/i6rHJtjo5v9bcIgYzswyx9CD
9PKl2Q0L0Jg7TMG+yLDIrLfGeuSeEc4XYJzN7bJcCeiizzu5iU9dQUkrncOrq9jn
Ub2pM/+A+JqIsoPK3IY/pJKqH4JYpGKhO1iPQF6iXIZT1r3ZgJUSQtzSeyYqhkla
2uR2BsbPbDqebCuXm3lAsY5w+dujijcn96PKwYha1LsK5sACHuJ79AMCAwEAAQ==
-----END RSA PUBLIC KEY-----
我不知道这个密钥是如何生成的,抱歉。我被告知使用此密钥并验证另一个字符串的签名,我将调用 "object"。有人告诉我,我必须用来验证 "object" 的算法是 SHA256withRSA。
所以,我写了下面的java读取key的方法
private PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
publicKey = publicKey.replaceAll("\n", "");
publicKey = publicKey.replace("-----BEGIN RSA PUBLIC KEY-----", "");
publicKey = publicKey.replace("-----END RSA PUBLIC KEY-----", "");
publicKey = publicKey.trim();
byte[] keyDecoded = Base64.getDecoder().decode(publicKey.getBytes());
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(keyDecoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(publicSpec);
return pubKey;
}
关键是我得到以下异常:
java.security.InvalidKeyException: IOException: algid parse error, not a sequence
我在 Whosebug 中阅读了很多问题。其他用户编写的代码与我的非常相似(有时相同)。所以我绝对不明白为什么它对我不起作用。其他开发人员(同事)也在 php 中做同样的事情并且效果很好,所以我会放弃错误的 public 键的假设。也许我没有清楚地了解这个过程?请问有什么线索吗?
我也尝试按照建议 here 使用 BouncyCastle 库来解决这个问题,但我遇到了同样的异常。以下是我写的代码:
private static PublicKey getPublicKey(String publicKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new BouncyCastleProvider());
PemReader pp = new PemReader(new StringReader(publicKey));
PemObject pem = pp.readPemObject();
byte[] content = pem.getContent();
pp.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(content);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
您无法使用 X509EncodedKeySpec 加载该密钥。根据它的 JavaDoc documentation 它需要以下格式:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
相反,您的密钥看起来不一样了。我使用了您 post 中的数据,将其转换为十六进制数据并 post 将其编辑到在线 ASN.1 decoder。
输出是这样的:
SEQUENCE (2 elem)
INTEGER (4096 bit) 873481340827968071893572683200799871431146795599597693981565010037737…
INTEGER 65537
您可能已经意识到您的密钥不包含 AlgorithmIdentifier
,因此无法使用 X509EncodedKeySpec
加载它。
我的建议是使用 BouncyCastle 库,它 PEMParser
class 用于加载此密钥:
File pemFile = new File("test.pem");
try (PEMParser pp = new PEMParser(new InputStreamReader(new FileInputStream(pemFile)))) {
SubjectPublicKeyInfo subjPubKeyInfo = (SubjectPublicKeyInfo) pp.readObject();
RSAKeyParameters rsa = (RSAKeyParameters) PublicKeyFactory.createKey(subjPubKeyInfo);
RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
java.security.PublicKey publicKey = kf.generatePublic(rsaSpec);
System.out.println(publicKey);
}
或者您通过 openssl 手动将密钥转换为 PKCS#8 格式。