安卓逆向(签名校验)

公钥加密 VS. 对称加密

(1) 如果使用公钥加密
  • 所有客户端使用同一个公钥加密数据
  • 加密后的结果可以不同,因为加密过程通常会引入随机性(填充/Nonce)
  • 但服务器都能用同一个私钥解密,因为公钥-私钥是配对的

➡️ 即使不同客户端加密同样的数据,加密后的结果通常不同! 但服务器解密后,原始数据是一样的。

(2) 如果使用对称加密
  • 每个客户端有自己的对称密钥,用来加密数据
  • 加密后的结果不同,因为密钥不同

➡️ 不同客户端加密相同数据,结果一定不同! 因为每个客户端的密钥都不同。

为什么加密后的结果不同?

即使用同一个密钥,每次加密的结果也可能不同,原因是:

  1. 填充(Padding):某些加密算法会在数据后面填充不同的字节,使每次加密结果不同。
  2. 初始化向量(IV/Nonce):许多加密算法(如 AES CBC、GCM 模式)会使用一个随机数(IV),保证每次加密都不一样。
  3. 随机填充:如 RSA/OAEP 加密会自动加一些随机填充数据,使相同内容加密后结果不同。

📌 1 字节(Byte)是什么?

1 字节(Byte)= 8 位(bit)

  • 位(bit):计算机的最小存储单位,可以是 01
  • 字节(Byte):8 个 bit 组成一个字节。
数据类型占用字节数示例
字符 (ASCII)1 字节'A' (0x41),'a' (0x61)
Unicode 字符(UTF-8)1~4 字节'中' (E4 B8 AD,3字节)
整数 (int)4 字节1234567890
浮点数 (float)4 字节3.14

📌 1 字节如何存储数据?

示例:存储字母 'A',它的 ASCII 码是 65,用二进制表示是 01000001(1 字节)。

📌 存储 "ABC"

'A' → 01000001  (0x41)
'B' → 01000010  (0x42)
'C' → 01000011  (0x43)

总共 3 字节

📌 存储 "你好"(UTF-8 编码)

'你' → 11100100 10111000 10101001  (0xE4 B8 A9, 3字节)
'好' → 11100101 10111010 10101101  (0xE5 BA AD, 3字节)

总共 6 字节(UTF-8 里中文一般占 3 字节)。

1. 填充(Padding)

适用场景: 对称加密(如 AES)、非对称加密(如 RSA)
作用: 让数据长度符合加密算法的要求,增加安全性

🔹 为什么要填充?

许多加密算法要求数据长度是固定的,比如 AES 需要 16 字节的块(Block)。如果你的数据长度不是 16 的倍数,就必须填充一些额外的字节来让它对齐。

🔹 常见的填充方式

  1. PKCS#7 填充(用于 AES CBC/ECB)
    • 如果数据长度不是 16 字节的倍数,就在结尾补上若干个相同数值的字节,这个数值等于填充的字节数。
    • 示例(AES 需要 16 字节):
明文: "HELLO" (5 字节)
需要补 11 字节:\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B

这样填充后,变成:

"HELLO\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B"
  1. 解密时,程序知道最后的 \x0B 是填充数据,会自动去掉
  2. OAEP 填充(用于 RSA)
    • RSA 不能直接加密太长的数据,所以会用 填充+随机数 让数据变长,并增加安全性。
    • OAEP(Optimal Asymmetric Encryption Padding)会用随机数填充数据,使得每次加密结果都不同,即使输入相同!

✅ 填充带来的变化: 即使你的原始数据相同填充可能会引入不同的字节,导致加密结果不同。

🔹 1. PKCS#7 填充(最常用)

🟢 示例 1:短于 16 字节(补足)

假设你要加密 "HELLO",它只有 5 字节(少了 11 字节),需要填充:

明文: "HELLO"  (5 字节)
填充: 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B (11 个字节)
最终数据(16 字节):
"H E L L O  0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B"
  • 0B = 11(十六进制),表示填充了 11 个字节。

解密时,程序会检查最后一个字节的值 0B,然后去掉 11 个 0B,恢复 "HELLO"

🟢 示例 2:长于 16 字节(填充至 32)

假设 "HELLO WORLD! 123"17 字节,超过 16,但不是 32:

明文: "HELLO WORLD! 123" (17 字节)
填充: 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F (15 字节)
最终数据(32 字节):
"H E L L O  W O R L D !  1 2 3  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F"
  • 0F = 15,表示填充了 15 个字节,补足到 32 字节

解密时,检测到最后一个字节是 0F,就知道要去掉 15 个 0F,恢复 "HELLO WORLD! 123"

现代安全系统一般不会只依赖密钥,通常会有以下几种安全措施防止这种攻击:

🔒 1. 绑定设备
  • 密钥通常存储在 AndroidKeyStore,只能由该设备访问。
  • 你即使知道密钥的内容,也没法直接复制到你的设备上使用,因为它可能是硬件绑定的
🔒 2. 使用动态 Token
  • 服务器可能会给客户端发送一个一次性 Token(比如 OAuth、JWT)。
  • 即使你伪造别人的密钥加密数据,你可能无法获取 Token,服务器会拒绝你的请求。
🔒 3. 密钥派生 & 指纹认证
  • 某些系统会把密钥和设备指纹(如 IMEI、设备ID)绑定。
  • 服务器可能会检查请求中的设备信息,如果不匹配,服务器会拒绝。

签名校验就是用来检查一个请求或数据包是否被篡改,是否在传输过程中被更改过。

工作原理:

  1. 创建签名:

    • 客户端或发送方通过某种加密算法(例如 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. 防止篡改: 如果数据在传输过程中被修改,签名就会不匹配,从而发现数据被篡改。
  2. 身份验证: 如果签名匹配,服务器可以确信数据是由客户端发送的,并且数据没有被篡改。
  3. 确保数据完整性: 签名能保证接收到的数据和发送的数据是一模一样的。

如果攻击者获得了客户端的密钥,并知道签名所用的加密算法和参数,那么他们就有可能伪造有效的请求。签名的安全性依赖于密钥的保密性,因此保护好密钥是防止伪造请求的关键。

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 类的作用:

  1. 加密:使用对称加密算法(例如 AES、DES 等)将明文数据转化为加密后的密文。
  2. 解密:使用相同的算法和密钥将加密后的密文还原为明文数据。

GCM(Galois/Counter Mode)是一种加密模式,它结合了加密和认证功能,用于确保数据的机密性完整性。GCM 是基于计数器模式(CTR)的,并使用一个额外的认证标签来验证数据是否在传输过程中被篡改。

主要特点:

  1. 加密性(Confidentiality):像其他加密模式一样,GCM 用于加密数据,确保只有授权的接收者能够读取数据内容。
  2. 完整性(Integrity):GCM 还提供认证功能,确保加密的数据在传输过程中没有被篡改。认证标签用于验证数据的完整性。

工作原理:

GCM 模式结合了加密和认证的功能,具体步骤如下:

  1. 加密:使用计数器模式(CTR)对数据进行加密。CTR 模式每次使用一个不同的计数器值(IV + nonce)来生成密钥流,将明文与密钥流进行异或操作,得到密文。
  2. 认证:同时计算认证标签(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 算法生成签名,确保请求的完整性和唯一性。

总结:

密钥保护的关键是存储安全、管理安全和传输安全:

  1. 加密存储密钥:使用 Android Keystore、硬件安全模块(HSM)等保护密钥。
  2. 密钥管理:定期轮换密钥,并确保每个密钥的生命周期受到有效管理。
  3. 加密传输:使用 HTTPS 协议确保密钥和加密数据在网络传输中不被泄露。
  4. 防止重放攻击:结合时间戳和随机数(nonce)等技术,防止攻击者伪造请求。

密钥保护

使用 HTTPS

使用短期有效的令牌(Token)

签名结合时间戳

Nonce(随机数)机制

双向认证(Mutual Authentication)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奶龙牛牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值