Retrofit+OkHttp 参数使用AES加密Demo
Posted microhex
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Retrofit+OkHttp 参数使用AES加密Demo相关的知识,希望对你有一定的参考价值。
最近在做App代码安全方面的优化,特此记录一下。
我们现在App大多数都是基于Retrofit+OkHttp
的网络请求框架,现在的需求是需要将请求的参数进行加密传输,下面图片中我们进行一个对比,一个是明文传输,一个是密文传输:
明文传输 | 密文传输 |
---|---|
由于考虑到加密和解密的效率,我们现在选用的是AES
对称加密。至于对称和非对称加密,可以参考以往的文章。
1. 客户端修改
在未加密前,我们客户端与后端协商,是采用Json
的形式传输字段请求的,比如一个登录接口,还有userName
和password
,那么就直接传输为:
"password":"12345","userName":"tom"
在Http
请求体可以看到如下内容:
POST /user/login/ HTTP/1.1
Content-Type: application/json; charset=UTF-8
Content-Length: 37
Host: 192.168.2.105:8080
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.3
Pragma: no-cache
Cache-Control: no-cache
"password":"12345","userName":"tom"
我们现在的目标,是需要将
"password":"12345","userName":"tom"
加密,加密成密文之后,再传输给后端服务器。
由于采用的是OKhttp
网络请求框架,我们很容易会想到使用拦截器Interceptor
来实现我们的需求。首先画图说明一下Interceptor
的逻辑:
我们将要对需要出去的数据进行加密,然后对返回的数据进行解密。那么因此我们可以定义一下DataEncryptInterceptor
逻辑,代码如下:
public class DataEncryptInterceptor implements Interceptor
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException
Request request = chain.request();
RequestBody oldBodyRequest = request.body();
Buffer requestBuffer = new Buffer();
oldBodyRequest.writeTo(requestBuffer);
String oldBodyStr = requestBuffer.readUtf8();
requestBuffer.close();
Log.d("TAG", "the old body str is :" + oldBodyStr);
//String randomKeyValue = "hello_" + System.currentTimeMillis() + "_world";
String randomKeyValue = "zhangsanlisiwangwu";
String newBodyStr = AESUtils.encrypt(oldBodyStr,randomKeyValue);
if (TextUtils.isEmpty(newBodyStr)) newBodyStr = "";
MediaType mediaType = MediaType.parse("text/plain;charset=utf-8");
RequestBody newRequestBody = RequestBody.create(mediaType, newBodyStr);
//构建新的request
Request newRequest = request.newBuilder().header("Content-type", newRequestBody.contentType().toString())
.header("Content-Length", String.valueOf(newRequestBody.contentLength()))
.method(request.method(), newRequestBody)
.header("key", randomKeyValue)
.build();
Response response = chain.proceed(newRequest);
if (response.code() / 200 == 1)
ResponseBody oldResponseBody = response.body();
String oldResponseBodyStr = oldResponseBody.string();
String newResponseBodyStr = AESUtils.decrypt(oldResponseBodyStr,randomKeyValue);
if (TextUtils.isEmpty(newResponseBodyStr)) newResponseBodyStr = "data decrypy error";
ResponseBody responseBody = ResponseBody.create(mediaType, newResponseBodyStr);
response = response.newBuilder().body(responseBody).build();
return response;
整体的请求大概分为这8步,逻辑已经写的很清楚了。这里讲一下第二步,对旧的请求体进行AES加密
。我们知道对AES加密
时,需要提供密钥的,就像是开一把锁,需要有钥匙一样。这里我写的比较简单,使用的是固定的值,在正式的商业开发中,我们需要与后端进行协商,尽量每次传输使用动态的,破解难度比较的密钥。
写完之后,直接在OkHttpClient
中添加即可:
OkHttpClient.Builder().
connectTimeout(8_000, TimeUnit.MILLISECONDS).
readTimeout(8_000,TimeUnit.MILLISECONDS).
addInterceptor(logger).
addInterceptor(DataEncryptInterceptor()).
build()
然后其它的地方几乎不需要动任何代码,直接就能实现数据的加密和解密了。
对于几个比较重要的类,现在直接把代码贴出来,到时候可以直接烤着拿去用:
AESUtils
:
import java.io.Closeable;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import java.nio.charset.Charset;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AESUtils
/**
* 加密算法
*/
private static final String KEY_ALGORITHM = "AES";
/**
* AES 的 密钥长度,32 字节,范围:16 - 32 字节
*/
public static final int SECRET_KEY_LENGTH = 32;
/**
* 字符编码
*/
private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
/**
* 秘钥长度不足 16 个字节时,默认填充位数
*/
private static final String DEFAULT_VALUE = "0";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/**
* 加密密码,长度:16 或 32 个字符(随便定义)
*/
private static final String secretKey = "zhangsanlisiwangwu";
public static String getSecretKey()
return secretKey;
public static String encrypt(String data, String secretKey)
try
//创建密码器
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
//初始化为加密密码器
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKey));
byte[] encryptByte = cipher.doFinal(data.getBytes(CHARSET_UTF8));
// 将加密以后的数据进行 Base64 编码
return base64Encode(encryptByte);
catch (Exception e)
handleException(e);
return null;
/**
* AES 加密
*
* @param data 待加密内容
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String data)
return encrypt(data,secretKey);
public static String decrypt(String base64Data, String secretKey)
try
byte[] data = base64Decode(base64Data);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
//设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKey));
//执行解密操作
byte[] result = cipher.doFinal(data);
return new String(result, CHARSET_UTF8);
catch (Exception e)
handleException(e);
return null;
/**
* AES 解密
*
* @param base64Data 加密的密文 Base64 字符串
*/
public static String decrypt(String base64Data)
return decrypt(base64Data,secretKey);
/**
* 使用密码获取 AES 秘钥
*/
public static SecretKeySpec getSecretKey(String secretKey)
secretKey = toMakeKey(secretKey, SECRET_KEY_LENGTH, DEFAULT_VALUE);
return new SecretKeySpec(secretKey.getBytes(CHARSET_UTF8), KEY_ALGORITHM);
/**
* 如果 AES 的密钥小于 @code length 的长度,就对秘钥进行补位,保证秘钥安全。
*
* @param secretKey 密钥 key
* @param length 密钥应有的长度
* @param text 默认补的文本
* @return 密钥
*/
private static String toMakeKey(String secretKey, int length, String text)
// 获取密钥长度
int strLen = secretKey.length();
// 判断长度是否小于应有的长度
if (strLen < length)
// 补全位数
StringBuilder builder = new StringBuilder();
// 将key添加至builder中
builder.append(secretKey);
// 遍历添加默认文本
for (int i = 0; i < length - strLen; i++)
builder.append(text);
// 赋值
secretKey = builder.toString();
return secretKey;
/**
* 将 Base64 字符串 解码成 字节数组
*/
public static byte[] base64Decode(String data)
return Base64.decode(data, Base64.NO_WRAP);
/**
* 将 字节数组 转换成 Base64 编码
*/
public static String base64Encode(byte[] data)
return Base64.encodeToString(data, Base64.NO_WRAP);
/**
* 处理异常
*/
private static void handleException(Exception e)
e.printStackTrace();
System.out.println(e.getLocalizedMessage());
/**
* 初始化 AES Cipher
*
* @param secretKey 密钥
* @param cipherMode 加密模式
* @return 密钥
*/
private static Cipher initFileAESCipher(String secretKey, int cipherMode)
try
// 创建密钥规格
SecretKeySpec secretKeySpec = getSecretKey(secretKey);
// 获取密钥
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 初始化
cipher.init(cipherMode, secretKeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
return cipher;
catch (Exception e)
handleException(e);
return null;
/**
* 关闭流
*
* @param closeable 实现Closeable接口
*/
private static void closeStream(Closeable closeable)
try
if (closeable != null) closeable.close();
catch (Exception e)
handleException(e);
其中还有一个Base64
类逻辑,也算是比较简单:
Base64
:
import java.io.UnsupportedEncodingException;
import java.io.UnsupportedEncodingException;
public class Base64
/**
* Default values for encoder/decoder flags.
*/
public static final int DEFAULT = 0;
/**
* Encoder flag bit to omit the padding '=' characters at the end
* of the output (if any).
*/
public static final int NO_PADDING = 1;
/**
* Encoder flag bit to omit all line terminators (i.e., the output
* will be on one long line).
*/
public static final int NO_WRAP = 2;
/**
* Encoder flag bit to indicate lines should be terminated with a
* CRLF pair instead of just an LF. Has no effect if @code
* NO_WRAP is specified as well.
*/
public static final int CRLF = 4;
/**
* Encoder/decoder flag bit to indicate using the "URL and
* filename safe" variant of Base64 (see RFC 3548 section 4) where
* @code - and @code _ are used in place of @code + and
* @code /.
*/
public static final int URL_SAFE = 8;
public static final int NO_CLOSE = 16;
// --------------------------------------------------------
// shared code
// --------------------------------------------------------
/* package */ static abstract class Coder
public byte[] output;
public int op;
/**
* Encode/decode another block of input data. this.output is
* provided by the caller, and must be big enough to hold all
* the coded data. On exit, this.opwill be set to the length
* of the coded data.
*
* @param finish true if this is the final call to process for
* this object. Will finalize the coder state and
* include any final bytes in the output.
*
* @return true if the input so far is good; false if some
* error has been detected in the input stream..
*/
public abstract boolean process(byte[] input, int offset, int len, boolean finish);
/**
* @return the maximum number of bytes a call to process()
* could produce for the given number of input bytes. This may
* be an overestimate.
*/
public abstract int maxOutputSize(int len);
// --------------------------------------------------------
// decoding
// --------------------------------------------------------
/**
* Decode the Base64-encoded data in input and return the data in
* a new byte array.
*
* <p>The padding '=' characters at the end are considered optional, but
* if any are present, there must be the correct number of them.
*
* @param str the input String to decode, which is converted to
* bytes using the default charset
* @param flags controls certain features of the decoded output.
* Pass @code DEFAULT to decode standard Base64.
*
* @throws IllegalArgumentException if the input contains
* incorrect padding
*/
public static byte[] decode(String str, int flags)
return decode(str.getBytes(), flags);
/**
* Decode the Base64-encoded data in input and return the data in
* a new byte array.
*
* <p>The padding '=' characters at the end are considered optional, but
* if any are present, there must be the correct number of them.
*
* @param input the input array to decode
* @param flags controls certain features of the decoded output.
* Pass @code DEFAULT to decode standard Base64.
*
* @throws IllegalArgumentException if the input contains
* incorrect padding
*/
public static byte[] decode(byte[] input, int flags)
return decode(input, 0, input.length, flags);
/**
* Decode the Base64-encoded data in input and return the data in
* a new byte array.
*
* <p>The padding '=' characters at the end are considered optional, but
* if any are present, there must be the correct number of them.
*
* @param input the data to decode
* @param offset the position within the input array at which to start
* @param len the number of bytes of input to decode
* @param flags controls certain features of the decoded output.
* Pass @code DEFAULT to decode standard Base64.
*
* @throws IllegalArgumentException if the input contains
* incorrect padding
*/
public static byte[] decode(byte[] input, int offset, int len, int flags)
// Allocate space for the most data the input could represent.
// (It could contain less if it contains whitespace, etc.)
Decoder decoder = new Decoder(flags, new byte[len*3/4]);
if (!decoder.process(input, offset, len, true))
throw new IllegalArgumentException("bad base-64");
// Maybe we got lucky and allocated exactly enough output space.
if (decoder.op == decoder.output.length)
return decoder.output;
// Need to shorten the array, so allocate a new one of the
// right size and copy.
byte[] temp = new byte[decoder.op];
System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
return temp;
/* package */ static class Decoder extends Coder
/**
* Lookup table for turning bytes into their position in the
* Base64 alphabet.
*/
private static final int DECODE[] =
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25Retrofit+OkHttp 参数使用AES加密Demo
Android Okhttp/Retrofit网络请求加解密实现方案
如何使用 Retrofit 2 + OkHttp 3 加密/隐藏 HTTPS 调用的主体?