Android客户端与服务端数据加密传输方案详解
作者:麦田里的守望者江 发布时间:2023-07-14 13:55:37
前言
在网络通信中,通信传输数据容易被截取或篡改,如果在传输用户隐私数据过程中,被不法分子截取或篡改,就可能导致用户受到伤害,比如被诈 骗,所以对客户端与服务端的传输数据加密,是网络通信中必不可少的。
数据加密方案
首先,客户端与服务端商量好数据加密协议,对传输数据做到安全保护。
安全保护至少需要有下面两点:
采用HTTPS协议
采用公钥密码体制RSA算法对数据加密
现在安全是保证了,但还要考虑到性能问题,由于RSA算法对数据加密时运算速度慢,所以直接把所有传输数据都用RSA加密,会导致网络通信慢,这对用户将是不好的体验。由于对称密钥密码体制中的AES运算速度快且安全性高,所以结合AES对传输数据加密是非常好的方案。
下面是对客户端与服务端通信数据加密比较通用的方案:
客户端生成AES密钥,并保存AES密钥
客户端用AES密钥对请求传输数据进行加密
客户端使用RSA公钥对AES密钥加密,然后把值放到自定义的一个请求头中
客户端向服务端发起请求
服务端拿到自定义的请求头值,然后使用RSA私钥解密,拿到AES密钥
服务端使用AES密钥对请求数据解密
服务端对响应数据使用AES密钥加密
服务端向客户端发出响应
客户端拿到服务端加密数据,并使用之前保存的AES密钥解密
注意:传输数据使用AES密钥加密,RSA公钥对AES密钥加密。RSA公钥和私钥由服务端生成,公钥放在客户端,私钥放在服务端。公钥私钥要私密保护,不能随便给人。
具体流程图如下:
上面网络通信过程是安全的,可以保证通信数据即使被截取了,也无法获得任何有效信息;即使被篡改了,也无法被客户端和服务端验证通过。
数据加密细节
AES加解密
生成AES密钥和使用AES密钥加密、解密,有下面重要的几点:
1.密钥长度的选择:AES能支持的密钥长度可以为128,192,256位(也即16,24,32个字节),这里选择128位。
2.算法/模式/填充的选择:
算法/模式/填充 | 字节加密后数据长度 | 不满16字节加密后长度 |
---|---|---|
AES/CBC/NoPadding | 16 | 不支持 |
AES/CBC/PKCS5Padding | 32 | 16 |
AES/CBC/ISO10126Paddind | 32 | 16 |
AES/CFB/NoPadding | 16 | 原始数据长度 |
AES/CFB/PKCS5Padding | 32 | 16 |
AES/CFB/ISO10126Padding | 32 | 16 |
AES/ECB/NoPadding | 16 | 不支持 |
AES/ECB/PKCS5Padding | 32 | 16 |
AES/ECB/ISO10126Padding | 32 | 16 |
AES/ECB/ISO10126Padding | 32 | 16 |
AES/OFB/NoPadding | 16 | 原始数据长度 |
AES/OFB/PKCS5Padding | 32 | 16 |
AES/OFB/ISO10126Padding | 32 | 16 |
AES/PCBC/NoPadding | 16 | 不支持 |
AES/PCBC/PKCS5Padding | 32 | 16 |
AES/PCBC/ISO10126Padding | 32 | 16 |
这里选择AES/CBC/PKCS5Padding。
3.添加向量 IvParameterSpec:增强算法强度。 4.编码格式选择:UTF-8。
下面为具体代码实现:
private final int AES_KEY_LENGTH = 16;//密钥长度16字节,128位
private final String AES_ALGORITHM = "AES";//算法名字
private final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";//算法/模式/填充
private final String AES_IV = "0112030445060709";//使用CBC模式,需要一个向量iv,可增加加密算法的强度
private final String AES_STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLOP";
private final Charset UTF_8 = Charset.forName("UTF-8");//编码格式
/**
* 使用AES加密
*
* @param aesKey AES Key
* @param data 被加密的数据
* @return AES加密后的数据
*/
private byte[] encodeAES(byte[] aesKey, String data) {
if (aesKey == null || aesKey.length != AES_KEY_LENGTH) {
return null;
}
SecretKeySpec keySpec = new SecretKeySpec(aesKey, AES_ALGORITHM);
try {
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
IvParameterSpec iv = new IvParameterSpec(AES_IV.getBytes(UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
return cipher.doFinal(data.getBytes(UTF_8));
} catch (Exception e) {
Log.d(TAG, e.getMessage(), e);
}
return null;
}
/**
* 使用AES解密
*
* @param aesKey AES Key
* @param data 被解密的数据
* @return AES解密后的数据
*/
private String decodeAES(byte[] aesKey, byte[] data) {
if (aesKey == null || aesKey.length != AES_KEY_LENGTH) {
return null;
}
SecretKeySpec keySpec = new SecretKeySpec(aesKey, AES_ALGORITHM);
try {
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
IvParameterSpec iv = new IvParameterSpec(AES_IV.getBytes(UTF_8));
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
return new String(cipher.doFinal(data), UTF_8);
} catch (Exception e) {
Log.d(TAG, e.getMessage(), e);
}
return null;
}
private int getRandom(int count) {
return (int) Math.round(Math.random() * (count));
}
/**
* 生成AES key
*
* @return AES key
*/
private String initAESKey() {
StringBuilder sb = new StringBuilder();
int len = AES_STRING.length();
for (int i = 0; i < AES_KEY_LENGTH; i++) {
sb.append(AES_STRING.charAt(getRandom(len - 1)));
}
return sb.toString();
}
现在AES密钥和AES加密、解密都有了,在通常情况下,还会对加密、解密过程进行Base64 编码、解码。
Base64编码,选择 URL_SAFE 标识,也就是 "-" 和 “_” 会被替换为 "+" 和 "/",:
/**
* 对数据进行Base64编码,使用的是{@link android.util.Base64},而且flags需要使用 {@link android.util.Base64#URL_SAFE,android.util#Base64.NO_PADDING,android.util.Base64#NO_WRAP}。
*
* @param input 来源数据
* @return Base64编码的数据
*/
private String encodeBase64(byte[] input) {
return new String(Base64.encode(input, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP), UTF_8);
}
Base64解码,和编码对应:
/**
* 对数据进行Base64解码,使用的是{@link android.util.Base64},而且flags需要使用 {@link android.util.Base64#URL_SAFE,android.util.Base64#NO_WRAP},主要是为了和Base64加密对应。
*
* @param str 需要解码的数据
* @return Base64解码后的数据
*/
private byte[] decodeBase64(String str) {
return Base64.decode(str.getBytes(UTF_8), Base64.URL_SAFE | Base64.DEFAULT);
}
RSA公钥加密
RSA公钥是从服务端拿到的,这个公钥不能被泄漏,必须做到安全保护。
使用RSA公钥加密,也有几个重要点:
1.拿到的公钥是Base64 编码后的,所以首先需要对公钥Base64解码。
2.算法/模式/填充的选择:RSA/ECB/PKCS1Padding
3.编码格式选择:UTF-8。
注意:使用RSA公钥加密的流程对应的就是服务端使用RSA私钥解密的流程,所以需要和服务端沟通商量好。
具体代码实现:
private final String RSA_PUB_KEY = "服务端给的公钥";
private final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
/**
* 公钥加密
*
* @param data 要加密的数据
* @param key 公钥
* @param transformation 算法/模式/填充
* @return 加密后的数据
*/
public byte[] encryptByPublicKey(byte[] data, String key, String transformation)
throws GeneralSecurityException {
byte[] keyBytes = Base64.decode(key.getBytes(UTF_8), Base64.NO_WRAP);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
来源:https://juejin.cn/post/7146386827522342948


猜你喜欢
- 一、基本概念Web Service也叫XML Web Service WebService是一种可以接收从Internet或者Intrane
- java返回json请求中文变成问号原来在个人项目时,用layui的数据表格获取数据时,不会出现中文变问号问题后来换了个项目,发现返回的js
- 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void通
- 在开发中,我们通常需要将从数据库中查询的集合数据转换成类似文件系统一样的树形集合,比如:省市单位,部门机构,书籍分类等TreeNode对象@
- 如何实现使用TextView的DrawableLeft使图片和文字居中显示呢???代码如下: 1.首先自定义一个类,继承TextViewpa
- 一 引入考虑实现一种三轴机器人控件。三轴机器人用来将某种工件从一个位置运送到另一个位置。其X轴为手臂轴,可以正向和反向运动,它处于末端,直接
- 一、背景假如:黑客黑进了数据库,或者离职人员导出了数据,那么就可能导致这些敏感数据的泄漏。因此我们就需要找到一种方法来解决这个问题。二、解决
- 详解Android Studio正式签名进行调试的实现步骤在Android Studio中,可以使用Gradle进行打包时自动签名。其实An
- 在Web的应用方面有js的插件实现自动完成(或叫智能提示)功能,但在WinForm窗体应用方面就没那么好了。TextBox控件本身是提供了一
- Android Fragment的回退栈点开之后按一次回退键只返回一次MainActivity 类public class Ma
- 前言众所周知在spring boot内,设置session过期时间只需在application.properties内添加server.se
- 本文实例为大家分享了用JavaMail发送HTML模板邮件的具体代码,供大家参考,具体内容如下依赖<dependency>&nb
- 前言java 10 引进一种新的闪闪发光的特性叫做局部变量类型推断。听起来很高大上吧?它是什么呢? 下面的两个情景是我们作为 Java 开发
- 堆排序是一种树形选择排序方法,它的特点是:在排序的过程中,将array[0,...,n-1]看成是一颗完全二叉树的顺序存储结构,利用完全二叉
- Okhttp 处理了很多网络疑难杂症,比如从很多常用的连接问题中自动恢复。如果你服务器配置了多个IP地址,当一个IP地址连接失败后Okhtt
- 前言不知道你是否参加过拼多多上邀请微信好友砍价功能,这个功能实现首先需要考虑的就是获取微信用户的信息。获取用户信息就是获取公众号下微信用户的
- 本文实例讲述了Android TabLayout(选项卡布局)简单用法。分享给大家供大家参考,具体如下:我们在应用viewpager的时候,
- 本文实例讲述了Java调用Shell命令的方法。分享给大家供大家参考。具体如下:近日项目中有这样一个需求:系统中的外币资金调度完成以后,要将
- 一、什么是Spring Cloud?SpringCloud 对常见的分布式系统模式提供了简单易用的编程模型,帮助开发者构建弹性、可靠、协调的
- springboot:接收date类型的参数今天有个postmapping方法,地址都正确,就是死活进不去,真是奇怪了。终于从日志中得出些端