RSA和AES双向加密(Android 和 Java)
接口数据加密
方案:客户端请求时数据加密,服务端解密;服务端返回数据时加密,客户端再去解密;需要两对公钥和私钥
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”)
核心类代码
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);
}
}
代码已经上传----->>> 快速通道