在没有库的情况下读取 Java 中的 PKCS#1 或 SPKI public 密钥
Reading a PKCS#1 or SPKI public key in Java without libraries
我需要使用 public 密钥来验证 Java 中的某些数据,但我似乎无法以 Java 可以使用的方式格式化密钥third-party 个插件。
我正在使用 Node.js 的 crypto
库生成密钥,它让我可以选择 PKCS#1
或 SPKI
,以及 .pem 或 .pem。 der文件格式。
我听说 Java 不支持 PKCS#1
out-of-the 盒子,Whosebug 上几乎所有其他答案都建议使用 BouncyCastle 或类似的,但就我而言,我正在编写一个 SDK,根本负担不起使用库来读取这个 public 密钥。
所以我目前正在读取 .der 格式的密钥,因为它节省了剥离 PEM headers 并从 base-64 解码密钥的麻烦。当我 运行 这个时,我得到错误:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
这是我所拥有的(抱歉,它是在 Kotlin 中,而不是标题所暗示的 Java)
// Here's a key for convenience
val key = Base64.getDecoder().decode("MFUCTgF/uLsPBS13Gy7C3dPpiDF6SYCLUyyl6CFqPtZT1h5bwKR9EDFLQjG/kMiwkRMcmEeaLKe5qdj9W/FfFitwRAm/8F53pQw2UETKQI2b2wIDAQAB");
val keySpec = X509EncodedKeySpec(key)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec) // error thrown here
val cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, publicKey)
我目前最好的想法是在 Node.js 端安装一个库,这样问题较少,以支持将密钥导出为 PKCS#8,但我想我会先检查一下如果我遗漏了什么。
以下代码将 PKCS#1 编码的 public 密钥转换为 SubjectPublicKeyInfo 编码的 public 密钥,这是 RSA KeyFactory
接受的 public 密钥编码使用 X509EncodedKeySpec
- 因为 SubjectPublicKeyInfo 在 X.509 规范中定义。
基本上它是一种低级 DER 编码方案,
- 将 PKCS#1 编码的密钥包装成位串(标记
0x03
,以及未使用位数的编码,一个字节值 0x00
);
- 在前面添加RSA算法标识符序列(RSA OID+一个空参数)-预编码为字节数组常量;
- 最后将两者放入一个序列中(标记
0x30
)。
没有使用任何库。实际上,对于 createSubjectPublicKeyInfoEncoding
,甚至不需要导入语句。
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class PKCS1ToSubjectPublicKeyInfo {
private static final int SEQUENCE_TAG = 0x30;
private static final int BIT_STRING_TAG = 0x03;
private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE =
{(byte) 0x30, (byte) 0x0d,
(byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x01,
(byte) 0x05, (byte) 0x00};
public static RSAPublicKey decodePKCS1PublicKey(byte[] pkcs1PublicKeyEncoding)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
byte[] subjectPublicKeyInfo2 = createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey generatePublic = (RSAPublicKey) rsaKeyFactory.generatePublic(new X509EncodedKeySpec(subjectPublicKeyInfo2));
return generatePublic;
}
public static byte[] createSubjectPublicKeyInfoEncoding(byte[] pkcs1PublicKeyEncoding)
{
byte[] subjectPublicKeyBitString = createDEREncoding(BIT_STRING_TAG, concat(NO_UNUSED_BITS, pkcs1PublicKeyEncoding));
byte[] subjectPublicKeyInfoValue = concat(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, subjectPublicKeyBitString);
byte[] subjectPublicKeyInfoSequence = createDEREncoding(SEQUENCE_TAG, subjectPublicKeyInfoValue);
return subjectPublicKeyInfoSequence;
}
private static byte[] concat(byte[] ... bas)
{
int len = 0;
for (int i = 0; i < bas.length; i++)
{
len += bas[i].length;
}
byte[] buf = new byte[len];
int off = 0;
for (int i = 0; i < bas.length; i++)
{
System.arraycopy(bas[i], 0, buf, off, bas[i].length);
off += bas[i].length;
}
return buf;
}
private static byte[] createDEREncoding(int tag, byte[] value)
{
if (tag < 0 || tag >= 0xFF)
{
throw new IllegalArgumentException("Currently only single byte tags supported");
}
byte[] lengthEncoding = createDERLengthEncoding(value.length);
int size = 1 + lengthEncoding.length + value.length;
byte[] derEncodingBuf = new byte[size];
int off = 0;
derEncodingBuf[off++] = (byte) tag;
System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
off += lengthEncoding.length;
System.arraycopy(value, 0, derEncodingBuf, off, value.length);
return derEncodingBuf;
}
private static byte[] createDERLengthEncoding(int size)
{
if (size <= 0x7F)
{
// single byte length encoding
return new byte[] { (byte) size };
}
else if (size <= 0xFF)
{
// double byte length encoding
return new byte[] { (byte) 0x81, (byte) size };
}
else if (size <= 0xFFFF)
{
// triple byte length encoding
return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
}
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
}
public static void main(String[] args) throws Exception
{
// some weird 617 bit key, which is way too small and not a multiple of 8
byte[] pkcs1PublicKeyEncoding = Base64.getDecoder().decode("MFUCTgF/uLsPBS13Gy7C3dPpiDF6SYCLUyyl6CFqPtZT1h5bwKR9EDFLQjG/kMiwkRMcmEeaLKe5qdj9W/FfFitwRAm/8F53pQw2UETKQI2b2wIDAQAB");
RSAPublicKey generatePublic = decodePKCS1PublicKey(pkcs1PublicKeyEncoding);
System.out.println(generatePublic);
}
}
备注:
NoSuchAlgorithmException
可能应该被捕获并放入 RuntimeException
;
- 私有方法
createDERLengthEncoding
可能不接受负尺寸。
- 更大的键尚未经过测试,请验证
createDERLengthEncoding
- 我认为它可以工作,但安全总比抱歉好。
我需要使用 public 密钥来验证 Java 中的某些数据,但我似乎无法以 Java 可以使用的方式格式化密钥third-party 个插件。
我正在使用 Node.js 的 crypto
库生成密钥,它让我可以选择 PKCS#1
或 SPKI
,以及 .pem 或 .pem。 der文件格式。
我听说 Java 不支持 PKCS#1
out-of-the 盒子,Whosebug 上几乎所有其他答案都建议使用 BouncyCastle 或类似的,但就我而言,我正在编写一个 SDK,根本负担不起使用库来读取这个 public 密钥。
所以我目前正在读取 .der 格式的密钥,因为它节省了剥离 PEM headers 并从 base-64 解码密钥的麻烦。当我 运行 这个时,我得到错误:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
这是我所拥有的(抱歉,它是在 Kotlin 中,而不是标题所暗示的 Java)
// Here's a key for convenience
val key = Base64.getDecoder().decode("MFUCTgF/uLsPBS13Gy7C3dPpiDF6SYCLUyyl6CFqPtZT1h5bwKR9EDFLQjG/kMiwkRMcmEeaLKe5qdj9W/FfFitwRAm/8F53pQw2UETKQI2b2wIDAQAB");
val keySpec = X509EncodedKeySpec(key)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec) // error thrown here
val cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, publicKey)
我目前最好的想法是在 Node.js 端安装一个库,这样问题较少,以支持将密钥导出为 PKCS#8,但我想我会先检查一下如果我遗漏了什么。
以下代码将 PKCS#1 编码的 public 密钥转换为 SubjectPublicKeyInfo 编码的 public 密钥,这是 RSA KeyFactory
接受的 public 密钥编码使用 X509EncodedKeySpec
- 因为 SubjectPublicKeyInfo 在 X.509 规范中定义。
基本上它是一种低级 DER 编码方案,
- 将 PKCS#1 编码的密钥包装成位串(标记
0x03
,以及未使用位数的编码,一个字节值0x00
); - 在前面添加RSA算法标识符序列(RSA OID+一个空参数)-预编码为字节数组常量;
- 最后将两者放入一个序列中(标记
0x30
)。
没有使用任何库。实际上,对于 createSubjectPublicKeyInfoEncoding
,甚至不需要导入语句。
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class PKCS1ToSubjectPublicKeyInfo {
private static final int SEQUENCE_TAG = 0x30;
private static final int BIT_STRING_TAG = 0x03;
private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE =
{(byte) 0x30, (byte) 0x0d,
(byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x01,
(byte) 0x05, (byte) 0x00};
public static RSAPublicKey decodePKCS1PublicKey(byte[] pkcs1PublicKeyEncoding)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
byte[] subjectPublicKeyInfo2 = createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey generatePublic = (RSAPublicKey) rsaKeyFactory.generatePublic(new X509EncodedKeySpec(subjectPublicKeyInfo2));
return generatePublic;
}
public static byte[] createSubjectPublicKeyInfoEncoding(byte[] pkcs1PublicKeyEncoding)
{
byte[] subjectPublicKeyBitString = createDEREncoding(BIT_STRING_TAG, concat(NO_UNUSED_BITS, pkcs1PublicKeyEncoding));
byte[] subjectPublicKeyInfoValue = concat(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, subjectPublicKeyBitString);
byte[] subjectPublicKeyInfoSequence = createDEREncoding(SEQUENCE_TAG, subjectPublicKeyInfoValue);
return subjectPublicKeyInfoSequence;
}
private static byte[] concat(byte[] ... bas)
{
int len = 0;
for (int i = 0; i < bas.length; i++)
{
len += bas[i].length;
}
byte[] buf = new byte[len];
int off = 0;
for (int i = 0; i < bas.length; i++)
{
System.arraycopy(bas[i], 0, buf, off, bas[i].length);
off += bas[i].length;
}
return buf;
}
private static byte[] createDEREncoding(int tag, byte[] value)
{
if (tag < 0 || tag >= 0xFF)
{
throw new IllegalArgumentException("Currently only single byte tags supported");
}
byte[] lengthEncoding = createDERLengthEncoding(value.length);
int size = 1 + lengthEncoding.length + value.length;
byte[] derEncodingBuf = new byte[size];
int off = 0;
derEncodingBuf[off++] = (byte) tag;
System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
off += lengthEncoding.length;
System.arraycopy(value, 0, derEncodingBuf, off, value.length);
return derEncodingBuf;
}
private static byte[] createDERLengthEncoding(int size)
{
if (size <= 0x7F)
{
// single byte length encoding
return new byte[] { (byte) size };
}
else if (size <= 0xFF)
{
// double byte length encoding
return new byte[] { (byte) 0x81, (byte) size };
}
else if (size <= 0xFFFF)
{
// triple byte length encoding
return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
}
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
}
public static void main(String[] args) throws Exception
{
// some weird 617 bit key, which is way too small and not a multiple of 8
byte[] pkcs1PublicKeyEncoding = Base64.getDecoder().decode("MFUCTgF/uLsPBS13Gy7C3dPpiDF6SYCLUyyl6CFqPtZT1h5bwKR9EDFLQjG/kMiwkRMcmEeaLKe5qdj9W/FfFitwRAm/8F53pQw2UETKQI2b2wIDAQAB");
RSAPublicKey generatePublic = decodePKCS1PublicKey(pkcs1PublicKeyEncoding);
System.out.println(generatePublic);
}
}
备注:
NoSuchAlgorithmException
可能应该被捕获并放入RuntimeException
;- 私有方法
createDERLengthEncoding
可能不接受负尺寸。 - 更大的键尚未经过测试,请验证
createDERLengthEncoding
- 我认为它可以工作,但安全总比抱歉好。