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

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加密)