【问题标题】:Android Java - Encrypting String using a RSA public key .PEMAndroid Java - 使用 RSA 公钥 .PEM 加密字符串
【发布时间】:2021-03-30 16:20:06
【问题描述】:

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

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

{键}

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

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

【问题讨论】:

    标签: java android encryption rsa pem


    【解决方案1】:

    正如@Topaco 已经评论的那样,您的 RSA 公钥采用 PEM 编码,但采用 PKCS#1 格式,而不是 Java 可读的 PKCS#8 格式”开箱即用”。

    @Maarten Bodewes 在此处提供了以下解决方案,因此 (https://stackoverflow.com/a/54246646/8166854) 将完成读取并将其转换为 (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;
    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);
        }
    }
    

    【讨论】:

      【解决方案2】:

      Michael Fehr 的回答展示了如何在没有第三方库的情况下加载 PKCS#1 格式的公钥。如果后者是一个要求,那么这就是你必须走的路。
      否则,如果您使用 BouncyCastle,可能还会考虑一些不太复杂的解决方案,因此值得一提(尽管给定的答案已经被接受):

      以下方法需要 PKCS#1 格式的公钥,PEM 编码并将其加载到 java.security.PublicKey 实例中:

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

      另一个类似的紧凑实现可以在here找到。

      要在 Android 中使用 BouncyCastle,必须在 build.gradle 文件的 dependencies 部分中引用与所用 API 级别对应的 BouncyCastle 依赖项。我使用了 API Level 28 (Pie) 并引用了以下依赖项(假设为 Android Studio):

      implementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
      

      【讨论】:

      • Bouncy Castle 的实现要优雅得多,完全同意。就个人而言,我尝试使用“内置”资源解决问题,但有时我无法避免它,然后我使用例如公元前。 @Topaco & Board:圣诞快乐,2021 年更美好。
      • @MichaelFehr - 第三方库提供的优势何时超过额外的依赖(假设要求首先允许一个)?有时决定并不那么容易。在这种特殊情况下,我更喜欢 BouncyCastle,因为它的实现更加紧凑。但这是一个见仁见智的问题,其他开发人员可能会做出不同的决定。圣诞快乐,新年快乐。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-29
      • 2014-09-27
      • 1970-01-01
      • 2013-06-08
      • 1970-01-01
      • 2014-01-07
      • 2021-02-21
      相关资源
      最近更新 更多