簡體   English   中英

在沒有庫的情況下在Java中讀取PKCS#1或SPKI公鑰

[英]Reading a PKCS#1 or SPKI public key in Java without libraries

我需要使用公鑰來驗證Java中的一些數據,但我似乎無法以不使用第三方插件的Java可以使用的方式格式化密鑰。

我正在使用Node.js的crypto庫生成密鑰,它為我提供了PKCS#1SPKI以及.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)

// 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編碼的公鑰轉換為SubjectPublicKeyInfo編碼的公鑰,該公鑰是RSA KeyFactory使用X509EncodedKeySpec接受的公鑰編碼 - 因為在X.509規范中定義了SubjectPublicKeyInfo。

基本上它是一種低級DER編碼方案

  1. 將PKCS#1編碼密鑰包裝成一個位串(標記0x03 ,以及未使用位數的編碼,一個值為0x00的字節);
  2. 在前面添加RSA算法標識符序列(RSA OID +空參數) - 預編碼為字節數組常量;
  3. 最后將這兩個放入一個序列(標記0x30 )。

沒有使用庫。 實際上,對於createSubjectPublicKeyInfoEncoding ,甚至不需要import語句。


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 - 我認為它有效,但最好是安全而不是抱歉。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM