从编码的私钥字节中提取算法 ID
Extracting algorithm ID from bytes of encoded private key
是否可以在事先不知道算法的情况下单独从编码字节数组创建PrivateKey
?
所以在某种程度上它是 that question 的一个转折点,答案中没有解决。
假设我有一对这样生成的密钥:
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); // Could be "EC" instead of "RSA"
String privateKeyB64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
writePrivateKeyToSafeLocation(privateKeyB64);
要从base64编码的字节中得到一个PrivateKey
,我可以这样做,但我必须提前知道算法族:
String privateKeyB64 = readPrivateKeyFromSafeLocation();
EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyB64));
byte[] encodedKeyBytes = encodedKeySpec.getEncoded();
String algorithmFamily = "RSA"; // Can this be deduced from encodedKeyBytes?
PrivateKey key = KeyFactory.getInstance(algorithmFamily).generatePrivate(encodedKeySpec);
可惜encodedKeySpec.getAlgorithm()
returnsnull
.
我很确定算法 ID 实际上是在 PKCS#8 格式的那些字节中指定的,但我不确定如何读取 ASN.1 编码。
我可以 "sniff" 从这些字节中可靠地获取算法 ID 吗?
只支持 RSA 和 EC(JRE 支持的算法,无需额外提供程序)即可。
为了了解我所追求的,这里有一个似乎凭经验行之有效的尝试:
private static final byte[] EC_ASN1_ID = {42, -122, 72, -50, 61, 2, 1};
private static final byte[] RSA_ASN1_ID = {42, -122, 72, -122, -9, 13, 1, 1, 1};
private static final int EC_ID_OFFSET = 9;
private static final int RSA_ID_OFFSET = 11;
private static String sniffAlgorithmFamily(byte[] keyBytes) {
if (Arrays.equals(Arrays.copyOfRange(keyBytes, EC_ID_OFFSET, EC_ID_OFFSET + EC_ASN1_ID.length), EC_ASN1_ID)) {
return "EC";
}
if (Arrays.equals(Arrays.copyOfRange(keyBytes, RSA_ID_OFFSET, RSA_ID_OFFSET + RSA_ASN1_ID.length), RSA_ASN1_ID)) {
return "RSA";
}
throw new RuntimeException("Illegal key, this thingy requires either RSA or EC private key");
}
但我不知道使用它是否安全。也许 ID 并不总是在这些偏移量处。也许它们可以用其他方式编码...
正如 James 在评论中所建议的那样,以更安全的方式尝试每一种支持的算法都会起作用。
可以动态获取此类算法列表:
Set<String> supportedKeyPairAlgorithms() {
Set<String> algos = new HashSet<>();
for (Provider provider : Security.getProviders()) {
for (Provider.Service service : provider.getServices()) {
if ("KeyPairGenerator".equals(service.getType())) {
algos.add(service.getAlgorithm());
}
}
}
return algos;
}
然后,尝试全部生成 KeyPair
:
PrivateKey generatePrivateKey(String b64) {
byte[] bytes = Base64.getDecoder().decode(b64);
for (String algorithm : supportedKeyPairAlgorithms()) {
try {
LOGGER.debug("Attempting to decode key as " + algorithm);
return KeyFactory.getInstance(algorithm).generatePrivate(new PKCS8EncodedKeySpec(bytes));
} catch (NoSuchAlgorithmException e) {
LOGGER.warn("Standard algorithm " + algorithm + " not known by this Java runtime from outer space", e);
} catch (InvalidKeySpecException e) {
LOGGER.debug("So that key is not " + algorithm + ", nevermind", e);
}
}
throw new RuntimeException("No standard KeyFactory algorithm could decode your key");
}
是否可以在事先不知道算法的情况下单独从编码字节数组创建PrivateKey
?
所以在某种程度上它是 that question 的一个转折点,答案中没有解决。
假设我有一对这样生成的密钥:
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); // Could be "EC" instead of "RSA"
String privateKeyB64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
writePrivateKeyToSafeLocation(privateKeyB64);
要从base64编码的字节中得到一个PrivateKey
,我可以这样做,但我必须提前知道算法族:
String privateKeyB64 = readPrivateKeyFromSafeLocation();
EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyB64));
byte[] encodedKeyBytes = encodedKeySpec.getEncoded();
String algorithmFamily = "RSA"; // Can this be deduced from encodedKeyBytes?
PrivateKey key = KeyFactory.getInstance(algorithmFamily).generatePrivate(encodedKeySpec);
可惜encodedKeySpec.getAlgorithm()
returnsnull
.
我很确定算法 ID 实际上是在 PKCS#8 格式的那些字节中指定的,但我不确定如何读取 ASN.1 编码。
我可以 "sniff" 从这些字节中可靠地获取算法 ID 吗?
只支持 RSA 和 EC(JRE 支持的算法,无需额外提供程序)即可。
为了了解我所追求的,这里有一个似乎凭经验行之有效的尝试:
private static final byte[] EC_ASN1_ID = {42, -122, 72, -50, 61, 2, 1};
private static final byte[] RSA_ASN1_ID = {42, -122, 72, -122, -9, 13, 1, 1, 1};
private static final int EC_ID_OFFSET = 9;
private static final int RSA_ID_OFFSET = 11;
private static String sniffAlgorithmFamily(byte[] keyBytes) {
if (Arrays.equals(Arrays.copyOfRange(keyBytes, EC_ID_OFFSET, EC_ID_OFFSET + EC_ASN1_ID.length), EC_ASN1_ID)) {
return "EC";
}
if (Arrays.equals(Arrays.copyOfRange(keyBytes, RSA_ID_OFFSET, RSA_ID_OFFSET + RSA_ASN1_ID.length), RSA_ASN1_ID)) {
return "RSA";
}
throw new RuntimeException("Illegal key, this thingy requires either RSA or EC private key");
}
但我不知道使用它是否安全。也许 ID 并不总是在这些偏移量处。也许它们可以用其他方式编码...
正如 James 在评论中所建议的那样,以更安全的方式尝试每一种支持的算法都会起作用。
可以动态获取此类算法列表:
Set<String> supportedKeyPairAlgorithms() {
Set<String> algos = new HashSet<>();
for (Provider provider : Security.getProviders()) {
for (Provider.Service service : provider.getServices()) {
if ("KeyPairGenerator".equals(service.getType())) {
algos.add(service.getAlgorithm());
}
}
}
return algos;
}
然后,尝试全部生成 KeyPair
:
PrivateKey generatePrivateKey(String b64) {
byte[] bytes = Base64.getDecoder().decode(b64);
for (String algorithm : supportedKeyPairAlgorithms()) {
try {
LOGGER.debug("Attempting to decode key as " + algorithm);
return KeyFactory.getInstance(algorithm).generatePrivate(new PKCS8EncodedKeySpec(bytes));
} catch (NoSuchAlgorithmException e) {
LOGGER.warn("Standard algorithm " + algorithm + " not known by this Java runtime from outer space", e);
} catch (InvalidKeySpecException e) {
LOGGER.debug("So that key is not " + algorithm + ", nevermind", e);
}
}
throw new RuntimeException("No standard KeyFactory algorithm could decode your key");
}