Android Java-使用RSA公钥加密字符串。PEM

Android Java-使用RSA公钥加密字符串。PEM,java,android,encryption,rsa,pem,Java,Android,Encryption,Rsa,Pem,我有一个RSA公钥证书。我可以使用扩展名为.PEM的文件,也可以将其用作具有以下格式的字符串: -----开始RSA公钥----- {KEY} -----结束RSA公钥----- 我试图使用此密钥向服务器发送加密的JSON。我尝试了许多其他相关堆栈溢出问题的解决方案,但没有一个答案不适合我。这个答案似乎有道理,但有些地方工作不正常,根据其中一条评论,可能是因为X509EncodedKeySpec期望的是DER编码的数据而不是PEM。但是在这种情况下,我应该使用什么来处理PEM编码的数据?正如@T

我有一个RSA公钥证书。我可以使用扩展名为.PEM的文件,也可以将其用作具有以下格式的字符串:

-----开始RSA公钥-----

{KEY}

-----结束RSA公钥-----


我试图使用此密钥向服务器发送加密的JSON。我尝试了许多其他相关堆栈溢出问题的解决方案,但没有一个答案不适合我。这个答案似乎有道理,但有些地方工作不正常,根据其中一条评论,可能是因为X509EncodedKeySpec期望的是DER编码的数据而不是PEM。但是在这种情况下,我应该使用什么来处理PEM编码的数据?

正如@Topaco所评论的,您的RSA公钥是PEM编码的,但是是PKCS 1格式,而不是Java“开箱即用”可读的PKCS 8格式

下面的解决方案是由@Maarten Bodewes在这里提供的,因此()将执行读取并将其转换为(Java)可用的RSAPublicKey的任务

解决方案在我的OpenJdk11上运行,如果您使用的是“Android Java”,您可能需要更改Base64调用。没有必要像Bouncy Castle这样的外部库。请遵守Maarten关于钥匙长度的说明

简单输出:

Load RSA PKCS#1 Public Keys
pkcs1PublicKey: Sun RSA public key, 2048 bits
  params: null
  modulus: 30333480050529072539152474433261825229175303911986187056546130987160889422922632165228273249976997833741424393377152058709551313162877595353675051556949998681388601725684016724167050111037861889500002806879899578986908702627237884089998121288607696752162223715667435607286689842713475938751449494999920670300421827737208147069624343973533326291094315256948284968840679921633097541211738122424891429452073949806872319418453594822983237338545978675594260211082913078702997218079517998196340177653632261614031770091082266225991043014081642881957716572923856737534043425399435601282335538921977379429228634484095086075971
  public exponent: 65537
代码:

import java.io.IOException;
导入java.security.GeneralSecurityException;
导入java.security.KeyFactory;
导入java.security.NoSuchAlgorithmException;
导入java.security.interfaces.RSAPublicKey;
导入java.security.spec.InvalidKeySpecException;
导入java.security.spec.X509EncodedKeySpec;
导入java.util.Base64;
公共类LoadPKCS1PublicKeypMSO{
//来自https://stackoverflow.com/a/54246646/8166854  19年1月18日1:36回答了马丁·博德维斯的问题
私有静态最终整数序列_标记=0x30;
私有静态最终整数位\字符串\标记=0x03;
私有静态最终字节[]无未使用字节=新字节[]{0x00};
私有静态最终字节[]RSA算法标识符序列=
{(字节)0x30,(字节)0x0d,
(字节)0x06,(字节)0x09,(字节)0x2a,(字节)0x86,(字节)0x48,(字节)0x86,(字节)0xf7,(字节)0x0d,(字节)0x01,(字节)0x01,(字节)0x01,
(字节)0x05,(字节)0x00};
公共静态void main(字符串[]args)引发GeneralSecurityException,IOException{
System.out.println(“加载RSA PKCS#1公钥”);
字符串rsaplickeypem=“----开始RSA公钥------\n”+
“MIIBCgKCAQEA8EmWJUZ/OSZ4VXTU2S+0M4BP9+s423gjMjoX+qP1iCnlcRcFWxt\n”+
“hQGN2CWSMZwR/vY9V0un/nsIxhZSWOH9iKzqUtZD4jt35jqOTeJ3PCSr48JirVDN\n”+
“Let7hRT37Ovfu5iieMN7ZNpkjeIG/CfT/QQl7R+kO/EnTmL3QjLKQNV/HhEbHS2/\n”+
“44x7PPoHqSqkOvl8GW0qtL39gTLWgAe801/W5PMCQ38CKG0OT2GDJMJQIXNMEAHK\n”+
“ATYGHCMDTXRBPHOSDRAFJ6SMPYHEMLBISHAQ7JM8NPPNK9QCEQ3Q+ERa5M6eM72\n”+
“PPF93G2P5CJKGYZFOIV09ZB/LJ2AW2GQWIDAKAB\n”+
“----结束RSA公钥------”;
RSAPublicKey pkcs1PublicKey=getPkcs1PublicKeyFromString(rsaPublicKeyPem);
System.out.println(“pkcs1PublicKey:+pkcs1PublicKey”);
}
public static RSAPublicKey getPkcs1PublicKeyFromString(字符串键)引发GeneralSecurityException{
字符串publicKeyPEM=key;
publicKeyPEM=publicKeyPEM.replace(“----开始RSA公钥----”,”);
publicKeyPEM=publicKeyPEM.replace(“----END RSA公钥----”,”);
publicKeyPEM=publicKeyPEM.replaceAll(“[\\r\\n]+”,”);
字节[]PKCS1PublicKeyEncode=Base64.getDecoder().decode(publicKeyPEM);
返回解码pkcs1publickey(pkcs1publickeyencoded);
}
/*
来自https://stackoverflow.com/a/54246646/8166854  19年1月18日1:36回答了马丁·博德维斯的问题
以下代码将PKCS#1编码公钥转换为SubjectPublicKeyInfo编码公钥,
这是RSA KeyFactory使用X509EncodedKeySpec接受的公钥编码-
正如SubjectPublicKeyInfo在X.509规范中定义的那样。
基本上,它是一种低级别的DER编码方案
将PKCS#1编码密钥包装为位字符串(标记0x03)和未使用密钥数的编码
位,一个字节值0x00);
在前面添加RSA算法标识符序列(RSA OID+空参数)-
预编码为字节数组常量;
最后将这两个元素放入一个序列(标记0x30)。
没有使用库。实际上,对于createSubjectPublicKeyInfoEncoding,甚至不需要导入语句。
笔记:
可能应该捕获NoSuchAlgorithmException并将其放入RuntimeException中;
私有方法CreateDerLength编码可能不接受负大小。
较大的密钥尚未测试,请验证这些密钥的编码-
我想它是有效的,但最好是安全的。
*/
公共静态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)
{
import java.io.IOException;
import java.security.GeneralSecurityException;
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 LoadPkcs1PublicKeyPemSo {
    // solution from https://stackoverflow.com/a/54246646/8166854  answered Jan 18 '19 at 1:36 Maarten Bodewes
    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 void main(String[] args) throws GeneralSecurityException, IOException {
        System.out.println("Load RSA PKCS#1 Public Keys");

        String rsaPublicKeyPem = "-----BEGIN RSA PUBLIC KEY-----\n" +
                "MIIBCgKCAQEA8EmWJUZ/Osz4vXtUU2S+0M4BP9+s423gjMjoX+qP1iCnlcRcFWxt\n" +
                "hQGN2CWSMZwR/vY9V0un/nsIxhZSWOH9iKzqUtZD4jt35jqOTeJ3PCSr48JirVDN\n" +
                "Let7hRT37Ovfu5iieMN7ZNpkjeIG/CfT/QQl7R+kO/EnTmL3QjLKQNV/HhEbHS2/\n" +
                "44x7PPoHqSqkOvl8GW0qtL39gTLWgAe801/w5PmcQ38CKG0oT2gdJmJqIxNmAEHk\n" +
                "atYGHcMDtXRBpOhOSdraFj6SmPyHEmLBishaq7Jm8NPPNK9QcEQ3q+ERa5M6eM72\n" +
                "PpF93g2p5cjKgyzzfoIV09Zb/LJ2aW2gQwIDAQAB\n" +
                "-----END RSA PUBLIC KEY-----";

        RSAPublicKey pkcs1PublicKey = getPkcs1PublicKeyFromString(rsaPublicKeyPem);
        System.out.println("pkcs1PublicKey: " + pkcs1PublicKey);
    }

    public static RSAPublicKey getPkcs1PublicKeyFromString(String key) throws GeneralSecurityException {
        String publicKeyPEM = key;
        publicKeyPEM = publicKeyPEM.replace("-----BEGIN RSA PUBLIC KEY-----", "");
        publicKeyPEM = publicKeyPEM.replace("-----END RSA PUBLIC KEY-----", "");
        publicKeyPEM = publicKeyPEM.replaceAll("[\\r\\n]+", "");
        byte[] pkcs1PublicKeyEncoding = Base64.getDecoder().decode(publicKeyPEM);
        return decodePKCS1PublicKey(pkcs1PublicKeyEncoding);
    }

/*
solution from https://stackoverflow.com/a/54246646/8166854  answered Jan 18 '19 at 1:36 Maarten Bodewes
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
    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);
    adds the RSA algorithm identifier sequence (the RSA OID + a null parameter) in front -
    pre-encoded as byte array constant;
    and finally puts both of those into a sequence (tag 0x30).

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

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.
*/

    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);
    }
}
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.security.PublicKey;
import java.io.StringReader;
...

private static PublicKey getPublicKey(String publicKeyc) throws Exception {
    PEMParser pemParser = new PEMParser(new StringReader(publicKeyc));
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter();
    SubjectPublicKeyInfo subjectPublicKeyInfo = (SubjectPublicKeyInfo)pemParser.readObject();
    PublicKey publicKey = jcaPEMKeyConverter.getPublicKey(subjectPublicKeyInfo);
    return publicKey;
}