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

前端 未结 1 554
栀梦
栀梦 2020-12-17 05:35

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.

I\'m generat

相关标签:
1条回答
  • 2020-12-17 06:07

    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.

    Basically it is a low level DER encoding scheme which

    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).

    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);
        }
    }
    

    Notes:

    • 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.
    0 讨论(0)
提交回复
热议问题