RSA和AES双向加密(Android 和 Java)

作者: 他晓 | 来源:发表于2019-10-08 21:46 被阅读0次

专注于Android开发,分享经验总结,欢迎加入

QQ群

接口数据加密

方案:客户端请求时数据加密,服务端解密;服务端返回数据时加密,客户端再去解密;需要两对公钥和私钥

RSA非对称加密,公钥加密,私钥去解密,AES是对称加密,由于RSA加密数据有上限,需要做分段加解密处理,在解密时很容易报错,所以用RSA公钥去加密AES,AES去加解密内容

在做的过程中遇到一些问题,特此记录

  • Android端加解密都正常,Java端加解密也正常,当Android和Java交互时RSA解密报错,出现乱码,原因是RSA加解密的格式不同,转byte时加上编码方式string.getBytes("utf-8")

  • 在Android端获取加解密的Cipher类时要使用Cipher.getInstance("RSA/ECB/PKCS1Padding");

  • 在Java端使用Cipher.getInstance("RSA")来获取

RSA Android端加密解密方式,Java端使用Cipher.getInstance("RSA")

RSA2.jpg RSA3.jpg

核心类代码

Base64编码解码工具类

public final class Base64 {

    private static final int BASELENGTH = 128;
    private static final int LOOKUPLENGTH = 64;
    private static final int TWENTYFOURBITGROUP = 24;
    private static final int EIGHTBIT = 8;
    private static final int SIXTEENBIT = 16;
    private static final int FOURBYTE = 4;
    private static final int SIGN = -128;
    private static char PAD = '=';
    private static byte[] base64Alphabet = new byte[BASELENGTH];
    private static char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];

    static {
        for (int i = 0; i < BASELENGTH; ++i) {
            base64Alphabet[i] = -1;
        }
        for (int i = 'Z'; i >= 'A'; i--) {
            base64Alphabet[i] = (byte) (i - 'A');
        }
        for (int i = 'z'; i >= 'a'; i--) {
            base64Alphabet[i] = (byte) (i - 'a' + 26);
        }

        for (int i = '9'; i >= '0'; i--) {
            base64Alphabet[i] = (byte) (i - '0' + 52);
        }

        base64Alphabet['+'] = 62;
        base64Alphabet['/'] = 63;

        for (int i = 0; i <= 25; i++) {
            lookUpBase64Alphabet[i] = (char) ('A' + i);
        }

        for (int i = 26, j = 0; i <= 51; i++, j++) {
            lookUpBase64Alphabet[i] = (char) ('a' + j);
        }

        for (int i = 52, j = 0; i <= 61; i++, j++) {
            lookUpBase64Alphabet[i] = (char) ('0' + j);
        }
        lookUpBase64Alphabet[62] = (char) '+';
        lookUpBase64Alphabet[63] = (char) '/';

    }

    private static boolean isWhiteSpace(char octect) {
        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
    }

    private static boolean isPad(char octect) {
        return (octect == PAD);
    }

    private static boolean isData(char octect) {
        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
    }

    /**
     * Encodes hex octects into Base64
     *
     * @param binaryData Array containing binaryData
     * @return Encoded Base64 array
     */
    public static String encode(byte[] binaryData) {

        if (binaryData == null) {
            return null;
        }

        int lengthDataBits = binaryData.length * EIGHTBIT;
        if (lengthDataBits == 0) {
            return "";
        }

        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1
                : numberTriplets;
        char encodedData[] = null;

        encodedData = new char[numberQuartet * 4];

        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;

        int encodedIndex = 0;
        int dataIndex = 0;

        for (int i = 0; i < numberTriplets; i++) {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];

            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)
                    : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4)
                    : (byte) ((b2) >> 4 ^ 0xf0);
            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6)
                    : (byte) ((b3) >> 6 ^ 0xfc);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
        }

        // form integral number of 6-bit groups
        if (fewerThan24bits == EIGHTBIT) {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)
                    : (byte) ((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex++] = PAD;
            encodedData[encodedIndex++] = PAD;
        } else if (fewerThan24bits == SIXTEENBIT) {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)
                    : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4)
                    : (byte) ((b2) >> 4 ^ 0xf0);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex++] = PAD;
        }

        return new String(encodedData);
    }

    /**
     * Decodes Base64 data into octects
     *
     * @param encoded string containing Base64 data
     * @return Array containind decoded data.
     */
    public static byte[] decode(String encoded) {

        if (encoded == null) {
            return null;
        }

        char[] base64Data = encoded.toCharArray();
        // remove white spaces
        int len = removeWhiteSpace(base64Data);

        if (len % FOURBYTE != 0) {
            return null;// should be divisible by four
        }

        int numberQuadruple = (len / FOURBYTE);

        if (numberQuadruple == 0) {
            return new byte[0];
        }

        byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;

        int i = 0;
        int encodedIndex = 0;
        int dataIndex = 0;
        decodedData = new byte[(numberQuadruple) * 3];

        for (; i < numberQuadruple - 1; i++) {

            if (!isData((d1 = base64Data[dataIndex++]))
                    || !isData((d2 = base64Data[dataIndex++]))
                    || !isData((d3 = base64Data[dataIndex++]))
                    || !isData((d4 = base64Data[dataIndex++]))) {
                return null;
            }// if found "no data" just return null

            b1 = base64Alphabet[d1];
            b2 = base64Alphabet[d2];
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];

            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
        }

        if (!isData((d1 = base64Data[dataIndex++]))
                || !isData((d2 = base64Data[dataIndex++]))) {
            return null;// if found "no data" just return null
        }

        b1 = base64Alphabet[d1];
        b2 = base64Alphabet[d2];

        d3 = base64Data[dataIndex++];
        d4 = base64Data[dataIndex++];
        if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters
            if (isPad(d3) && isPad(d4)) {
                if ((b2 & 0xf) != 0)// last 4 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 1];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
                return tmp;
            } else if (!isPad(d3) && isPad(d4)) {
                b3 = base64Alphabet[d3];
                if ((b3 & 0x3) != 0)// last 2 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 2];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
                return tmp;
            } else {
                return null;
            }
        } else { // No PAD e.g 3cQl
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];
            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);

        }

        return decodedData;
    }

    /**
     * remove WhiteSpace from MIME containing encoded Base64 data.
     *
     * @param data the byte array of base64 data (with WS)
     * @return the new length
     */
    private static int removeWhiteSpace(char[] data) {
        if (data == null) {
            return 0;
        }

        // count characters that's not whitespace
        int newSize = 0;
        int len = data.length;
        for (int i = 0; i < len; i++) {
            if (!isWhiteSpace(data[i])) {
                data[newSize++] = data[i];
            }
        }
        return newSize;
    }
}

Base64转换工具类

/**
 * Base64转换工具类
 */
public class Base64Util {
    /**
     * 字节数组转Base64编码
     *
     * @param bytes 字节数组
     * @return 字节数组
     */
    public static String byte2Base64(byte[] bytes) {
        return Base64.encode(bytes);
    }


    /**
     * Base64编码转字节数组
     *
     * @param base64Key Base64编码
     * @return Base64编码
     * @throws IOException
     */
    public static byte[] base642Byte(String base64Key) throws IOException {
        return Base64.decode(base64Key);
    }
}

AES加密工具类

/**
 * AES加密工具类
 * 
 */
public class AESUtil {

    /**
     * 生成AES秘钥,然后Base64编码
     *
     * @return Base64编码
     * @throws Exception
     */
    public static String genKeyAES() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128);
        SecretKey key = keyGen.generateKey();
        return Base64Util.byte2Base64(key.getEncoded());
    }

    /**
     * 将Base64编码后的AES秘钥转换成SecretKey对象
     *
     * @param base64Key
     * @return SecretKey对象
     * @throws Exception
     */
    public static SecretKey loadKeyAES(String base64Key) throws Exception {
        byte[] bytes = Base64Util.base642Byte(base64Key);
        return new SecretKeySpec(bytes, "AES");
    }


    /**
     * AES加密
     *
     * @param source 加密内容
     * @param key    SecretKey对象
     * @return 加密后的字节数组
     * @throws Exception
     */
    public static byte[] encryptAES(byte[] source, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(source);
    }


    /**
     * AES解密
     *
     * @param source 解密内容
     * @param key    SecretKey对象
     * @return 解密后的字节数组
     * @throws Exception
     */
    public static byte[] decryptAES(byte[] source, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(source);
    }

}

RSA加密工具类

/**
 * RSA加密工具类
 * EC和RSA的优缺点:
 * RSA的优点:JDK自己支持。不需要第三方库。同时支持RSA的开发库也很多(最典型的就是OpenSSL)
 * EC的缺点: 需要第三方库,支持的广度比不上RSA
 * EC的优点: 1.在达到相同加密程度下,EC需要的秘钥长度比RSA要短得多
 *           2.bouncycastle实现的EC加密算法,对密文长度的限制比较松。在下面的测试程序中构造了一个长字符串加密,没有报错。
 * RSA的加密则是有限制的,必须分片。不过我不知道是不是bouncycastle自己事先做了分片
 * 明文长度(bytes) = (加密长度/8 -11)
 * 片数=(明文长度(bytes)/(密钥长度(bytes)-11))的整数部分+1,就是不满一片的按一片算
 * 密文长度=密钥长度*片数
 * <p>
 * 在移动端获取解密的Cipher类时要使用Cipher.getInstance("RSA/ECB/PKCS1Padding");
 * 在后端使用Cipher.getInstance("RSA");来获取
 * <p>
 * 
 */
public class RSAUtil {


    /**
     * 生成RSA秘钥
     *
     * @return 秘钥对
     * @throws Exception
     */
    public static KeyPair getKeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");//加密方式
        keyPairGenerator.initialize(2048);//加密长度
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * 获取RSA公钥并转为Base64编码
     *
     * @param keyPair 秘钥对
     * @return RSA公钥
     */
    public static String getPublicKey(KeyPair keyPair) {
        PublicKey publicKey = keyPair.getPublic();
        byte[] bytes = publicKey.getEncoded();
        return Base64Util.byte2Base64(bytes);
    }


    /**
     * 获取RSA私钥并转为Base64编码
     *
     * @param keyPair 秘钥对
     * @return RSA私钥
     */
    public static String getPrivateKey(KeyPair keyPair) {
        PrivateKey privateKey = keyPair.getPrivate();
        byte[] bytes = privateKey.getEncoded();
        return Base64Util.byte2Base64(bytes);
    }


    /**
     * 将Base64编码后的公钥转换成PublicKey对象
     *
     * @param pubStr Base64编码后的公钥
     * @return PublicKey公钥对象
     * @throws Exception
     */
    public static PublicKey string2PublicKey(String pubStr) throws Exception {
        byte[] keyBytes = Base64Util.base642Byte(pubStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 将Base64编码后的私钥转换成PrivateKey对象
     *
     * @param priStr Base64编码后的私钥
     * @return PrivateKey私钥对象
     * @throws Exception
     */
    public static PrivateKey string2PrivateKey(String priStr) throws Exception {
        byte[] keyBytes = Base64Util.base642Byte(priStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }


    /**
     * 公钥加密
     * 在移动端获取解密的Cipher类时要使用Cipher.getInstance("RSA/ECB/PKCS1Padding");
     * 在后端使用Cipher.getInstance("RSA");来获取
     *
     * @param content   加密的内容
     * @param publicKey PublicKey公钥对象
     * @return 加密后的字节数组
     * @throws Exception
     */
    public static byte[] publicEncrypt(byte[] content, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(content);
    }


    /**
     * 私钥去解密
     * 在移动端获取解密的Cipher类时要使用Cipher.getInstance("RSA/ECB/PKCS1Padding");
     * 在后端使用Cipher.getInstance("RSA");来获取
     *
     * @param content    需要解密的内容
     * @param privateKey PrivateKey私钥对象
     * @return 解密后的字节数组
     * @throws Exception
     */
    public static byte[] privateDecrypt(byte[] content, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(content);
    }
}

双向RSA + AES 工具类

/**
 * 双向RSA + AES 工具类
 */
public class HttpEncryptUtil {

    /**
     * APP端加密请求内容
     *
     * @param action  需要加密的路径地址
     * @param content 需要加密的内容
     * @return 返回map
     * @throws Exception
     */
    public static Map<String, String> appEncrypt(String action, String content) throws Exception {
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        //将Base64编码后的(server端)公钥转换成PublicKey对象
        PublicKey serverPublicKey = RSAUtil.string2PublicKey(KeyUtil.SERVER_PUBLIC_KEY.replaceAll("\n", ""));
        //每次都随机生成AES秘钥
        String aesKeyStr = AESUtil.genKeyAES();
        SecretKey aesKey = AESUtil.loadKeyAES(aesKeyStr);
        //用(server端)公钥加密AES秘钥
        byte[] encryptAesKey = RSAUtil.publicEncrypt(aesKeyStr.getBytes("utf-8"), serverPublicKey);
        //用AES秘钥加密路径地址
        byte[] encryptActionRequest = AESUtil.encryptAES(action.getBytes("utf-8"), aesKey);
        //用AES秘钥加密请求内容
        byte[] encryptContentRequest = AESUtil.encryptAES(content.getBytes("utf-8"), aesKey);
        map.put("ak", Base64Util.byte2Base64(encryptAesKey));//加密aesKey
        map.put("act", Base64Util.byte2Base64(encryptActionRequest));//加密路径
        map.put("data", Base64Util.byte2Base64(encryptContentRequest));//加密内容
        LogUtils.e("----AES加密秘钥 ----" + aesKeyStr);
        LogUtils.e("----AES加密数据 ---- ak==" + Base64Util.byte2Base64(encryptAesKey));
        LogUtils.e("----AES加密数据 ---- act==" + Base64Util.byte2Base64(encryptActionRequest));
        LogUtils.e("----AES加密数据 ---- data==" + Base64Util.byte2Base64(encryptContentRequest));
        return map;
    }


    /**
     * APP解密服务器的响应内容
     *
     * @param content 内容
     * @return
     * @throws Exception
     */
    public static String appDecrypt(String content) throws Exception {
        JSONObject result = new JSONObject(content);
        String decryptAesKey = (String) result.get("ak");//加密后的aesKey
        String decryptContent = (String) result.get("data");//内容
        //将Base64编码后的APP私钥转换成PrivateKey对象
        PrivateKey appPrivateKey = RSAUtil.string2PrivateKey(KeyUtil.APP_PRIVATE_KEY.replace("\n", ""));
        //用APP私钥解密AES秘钥
        byte[] aesKeyBytes = RSAUtil.privateDecrypt(Base64Util.base642Byte(decryptAesKey), appPrivateKey);
        //用AES秘钥解密请求内容
        String base64Key = new String(aesKeyBytes);
        SecretKey aesKey = AESUtil.loadKeyAES(base64Key);
        byte[] response = AESUtil.decryptAES(Base64Util.base642Byte(decryptContent), aesKey);
        LogUtils.e("----AES解密秘钥 ----" + base64Key);
        return new String(response);
    }
}

代码已经上传----->>> 快速通道

相关文章

网友评论

    本文标题:RSA和AES双向加密(Android 和 Java)

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