安卓逆向(签名校验)
签名校验就是用来检查一个请求或数据包是否被篡改,是否在传输过程中被更改过。
工作原理:
-
创建签名:
- 客户端或发送方通过某种加密算法(例如 HMAC)计算消息的“签名”。
- 计算签名时,客户端会使用私有的密钥和消息本身的内容一起计算,生成一段加密字符串(签名)。
这个签名是基于消息内容和密钥生成的,意味着如果消息内容发生了任何变化,签名也会变化。
String message = "City=Shanghai&Temperature=30";
String secretKey = "mySecretKey";
String signature = HMAC.calculateHMAC(message, secretKey); // 计算签名
发送签名:
- 客户端将生成的签名和数据一起发送给服务器。
- 例如,客户端可能会发送一个包含数据和签名的请求:
Data: City=Shanghai&Temperature=30
Signature: d13b2e4f5c29a8de75a3b1b68eaf7e57c7b4f9f95cc27b228c7d9fa38d0d4978
校验签名:
- 服务器收到请求后,会使用与客户端相同的算法和密钥(如果是对称加密的话)重新计算消息的签名。
- 然后,服务器将重新计算的签名与客户端发送的签名进行对比。
如果两者相同,说明消息在传输过程中没有被篡改,且是客户端发送的合法请求;如果不同,说明消息的内容可能被篡改过。
String calculatedSignature = HMAC.calculateHMAC("City=Shanghai&Temperature=30", "mySecretKey");
if (!calculatedSignature.equals(receivedSignature)) {
throw new SecurityException("Signature validation failed! Data might be tampered.");
}
签名的作用:
- 防止篡改: 如果数据在传输过程中被修改,签名就会不匹配,从而发现数据被篡改。
- 身份验证: 如果签名匹配,服务器可以确信数据是由客户端发送的,并且数据没有被篡改。
- 确保数据完整性: 签名能保证接收到的数据和发送的数据是一模一样的。
如果攻击者获得了客户端的密钥,并知道签名所用的加密算法和参数,那么他们就有可能伪造有效的请求。签名的安全性依赖于密钥的保密性,因此保护好密钥是防止伪造请求的关键。
1. 加密存储密钥
使用 Android Keystore 存储密钥
Android 提供了 Keystore
系统,它允许你存储加密密钥并对其进行加密操作,而不直接暴露密钥本身。使用 Keystore 可以确保密钥不会被泄露,因为密钥是存储在安全硬件中。
import android.security.keystore.KeyGenParameterSpec; // 导入密钥生成参数规范类,用于指定密钥的属性和使用方式
import android.security.keystore.KeyProperties; // 导入密钥属性类,用于定义加密算法和密钥用途
import android.util.Base64; // 导入 Base64 编解码工具类
import javax.crypto.Cipher; // 导入用于加解密操作的类
import javax.crypto.KeyGenerator; // 导入密钥生成器类,用于生成对称加密密钥
import javax.crypto.SecretKey; // 导入对称密钥类,代表加密和解密所用的密钥
import java.security.KeyStore; // 导入密钥库类,用于存储密钥
import java.security.NoSuchAlgorithmException; // 导入异常类,用于处理没有对应加密算法的错误
import java.security.cert.CertificateException; // 导入证书异常类,用于处理证书相关错误
public class KeyStoreExample {
private static final String KEY_ALIAS = "MyAppKeyAlias"; // 定义密钥的别名,用于在 KeyStore 中查找该密钥
private KeyStore keyStore; // 声明 KeyStore 实例变量,用于访问 Android KeyStore
// 构造方法,初始化 KeyStore 实例
public KeyStoreExample() throws Exception {
keyStore = KeyStore.getInstance("AndroidKeyStore"); // 获取 AndroidKeyStore 实例
keyStore.load(null); // 加载 KeyStore(null 表示加载默认配置)
}
// 创建密钥并保存到 Keystore 中
public void createKey() throws Exception {
// 创建 KeyGenerator 实例,用于生成密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
// 初始化 KeyGenerator,并指定密钥用途和加密方式
keyGenerator.init(
new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) // 设置密钥别名和用途(加密、解密)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM) // 设置块模式为 GCM(Galois/Counter Mode),用于加密
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) // 设置加密填充方式为无填充
.build() // 创建密钥生成参数配置对象
);
keyGenerator.generateKey(); // 根据配置生成密钥并存储在 KeyStore 中
}
// 使用 Keystore 中的密钥进行加密操作
public String encryptData(String inputData) throws Exception {
// 从 KeyStore 中获取存储的密钥
SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
// 创建 Cipher 实例,用于执行加密操作
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // 使用 AES 加密算法,块模式为 GCM,且无填充
cipher.init(Cipher.ENCRYPT_MODE, key); // 初始化 Cipher 为加密模式,并使用 Keystore 中的密钥
// 获取初始化向量(IV),它是 GCM 模式中必需的
byte[] iv = cipher.getIV();
// 执行加密操作,输入数据被加密为字节数组
byte[] encryption = cipher.doFinal(inputData.getBytes()); // 将输入数据转换为字节数组并进行加密
// 将加密后的数据和 IV 合并,并用 Base64 编码方便传输
return Base64.encodeToString(encryption, Base64.DEFAULT) + ":" + Base64.encodeToString(iv, Base64.DEFAULT);
}
// 使用 Keystore 中的密钥进行解密操作
public String decryptData(String encryptedData) throws Exception {
// 将加密后的数据和 IV 从传输字符串中分离
String[] parts = encryptedData.split(":"); // 分割加密数据和 IV
byte[] encryption = Base64.decode(parts[0], Base64.DEFAULT); // 对加密数据进行 Base64 解码
byte[] iv = Base64.decode(parts[1], Base64.DEFAULT); // 对 IV 进行 Base64 解码
// 从 KeyStore 中获取存储的密钥
SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
// 创建 Cipher 实例,用于执行解密操作
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // 使用 AES 加密算法,块模式为 GCM,且无填充
// 初始化 Cipher 为解密模式,并使用获取的 IV
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv)); // 使用 IV 创建 GCMParameterSpec 以便解密
// 执行解密操作,恢复原始数据
byte[] original = cipher.doFinal(encryption); // 解密数据
// 将解密后的字节数组转换为字符串并返回
return new String(original); // 将字节数组转换为字符串并返回
}
}
代码解释:
KeyStore
是用于存储和管理密钥的 Android 系统组件。KeyGenParameterSpec.Builder
用来创建密钥生成的配置,确保密钥只能用于加密和解密操作。Cipher
用来执行加密和解密。加密后数据和 IV 会一起存储,解密时需要使用同样的 IV。
Cipher
是 Java 和 Android 中用于加密和解密数据的类。它是 javax.crypto.Cipher
类的一部分,提供了一种方法来执行加密和解密操作,支持多种加密算法和模式。
Cipher
类的作用:
- 加密:使用对称加密算法(例如 AES、DES 等)将明文数据转化为加密后的密文。
- 解密:使用相同的算法和密钥将加密后的密文还原为明文数据。
GCM(Galois/Counter Mode)是一种加密模式,它结合了加密和认证功能,用于确保数据的机密性和完整性。GCM 是基于计数器模式(CTR)的,并使用一个额外的认证标签来验证数据是否在传输过程中被篡改。
主要特点:
- 加密性(Confidentiality):像其他加密模式一样,GCM 用于加密数据,确保只有授权的接收者能够读取数据内容。
- 完整性(Integrity):GCM 还提供认证功能,确保加密的数据在传输过程中没有被篡改。认证标签用于验证数据的完整性。
工作原理:
GCM 模式结合了加密和认证的功能,具体步骤如下:
- 加密:使用计数器模式(CTR)对数据进行加密。CTR 模式每次使用一个不同的计数器值(IV + nonce)来生成密钥流,将明文与密钥流进行异或操作,得到密文。
- 认证:同时计算认证标签(Authentication Tag),这个标签是通过对加密数据和一些附加数据(如头信息等)进行特定的数学操作(Galois 域上的乘法)来生成的,确保数据在传输过程中没有被篡改。
2. 密钥管理
密钥的定期更换
定期更换密钥是防止密钥长期泄露的有效措施。密钥的更换可以通过引入新的密钥别名来完成。
示例代码:
public void rotateKey() throws Exception {
// 删除旧密钥(如果有的话)
if (keyStore.containsAlias(KEY_ALIAS)) {
keyStore.deleteEntry(KEY_ALIAS); // 删除旧密钥
}
// 创建新密钥
createKey();
}
通过 rotateKey()
方法,我们可以删除旧的密钥,并创建一个新的密钥。定期更换密钥可以降低密钥泄露后的风险。
3. 密钥保护的传输
使用 HTTPS 保护传输
在客户端和服务器之间传输密钥或密钥加密的数据时,使用 HTTPS 来加密整个通信通道,以防止密钥和其他敏感数据在传输过程中被中间人窃取。
// 使用 OkHttp 发起 HTTPS 请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://secure.example.com/endpoint")
.addHeader("Authorization", "Bearer your-token")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
// 处理响应
}
@Override
public void onFailure(Call call, IOException e) {
// 处理失败
}
});
在客户端,OkHttp
默认会使用 HTTPS 协议来加密数据传输,确保中间人无法篡改或窃取传输的数据。
4. 结合时间戳和随机数
为进一步增加安全性,可以在每个请求中加入时间戳和随机数(nonce)来防止重放攻击。每个请求都必须包含唯一的时间戳和随机数,以确保每个请求都是唯一的。
public String generateRequestSignature(String data, long timestamp, String nonce) {
String input = data + timestamp + nonce;
return calculateSignature(input);
}
private String calculateSignature(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.encodeToString(hash, Base64.DEFAULT);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
代码解释:
- 使用时间戳和随机数作为加密数据的一部分。
- 使用
SHA-256
算法生成签名,确保请求的完整性和唯一性。
总结:
密钥保护的关键是存储安全、管理安全和传输安全:
- 加密存储密钥:使用 Android Keystore、硬件安全模块(HSM)等保护密钥。
- 密钥管理:定期轮换密钥,并确保每个密钥的生命周期受到有效管理。
- 加密传输:使用 HTTPS 协议确保密钥和加密数据在网络传输中不被泄露。
- 防止重放攻击:结合时间戳和随机数(nonce)等技术,防止攻击者伪造请求。
密钥保护
使用 HTTPS
使用短期有效的令牌(Token)
签名结合时间戳
Nonce(随机数)机制
双向认证(Mutual Authentication)