在没有库的Java中读取PKCS#1或SPKI公钥
我需要使用公钥来验证Java中的一些数据,但我似乎无法将密钥格式化为Java在没有第三方插件的情况下可以使用的方式 我使用Node.js的在没有库的Java中读取PKCS#1或SPKI公钥,java,encryption,public-key-encryption,java-security,Java,Encryption,Public Key Encryption,Java Security,我需要使用公钥来验证Java中的一些数据,但我似乎无法将密钥格式化为Java在没有第三方插件的情况下可以使用的方式 我使用Node.js的crypto库生成密钥,该库提供了PKCS#1或SPKI选项,以及.pem或.der文件格式 我听说Java不支持开箱即用的PKCS#1,关于StackOverflow的几乎所有其他答案都建议使用BouncyCastle或类似工具,但就我而言,我正在编写一个SDK,根本无法使用库来读取此公钥 因此,我目前正在读取.der格式的密钥,因为它省去了剥离PEM头并从
crypto
库生成密钥,该库提供了PKCS#1
或SPKI
选项,以及.pem或.der文件格式
我听说Java不支持开箱即用的PKCS#1
,关于StackOverflow的几乎所有其他答案都建议使用BouncyCastle或类似工具,但就我而言,我正在编写一个SDK,根本无法使用库来读取此公钥
因此,我目前正在读取.der格式的密钥,因为它省去了剥离PEM头并从base-64解码密钥的麻烦。当我运行此操作时,我会得到以下错误:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
以下是我所拥有的(抱歉,它是用Kotlin编写的,而不是像标题所示的Java)
目前我最好的办法是在Node.js端安装一个库,这是一个问题较少的库,以支持将密钥导出为PKCS#8,但我想我应该先检查一下是否缺少任何内容。以下代码将PKCS#1编码的公钥转换为SubjectPublicKeyInfo编码的公钥,这是RSA
KeyFactory使用X509EncodedKeySpec
接受的公钥编码,因为X.509规范中定义了SubjectPublicKeyInfo
基本上,它是一种低级别的DER编码方案
将PKCS#1编码密钥包装为位字符串(标记为0x03
,编码为未使用的位数,字节值为0x00
)李>
在前面添加RSA算法标识符序列(RSA OID+空参数),预编码为字节数组常量李>
最后将这两个元素放入一个序列中(tag0x30
)
没有使用任何库。实际上,对于createSubjectPublicKeyInfoEncoding
,甚至不需要导入语句
导入java.security.KeyFactory;
导入java.security.NoSuchAlgorithmException;
导入java.security.interfaces.RSAPublicKey;
导入java.security.spec.InvalidKeySpecException;
导入java.security.spec.X509EncodedKeySpec;
导入java.util.Base64;
公共类PKCS1对象PublicKeyInfo{
私有静态最终整数序列_标记=0x30;
私有静态最终整数位\字符串\标记=0x03;
私有静态最终字节[]无未使用字节=新字节[]{0x00};
私有静态最终字节[]RSA算法标识符序列=
{(字节)0x30,(字节)0x0d,
(字节)0x06,(字节)0x09,(字节)0x2a,(字节)0x86,(字节)0x48,(字节)0x86,(字节)0xf7,(字节)0x0d,(字节)0x01,(字节)0x01,(字节)0x01,
(字节)0x05,(字节)0x00};
公共静态RSAPublicKey decodePKCS1PublicKey(字节[]pkcs1PublicKeyEncoding)
抛出NoSuchAlgorithmException、InvalidKeySpecException
{
字节[]subjectPublicKeyInfo2=createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
KeyFactory rsaKeyFactory=KeyFactory.getInstance(“RSA”);
RSAPublicKey generatePublic=(RSAPublicKey)rsaKeyFactory.generatePublic(新的X509EncodedKeySpec(subjectPublicKeyInfo2));
返回生成公共;
}
公共静态字节[]createSubjectPublicKeyInfoEncoding(字节[]pkcs1PublicKeyEncoding)
{
byte[]subjectPublicKeyBitString=CreateDeRecoded(位字符串标记,concat(无未使用位,pkcs1PublicKeyEncoding));
字节[]subjectPublicKeyInfoValue=concat(RSA算法标识符序列,subjectPublicKeyBitString);
字节[]subjectPublicKeyInfoSequence=CreatedEncoding(序列标记,subjectPublicKeyInfoValue);
返回subjectPublicKeyInfoSequence;
}
专用静态字节[]concat(字节[]…bas)
{
int len=0;
对于(int i=0;i=0xFF)
{
抛出新的IllegalArgumentException(“当前仅支持单字节标记”);
}
字节[]LengthCoding=CreateDerLengthCoding(值.length);
int size=1+lengtencoding.length+value.length;
字节[]DeRecodingBuf=新字节[大小];
int off=0;
DeRecodingBuf[off++]=(字节)标记;
System.arraycopy(LengthCoding,0,DeRecodingBuf,off,LengthCoding.length);
off+=lengtencoding.length;
System.arraycopy(值,0,DeRecodingBuf,关闭,值.长度);
返回DerecodingBuf;
}
私有静态字节[]CreateDerLengthCoding(整数大小)
{
如果(虽然SDK是针对Android的,因此有一个精简版的BouncyCastle可用,但我相信它只是作为密码的提供者,因此没有用处。此外,提供纯Java解决方案对未来的读者更有帮助。由于nodejs支持SPKI,这是Java称之为X509Encoded的,所以使用起来最简单那就是。要么使用PEM,在java中去掉头/尾并解码base64,要么使用DER,在java中按原样使用。正如nodejs doc所说,PKCS8用于私钥而不是公钥。顺便说一句,617位对于RSA来说是一个奇怪的大小,太小而不安全。
// 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)
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);
}
}