美文网首页Android开发知识小集
在Android项目中使用AES、RSA加密

在Android项目中使用AES、RSA加密

作者: 苍蝇的梦 | 来源:发表于2018-08-24 17:45 被阅读623次

    2018-08-23遇到的一点小问题
    公司最近和银联合作,要求接口请求必须加密。网上搜一下有很多相关内容,这边贴几个有参考到的。
    Android数据加密
    AES加密CBC模式兼容互通四种编程语言平台
    Java进行 RSA 加解密时不得不考虑到的那些事儿

    AES加密

        /**
         * AES加密
         */
        public static String aesEncrypt(String content, String encryptKey, String encryptIv) {
            try {
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                int blockSize = cipher.getBlockSize();
                byte[] dataBytes = content.getBytes();
                int plaintextLength = dataBytes.length;
                if (plaintextLength % blockSize != 0) {
                    plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
                }
                byte[] plaintext = new byte[plaintextLength];
                System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
                SecretKeySpec keyspec = new SecretKeySpec(encryptKey.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(encryptIv.getBytes());
                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
                byte[] encrypted = cipher.doFinal(plaintext);
                return Base64Coder.encodeLines(encrypted);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("wannoo", "ASE加密出错:", e);
                return null;
            }
        }
    

    公司使用的AES选择的CBC模式,还要有偏移量,服务端给的,长度9位,因为PHP和IOS竟然都没有长度限制,能正常测试。然而我是需要16位(128 bits),不然会报错,这个网上在线加密,有的会提示,有的不会。然后和服务端协商后统一给的16位。

    java.security.InvalidAlgorithmParameterException: expected IV length of 16 but was 9
    at com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER.engineInitInternal(OpenSSLCipher.java:512)
    at com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:274)
    at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
    at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
    at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
    at javax.crypto.Cipher.init(Cipher.java:973)
    at javax.crypto.Cipher.init(Cipher.java:908)
    

    然后服务端给的秘钥是9位,我使用的这个方法会出错。没办法,网上又找方法解决。有的方法出来的文本竟然不一样,试了几次才解决。不过因为后面服务端又给了长度16位的秘钥,所以方法删掉了,下次有需要还要网上找。

    java.security.InvalidKeyException: Unsupported key size: 9 bytes
    at com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER$AES.checkSupportedKeySize(OpenSSLCipher.java:733)
    at com.android.org.conscrypt.OpenSSLCipher.checkAndSetEncodedKey(OpenSSLCipher.java:443)
    at com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:273)
    at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
    at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
    at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
    at javax.crypto.Cipher.init(Cipher.java:973)
    at javax.crypto.Cipher.init(Cipher.java:908)
    

    然后说一下获取的数据byte[] encrypted = cipher.doFinal(plaintext);不能直接new String()获取,需要base64HEX转换,具体看服务端需求。这一点包括生成AES加密秘钥时的处理也一样。

        /**
         * 随机生成秘钥
         */
        public static String getAesKey() {
            try {
                KeyGenerator kg = KeyGenerator.getInstance("AES");
                kg.init(128);
                SecretKey sk = kg.generateKey();
                byte[] b = sk.getEncoded();
                return byteToHexString(b);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("wannoo", "生成ASE秘钥出错:", e);
            }
            return null;
        }
    

    MD5加密

    这里顺便贴一下MD5加密的代码,至于其他的下次再说。

        /**
         * MD5加密
         */
        public static String md5Encrypt(String str) {
            if (str != null && !str.equals("")) {
                try {
                    MessageDigest md5 = MessageDigest.getInstance("MD5");
                    char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
                    byte[] md5Byte = md5.digest(str.getBytes("UTF8"));
                    StringBuilder sb = new StringBuilder();
                    for (byte aMd5Byte : md5Byte) {
                        sb.append(HEX[(int) (aMd5Byte & 0xff) / 16]);
                        sb.append(HEX[(int) (aMd5Byte & 0xff) % 16]);
                    }
                    str = sb.toString();
                } catch (Exception e) {
                    Log.e("wannoo", "MD5加密出错", e);
                }
            }
            return str;
        }
    

    RSA加密

    我们进行的RSA加密是服务端给公钥,我们加密后发送数据,服务端用私钥解密。
    收到的公钥是类似这样子的,很长一串。

     -----BEGIN PUBLIC KEY-----
    QWERTYUIOPpoiuytrewq
    asdfghjklAYLsU50c7gK5OxTrfdqe
    ZXCVBNMEulERs6a2rVCo5xCVoCuJjq
    1234567890mqv6CwIDAQAB
    -----END PUBLIC KEY-----
    

    然后加密时就一堆问题了,现在随便列几个:

    java.security.spec.InvalidKeySpecException: key spec not recognized
    

    这个错误是使用PKCS8EncodedKeySpec获取公钥对象,参考这个

    java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.Object.clone()' on a null object reference
    

    这个错误是使用X509EncodedKeySpec获取公钥对象时,没去掉 -----BEGIN PUBLIC KEY----------END PUBLIC KEY-----

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

    这个错误说是因为Android版本问题,具体是不是我就不懂了,参考这个

    java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unknown tag 13 encountered
    

    这个错误是直接将服务端给的公钥用.getBytes();获取byte[]时出错的

    java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: Extra data detected in stream
    
    java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unexpected end-of-contents marker
    

    上面那两个是将公钥用Base64转byte[]时出错的。找了很久原因才发现我之前用的Base64库不适用这个。还找了个包含sun.misc.BASE64Encoder的jar包,可惜不行。最后上Github找了一个,没问题了。
    贴一下里的客户端获取公钥和私钥的方法,测试时可以用。

    SecureRandom sr = new SecureRandom();//RSA算法要求有一个可信任的随机数源
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");//为RSA算法创建一个KeyPairGenerator对象
    kpg.initialize(KEYSIZE, sr);//利用上面的随机数据源初始化这个KeyPairGenerator对象
    KeyPair kp = kpg.generateKeyPair();//生成密匙对
    
    Key publicKey = kp.getPublic();//得到公钥
    byte[] publicKeyBytes = publicKey.getEncoded();
    String pub = new String(Base64.encodeBase64(publicKeyBytes),"UTF-8");
    
    Key privateKey = kp.getPrivate();// 得到私钥
    byte[] privateKeyBytes = privateKey.getEncoded();
    String pri = new String(Base64.encodeBase64(privateKeyBytes),"UTF-8");
    

    然后贴一下用公钥进行加密的步骤

    encryptKey = encryptKey.replaceAll("-----BEGIN PUBLIC KEY-----", "");
    encryptKey = encryptKey.replaceAll("-----END PUBLIC KEY-----", "");
    /**
     * RSA加密
     */
    public static String rsaEncrypt(String content, String encryptKey) {
       try {
            byte[] buffer = Base64Coder2.decode2(encryptKey);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            PublicKey pubKey = keyFactory.generatePublic(keySpec);
    
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] dataBytes = content.getBytes();
            byte[] encrypted = cipher.doFinal(dataBytes);
                
            return Base64Coder.encodeLines(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("wannoo", "RSA加密出错:", e);
           return null;
       }
    }
    

    相关文章

      网友评论

        本文标题:在Android项目中使用AES、RSA加密

        本文链接:https://www.haomeiwen.com/subject/acowbftx.html