在没有库的情况下用Java读取PKCS#1或SPKI公钥 [英] Reading a PKCS#1 or SPKI public key in Java without libraries

查看:146
本文介绍了在没有库的情况下用Java读取PKCS#1或SPKI公钥的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要使用公共密钥来验证Java中的某些数据,但是我似乎无法以无需第三方插件就可以使用Java的方式来格式化密钥.

I need to use a public key to verify some data in Java, but I can't seem to format the key in such a way that Java can use without third-party plugins.

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

I'm generating the key with Node.js's crypto library, which gives me the option of PKCS#1 or SPKI, and either .pem or .der file format.

我听说Java不支持现成的 PKCS#1 ,几乎所有关于StackOverflow的答案都建议使用BouncyCastle或类似的软件,但就我而言,我我正在编写一个SDK,并且根本负担不起仅使用库来读取此公钥.

I've heard that Java doesn't support PKCS#1 out-of-the box, and pretty much every other answer on StackOverflow recommends using BouncyCastle or similar, but in my case, I am writing an SDK, and simply cannot afford to use a library just to read this public key.

因此,我目前正在读取.der格式的密钥,因为它省去了剥离PEM标头并从base-64解码密钥的麻烦.运行此命令时,出现错误:

So I'm currently reading the key in .der format as it saves having to strip the PEM headers and decode the key from base-64. When I run this, I get the error:

java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG

这就是我所拥有的(对不起,它在Kotlin,而不是Java的标题)

Here's what I have (sorry, it's in Kotlin, not Java like the title suggests)

// 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的麻烦,但我想我首先要检查一下是否缺少任何东西.

My best idea at the minute is to install a library on the Node.js side, which is less problematic, to support exporting the key as PKCS#8, but I thought I'd check first to see if I'm missing anything.

推荐答案

以下代码将PKCS#1编码的公共密钥转换为SubjectPublicKeyInfo编码的公共密钥,这是RSA KeyFactory 使用 X509EncodedKeySpec -X.509规范中定义了SubjectPublicKeyInfo.

The following code turns a PKCS#1 encoded public key into a SubjectPublicKeyInfo encoded public key, which is the public key encoding accepted by the RSA KeyFactory using X509EncodedKeySpec - as SubjectPublicKeyInfo is defined in the X.509 specifications.

基本上是一种低级DER编码方案,

Basically it is a low level DER encoding scheme which

  1. 将PKCS#1编码的密钥包装为一个位字符串(标记 0x03 ,以及未使用位数的编码,一个字节为 0x00 );
  2. li>
  3. 在前面添加RSA算法标识符序列(RSA OID +空参数)-预编码为字节数组常量;
  4. ,最后将它们都放入一个序列中(标记 0x30 ).
  1. wraps the PKCS#1 encoded key into a bit string (tag 0x03, and a encoding for the number of unused bits, a byte valued 0x00);
  2. adds the RSA algorithm identifier sequence (the RSA OID + a null parameter) in front - pre-encoded as byte array constant;
  3. and finally puts both of those into a sequence (tag 0x30).

不使用任何库.实际上,对于 createSubjectPublicKeyInfoEncoding ,甚至不需要导入语句.

No libraries are used. Actually, for createSubjectPublicKeyInfoEncoding, no import statements are even required.

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 -我认为它可以正常工作,但是比后悔更安全.
  • NoSuchAlgorithmException should probably be caught and put into a RuntimeException;
  • the private method createDERLengthEncoding should probably not accept negative sizes.
  • Larger keys have not been tested, please validate createDERLengthEncoding for those - I presume it works, but better be safe than sorry.

这篇关于在没有库的情况下用Java读取PKCS#1或SPKI公钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆