在没有库的情况下在 Java 中读取 PKCS#1 或 SPKI 公钥

Posted

技术标签:

【中文标题】在没有库的情况下在 Java 中读取 PKCS#1 或 SPKI 公钥【英文标题】:Reading a PKCS#1 or SPKI public key in Java without libraries 【发布时间】:2019-06-11 19:25:05 【问题描述】:

我需要使用公钥来验证 Java 中的某些数据,但我似乎无法以 Java 可以在没有第三方插件的情况下使用的方式格式化密钥。

我正在使用 Node.js 的 crypto 库生成密钥,这让我可以选择 PKCS#1SPKI,以及 .pem 或 .der 文件格式。

我听说 Java 不支持开箱即用的 PKCS#1,而且 *** 上的几乎所有其他答案都建议使用 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)

// 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,但我想我会先检查一下我是否缺少任何东西。

【问题讨论】:

虽然 SDK 适用于 android,因此有一个精简版的 BouncyCastle 可用,但我相信它只能作为 Cipher 的提供程序,因此没有用处。此外,提供纯 Java 解决方案对未来的读者更有帮助。 由于 nodejs 支持 SPKI, Java 称之为 X509Encoded,使用它是最简单的。要么使用 PEM 并在 java 中剥离 header/trailer 并解码 base64,要么使用 DER 并在 Java 中按原样使用。正如 nodejs 文档所说,PKCS8 是 private 密钥不是公共的。顺便说一句,617 位对于 RSA 来说是一个奇怪的大小,而且太小而不安全。 【参考方案1】:

以下代码将 PKCS#1 编码的公钥转换为 SubjectPublicKeyInfo 编码的公钥,这是 RSA KeyFactory 使用 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 是否有效 - 我认为它可以工作,但最好是安全而不是抱歉。

【讨论】:

以上是关于在没有库的情况下在 Java 中读取 PKCS#1 或 SPKI 公钥的主要内容,如果未能解决你的问题,请参考以下文章

读取pkcs12证书信息

如何在没有任何 vue 库的情况下在 vue 回调中获取 http 响应标头(axios)

如何在没有来自 c 库的 printf 的情况下在汇编级编程中打印整数?

如何在没有来自 c 库的 printf 的情况下在汇编级编程中打印整数?

如何在没有外部库的情况下在 React 中实现 Google Maps JS API?

在没有 JS 库的情况下在 <canvas> 上为 spritesheet 设置动画 [重复]