1-解决问题
在网络通信中,其实本质就是保证两个问题:
- 数据不能被别人看懂:用加密
- 数据确实是你发的:用签名
一般内部系统用token或者网关认证,外部系统的对接,会使用签名来校验来源的可靠性和真实性
2-对称加密
- 特点:一把钥匙(key)加密 + 解密
bash
明文 --(key加密)--> 密文
密文 --(key解密)--> 明文
-
优点:
- 速度快(适合大数据)
- 实现简单
-
缺点:
加解密都是一个秘钥,系统间约定,但是秘钥的传输,可能泄露 -
常见算法:
AES(最常用,基本行业标准)- DES(已淘汰)
- 3DES(过渡方案)
-
基础代码:
java
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal("hello".getBytes());
3-非对称加密
- 特点:
bash
公钥(public key)------可以公开
私钥(private key)------必须保密
- 常见算法:
RSA(最常用)- SM2(国产)
- ECC(更先进,性能更好)
3-2.签名机制
签名流程:
发送方:
原文 → hash → 用私钥加密 → 签名
接收方:
签名 → 公钥解密 → 得到hash
原文 → hash
对比两个hash
hash一致,说明数据没被篡改
常见算法
- MD5(不安全)
- SHA256(常用)
- RSA + SHA256(标准组合)
3-3.流程
请求:A → B
A:
用 B 的公钥加密数据
(可选)用 A 的私钥签名
→ 发送
B:
用自己的私钥解密
用 A 的公钥验签(如果有签名)
响应:B → A
B:
用 A 的公钥加密(可选)
用自己的私钥签名
→ 返回
A:
用自己的私钥解密(如果加密)
用 B 的公钥验签
口诀:
- 加密:给谁 → 用谁公钥
- 解密:谁收 → 谁私钥
- 签名:谁发 → 谁私钥
- 验签:验谁 → 用谁公钥
bash
# 加密
我 → 用对方公钥加密 → 对方私钥解密
# 签名
我 → 用自己私钥签名 → 对方用我公钥验证
3-4.基础实现
RSA 直接加密数据 + RSA 做签名
java
import javax.crypto.Cipher;
import java.security.*;
import java.util.Base64;
public class RSADemo {
public static void main(String[] args) throws Exception {
// ====== 1. 模拟 A 和 B 各自生成密钥对 ======
KeyPair A = generateKeyPair();
KeyPair B = generateKeyPair();
// A的公私钥
PublicKey A_pub = A.getPublic();
PrivateKey A_pri = A.getPrivate();
// B的公私钥
PublicKey B_pub = B.getPublic();
PrivateKey B_pri = B.getPrivate();
// ====== 2. A -> B:发送数据(加密 + 签名) ======
String original = "hello,我是A";
// 用 B 的公钥加密
String encrypted = encrypt(original, B_pub);
// 用 A 的私钥签名
String signature = sign(original, A_pri);
System.out.println("A发送密文: " + encrypted);
System.out.println("A发送签名: " + signature);
// ====== 3. B 接收数据 ======
// 用自己的私钥解密
String decrypted = decrypt(encrypted, B_pri);
// 用 A 的公钥验签
boolean verify = verify(decrypted, signature, A_pub);
System.out.println("B解密后: " + decrypted);
System.out.println("B验签结果: " + verify);
}
// ====== 生成密钥对 ======
public static KeyPair generateKeyPair() throws Exception {
// 1. KeyPairGenerator秘钥生成器,使用RSA算法
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化生成器,设置密钥长度为 2048 位
generator.initialize(2048);
// 3. 生成密钥对(包含公钥和私钥)
return generator.generateKeyPair();
}
// ====== 加密(公钥) ======
public static String encrypt(String data, PublicKey publicKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为加密模式,并传入公钥
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 3. 用公钥加密数据(先转成字节数组)
byte[] bytes = cipher.doFinal(data.getBytes());
// 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
return Base64.getEncoder().encodeToString(bytes);
}
// ====== 解密(私钥) ======
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为解密模式,并传入私钥
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 3. 先把 Base64 字符串解码成字节数组
byte[] bytes = Base64.getDecoder().decode(data);
// 4. 用私钥解密字节数组,得到原始数据
return new String(cipher.doFinal(bytes));
}
// ====== 签名(私钥) ======
// SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
public static String sign(String data, PrivateKey privateKey) throws Exception {
// 1. 创建 Signature 对象,指定算法为 SHA256withRSA
Signature signature = Signature.getInstance("SHA256withRSA");
// 2. 初始化为"签名模式",使用私钥
signature.initSign(privateKey);
// 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
signature.update(data.getBytes());
// 4. 生成签名字节数组
byte[] signBytes = signature.sign();
// 5. 转成 Base64 字符串,方便传输
return Base64.getEncoder().encodeToString(signBytes);
}
// ====== 验签(公钥) ======
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
// 1. 创建 Signature 对象,指定算法为 SHA256withRSA
Signature signature = Signature.getInstance("SHA256withRSA");
// 2. 初始化为"验证模式",使用公钥
signature.initVerify(publicKey);
// 3. 把原始数据输入进去
signature.update(data.getBytes());
// 4. 用公钥验证签名是否匹配
return signature.verify(Base64.getDecoder().decode(sign));
}
}
-
现在是RSA 直接加密数据 + RSA 做签名
-
这里加解密部分使用了RSA,但是因为RSA的长度问题以及性能问题,一般加解密这里会切换为对称加密,也就是
混合加密
4-混合加密
- 原来是RSA加密数据
- 现在是AES加密数据,然后RSA加密AES key(这个加密过的 AES Key也传递就过去,规避性能问题),最后解密出AES key,最后使用AES解密原文
- 签名部分最好也用加密后的密文签
java
package com.personal.downloadbooks.demos;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
public class RSADemo {
public static void main(String[] args) throws Exception {
// ====== 1. 模拟 A 和 B 各自生成密钥对 ======
KeyPair A = generateKeyPair();
KeyPair B = generateKeyPair();
// A的公私钥
PublicKey A_pub = A.getPublic();
PrivateKey A_pri = A.getPrivate();
// B的公私钥
PublicKey B_pub = B.getPublic();
PrivateKey B_pri = B.getPrivate();
// ====== 2. A -> B:发送数据(加密 + 签名) ======
String original = "hello,我是A";
// 生成 AES key
SecretKey aesKey = generateAESKey();
// 用 AES 加密数据
String encryptedData = aesEncrypt(original, aesKey);
// 用 B公钥 加密 AES key
String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
// 签名(建议签名密文)
String signature = sign(encryptedData, A_pri);
System.out.println("加密数据: " + encryptedData);
System.out.println("加密AES key: " + encryptedKey);
System.out.println("签名: " + signature);
// ====== 3. B 接收数据 ======
// 用 B私钥 解密 AES key
String aesKeyStr = decrypt(encryptedKey, B_pri);
byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
// 用 AES 解密数据
String decrypted = aesDecrypt(encryptedData, originAesKey);
// 验签
boolean verify = verify(encryptedData, signature, A_pub);
System.out.println("解密后: " + decrypted);
System.out.println("验签结果: " + verify);
}
// ====== 生成密钥对 ======
public static KeyPair generateKeyPair() throws Exception {
// 1. KeyPairGenerator秘钥生成器,使用RSA算法
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化生成器,设置密钥长度为 2048 位
generator.initialize(2048);
// 3. 生成密钥对(包含公钥和私钥)
return generator.generateKeyPair();
}
// ====== 加密(公钥) ======
public static String encrypt(String data, PublicKey publicKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为加密模式,并传入公钥
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 3. 用公钥加密数据(先转成字节数组)
byte[] bytes = cipher.doFinal(data.getBytes());
// 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
return Base64.getEncoder().encodeToString(bytes);
}
// ====== 解密(私钥) ======
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为解密模式,并传入私钥
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 3. 先把 Base64 字符串解码成字节数组
byte[] bytes = Base64.getDecoder().decode(data);
// 4. 用私钥解密字节数组,得到原始数据
return new String(cipher.doFinal(bytes));
}
// ====== 签名(私钥) ======
// SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
public static String sign(String data, PrivateKey privateKey) throws Exception {
// 1. 创建 Signature 对象,指定算法为 SHA256withRSA
Signature signature = Signature.getInstance("SHA256withRSA");
// 2. 初始化为"签名模式",使用私钥
signature.initSign(privateKey);
// 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
signature.update(data.getBytes());
// 4. 生成签名字节数组
byte[] signBytes = signature.sign();
// 5. 转成 Base64 字符串,方便传输
return Base64.getEncoder().encodeToString(signBytes);
}
// ====== 验签(公钥) ======
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
// 1. 创建 Signature 对象,指定算法为 SHA256withRSA
Signature signature = Signature.getInstance("SHA256withRSA");
// 2. 初始化为"验证模式",使用公钥
signature.initVerify(publicKey);
// 3. 把原始数据输入进去
signature.update(data.getBytes());
// 4. 用公钥验证签名是否匹配
return signature.verify(Base64.getDecoder().decode(sign));
}
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128位足够
return keyGen.generateKey();
}
public static String aesEncrypt(String data, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
}
public static String aesDecrypt(String data, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decoded = Base64.getDecoder().decode(data);
return new String(cipher.doFinal(decoded));
}
}
Cipher.getInstance("AES"),默认是ECB模式,不安全,后续需要改为AES/CBC/PKCS5Padding或者AES/GCM/NoPadding
PS:EBC会被看出来,通过比较密文块,推断出明文的重复结构。相当于默认的AES,假如明文AAAA加密后变成BBBB,然后后续再加密明文AAAA,还会是BBBB,后续可以被推断出来
4-1.CBC混合加密
4-1-1.Cipher
常用写法:Cipher.getInstance("AES/CBC/PKCS5Padding");
斜杠区别的是:
算法 / 工作模式 / 填充方式
- AES:用什么算法
- CBC:怎么加密数据(模式)
- PKCS5Padding:数据不够怎么补齐
4-1-2.CBC
Cipher Block Chaining的缩写,决定了 AES 如何处理数据块
- AES 是分块加密的(每次处理 16 字节)
- 在 ECB 模式 下,每个块独立加密,相同的明文块 → 相同的密文块,不安全
- 在 CBC 模式 下,每个块的加密结果会依赖前一个块的密文
- 第一个块会和一个随机生成的 IV(初始化向量) 做异或运算
- 后续块会和前一个密文块做异或,再加密
- 这样即使明文重复,密文也会不同,安全性大大提高
CBC 的关键点:需要一个随机 IV,每次加密都不同。IV 不需要保密,但必须随机
4-1-3.CBC大致理解
一个 byte = 8位的计算,示例:
bash
P1[0] = 0x41 → 01000001 ('A')
IV[0] = 0x12 → 00010010
# 做 XOR:
01000001
⊕00010010
---------
01010011 → 0x53 ('S')
换到CBC的情况下,16子节的数组,就是两个byte数组,逐个位置做 XOR:
bash
明文块 P1(16字节):
[ p0, p1, p2, ..., p15 ]
IV(16字节):
[ iv0, iv1, iv2, ..., iv15 ]
然后我们这个IV是随机生成的,就相当于多了一个扰乱因子,后续就是链式的情况了:
bash
P1 ⊕ IV → AES → C1
P1 ⊕ C1 → AES → C2
因为解密也需要IV,IV本身不需要保密,只要保证随机性和完整性即可传递的时候转成Base64直接传就行- 或者将IV拼在密文前面,例如:
[IV][密文],传递过去让接收方切第一个16子节作为IV
4-1-4.PKCS5Padding
这是 填充方式,因为 AES 必须处理固定大小的块(16 字节)
- 如果明文不是 16 的倍数,就要补齐
- PKCS5Padding 的规则是:补多少字节,就用多少值来填充
- 比如明文差 3 字节,就补
0x03 0x03 0x03 - 差 5 字节,就补
0x05 0x05 0x05 0x05 0x05
- 比如明文差 3 字节,就补
- 解密时,AES 会自动去掉这些填充
4-1-5.基本代码
java
package com;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
public class RSADemo {
public static void main(String[] args) throws Exception {
// ====== 1. 模拟 A 和 B 各自生成密钥对 ======
KeyPair A = generateKeyPair();
KeyPair B = generateKeyPair();
// A的公私钥
PublicKey A_pub = A.getPublic();
PrivateKey A_pri = A.getPrivate();
// B的公私钥
PublicKey B_pub = B.getPublic();
PrivateKey B_pri = B.getPrivate();
// ====== 2. A -> B:发送数据(加密 + 签名) ======
String original = "hello,我是A";
// 生成 AES key
SecretKey aesKey = generateAESKey();
// 生成扰动因子IV
byte[] iv = generateIV();
// AES加密数据(IV 会拼接在密文前面)
String encryptedData = aesEncrypt(original, aesKey, iv);
// 用 B公钥 加密 AES key
String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
// 签名(建议签名密文)
String signature = sign(encryptedData, A_pri);
System.out.println("加密数据: " + encryptedData);
System.out.println("加密AES key: " + encryptedKey);
System.out.println("签名: " + signature);
// ====== 3. B 接收数据 ======
// 用 B私钥 解密 AES key
String aesKeyStr = decrypt(encryptedKey, B_pri);
byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
// 用 AES 解密数据
String decrypted = aesDecrypt(encryptedData, originAesKey);
// 验签
boolean verify = verify(encryptedData, signature, A_pub);
System.out.println("解密后: " + decrypted);
System.out.println("验签结果: " + verify);
}
// ====== 生成密钥对 ======
public static KeyPair generateKeyPair() throws Exception {
// 1. KeyPairGenerator秘钥生成器,使用RSA算法
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化生成器,设置密钥长度为 2048 位
generator.initialize(2048);
// 3. 生成密钥对(包含公钥和私钥)
return generator.generateKeyPair();
}
// ====== 加密(公钥) ======
public static String encrypt(String data, PublicKey publicKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为加密模式,并传入公钥
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 3. 用公钥加密数据(先转成字节数组)
byte[] bytes = cipher.doFinal(data.getBytes());
// 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
return Base64.getEncoder().encodeToString(bytes);
}
// ====== 解密(私钥) ======
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为解密模式,并传入私钥
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 3. 先把 Base64 字符串解码成字节数组
byte[] bytes = Base64.getDecoder().decode(data);
// 4. 用私钥解密字节数组,得到原始数据
return new String(cipher.doFinal(bytes));
}
// ====== 签名(私钥) ======
// SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
public static String sign(String data, PrivateKey privateKey) throws Exception {
// 1. 创建 Signature 对象,指定算法为 SHA256withRSA
Signature signature = Signature.getInstance("SHA256withRSA");
// 2. 初始化为"签名模式",使用私钥
signature.initSign(privateKey);
// 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
signature.update(data.getBytes());
// 4. 生成签名字节数组
byte[] signBytes = signature.sign();
// 5. 转成 Base64 字符串,方便传输
return Base64.getEncoder().encodeToString(signBytes);
}
// ====== 验签(公钥) ======
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
// 1. 创建 Signature 对象,指定算法为 SHA256withRSA
Signature signature = Signature.getInstance("SHA256withRSA");
// 2. 初始化为"验证模式",使用公钥
signature.initVerify(publicKey);
// 3. 把原始数据输入进去
signature.update(data.getBytes());
// 4. 用公钥验证签名是否匹配
return signature.verify(Base64.getDecoder().decode(sign));
}
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128位足够
return keyGen.generateKey();
}
// ====== AES 加密(CBC + PKCS5Padding) ======
public static String aesEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
// 拼接 IV + 密文,一起传输
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(result);
}
// ====== AES 解密(CBC + PKCS5Padding) ======
public static String aesDecrypt(String data, SecretKey key) throws Exception {
byte[] decoded = Base64.getDecoder().decode(data);
// 拆分出 IV 和密文
byte[] iv = new byte[16];
byte[] ciphertext = new byte[decoded.length - 16];
System.arraycopy(decoded, 0, iv, 0, 16);
System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted);
}
// ====== 生成随机 IV ======
public static byte[] generateIV() {
byte[] iv = new byte[16]; // AES block size = 16 bytes
new SecureRandom().nextBytes(iv);
return iv;
}
}
4-1-6.调整
主流来说签名不能只签密文部分,最好是更多更全面的信息全部拿进去签,防止有人篡改
比如主流结构:
json
{
"ciphertext": "Base64编码的密文",
"iv": "Base64编码的IV",
"encryptedKey": "Base64编码的RSA加密AES密钥",
"signature": "Base64编码的签名",
"sender": "A",
"timestamp": "2026-03-25T11:42:00Z"
}
两种签法:
- 签整个JSON字符串:通常是整个 JSON(除 signature 字段本身)
- 签字段拼接(Canonicalization):ciphertext、iv、encryptedKey、timestamp按固定顺序拼接成一个字符串
java
package com;
import lombok.Data;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
public class RSADemo {
@Data
public static class SecureRequest {
/**
* 应用ID(标识调用方)
*/
private String appId;
/**
* 时间戳(防重放)
*/
private String timestamp;
/**
* 随机串(防重放)
*/
private String nonce;
/**
* 业务数据(AES加密后的Base64)
*/
private String data;
/**
* AES密钥(用对方公钥RSA加密)
*/
private String encryptKey;
/**
* 签名(对指定字段签名)
*/
private String sign;
}
public static void main(String[] args) throws Exception {
// ====== 1. 模拟 A 和 B 各自生成密钥对 ======
KeyPair A = generateKeyPair();
KeyPair B = generateKeyPair();
// A的公私钥
PublicKey A_pub = A.getPublic();
PrivateKey A_pri = A.getPrivate();
// B的公私钥
PublicKey B_pub = B.getPublic();
PrivateKey B_pri = B.getPrivate();
// ====== 2. A -> B:发送数据(加密 + 签名) ======
String original = "hello,我是A";
// 生成 AES key
SecretKey aesKey = generateAESKey();
// 生成扰动因子IV
byte[] iv = generateIV();
// AES加密数据(IV 会拼接在密文前面)
String encryptedData = aesEncrypt(original, aesKey, iv);
// 用 B公钥 加密 AES key
String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
// 构建请求
SecureRequest request = new SecureRequest();
request.setAppId("appA");
request.setTimestamp(String.valueOf(System.currentTimeMillis()));
request.setNonce(String.valueOf(new SecureRandom().nextInt()));
request.setData(encryptedData);
request.setEncryptKey(encryptedKey);
// 签名
String signature = signRequest(request, A_pri);
request.setSign(signature);
System.out.println("加密数据: " + encryptedData);
System.out.println("加密AES key: " + encryptedKey);
System.out.println("签名: " + signature);
// ====== 3. B 接收数据 ======
// 用 B私钥 解密 AES key
String aesKeyStr = decrypt(encryptedKey, B_pri);
byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
// 用 AES 解密数据
String decrypted = aesDecrypt(encryptedData, originAesKey);
// 验签
boolean verify = verifyRequest(request, A_pub);
System.out.println("解密后: " + decrypted);
System.out.println("验签结果: " + verify);
}
// ====== 生成密钥对 ======
public static KeyPair generateKeyPair() throws Exception {
// 1. KeyPairGenerator秘钥生成器,使用RSA算法
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化生成器,设置密钥长度为 2048 位
generator.initialize(2048);
// 3. 生成密钥对(包含公钥和私钥)
return generator.generateKeyPair();
}
// ====== 加密(公钥) ======
public static String encrypt(String data, PublicKey publicKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为加密模式,并传入公钥
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 3. 用公钥加密数据(先转成字节数组)
byte[] bytes = cipher.doFinal(data.getBytes());
// 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
return Base64.getEncoder().encodeToString(bytes);
}
// ====== 解密(私钥) ======
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
// 1. 创建一个 Cipher(密码器),指定算法为 RSA
Cipher cipher = Cipher.getInstance("RSA");
// 2. 初始化为解密模式,并传入私钥
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 3. 先把 Base64 字符串解码成字节数组
byte[] bytes = Base64.getDecoder().decode(data);
// 4. 用私钥解密字节数组,得到原始数据
return new String(cipher.doFinal(bytes));
}
// ====== 签名(私钥) ======
// SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
// public static String sign(String data, PrivateKey privateKey) throws Exception {
// // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
// Signature signature = Signature.getInstance("SHA256withRSA");
// // 2. 初始化为"签名模式",使用私钥
// signature.initSign(privateKey);
// // 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
// signature.update(data.getBytes());
// // 4. 生成签名字节数组
// byte[] signBytes = signature.sign();
// // 5. 转成 Base64 字符串,方便传输
// return Base64.getEncoder().encodeToString(signBytes);
// }
// ====== 验签(公钥) ======
// public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
// // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
// Signature signature = Signature.getInstance("SHA256withRSA");
// // 2. 初始化为"验证模式",使用公钥
// signature.initVerify(publicKey);
// // 3. 把原始数据输入进去
// signature.update(data.getBytes());
// // 4. 用公钥验证签名是否匹配
// return signature.verify(Base64.getDecoder().decode(sign));
// }
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128位足够
return keyGen.generateKey();
}
// ====== AES 加密(CBC + PKCS5Padding) ======
public static String aesEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
// 拼接 IV + 密文,一起传输
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(result);
}
// ====== AES 解密(CBC + PKCS5Padding) ======
public static String aesDecrypt(String data, SecretKey key) throws Exception {
byte[] decoded = Base64.getDecoder().decode(data);
// 拆分出 IV 和密文
byte[] iv = new byte[16];
byte[] ciphertext = new byte[decoded.length - 16];
System.arraycopy(decoded, 0, iv, 0, 16);
System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted);
}
// ====== 生成随机 IV ======
public static byte[] generateIV() {
byte[] iv = new byte[16]; // AES block size = 16 bytes
new SecureRandom().nextBytes(iv);
return iv;
}
// ====== 构建待签名字符串 ======
public static String buildSignContent(SecureRequest request) {
Map<String, String> map = new TreeMap<>();
map.put("appId", request.getAppId());
map.put("timestamp", request.getTimestamp());
map.put("nonce", request.getNonce());
map.put("data", request.getData());
map.put("encryptKey", request.getEncryptKey());
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString();
}
// ====== 签名 ======
public static String signRequest(SecureRequest request, PrivateKey privateKey) throws Exception {
// 构建待签名字符串
String content = buildSignContent(request);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
// ====== 验签 ======
public static boolean verifyRequest(SecureRequest request, PublicKey publicKey) throws Exception {
// 构建待签名字符串
String content = buildSignContent(request);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes());
return signature.verify(Base64.getDecoder().decode(request.getSign()));
}
}
PS:
加解密,使用了Cipher.getInstance("RSA"),这个其实是默认RSA/ECB/PKCS1Padding,确实存在一定的安全漏洞,以后改为Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"),具体修改以前的encrypt、decrypt方法即可
java
// 加密
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(bytes);
}
// 解密
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = Base64.getDecoder().decode(data);
return new String(cipher.doFinal(bytes));
}
// 如果需要详细设置,则:
//OAEPParameterSpec oaepParams = new OAEPParameterSpec(
// "SHA-256",
// "MGF1",
// MGF1ParameterSpec.SHA256,
// PSource.PSpecified.DEFAULT
//);
//cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
PS:使用SHA-256时,最大明文约为 190 字节
4-1-7.最终版
java
package com.taiji;
import lombok.Data;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
public class RSADemo {
@Data
public static class SecureRequest {
/**
* 应用ID(标识调用方)
*/
private String appId;
/**
* 时间戳(防重放)
*/
private String timestamp;
/**
* 随机串(防重放)
*/
private String nonce;
/**
* 业务数据(AES加密后的Base64)
*/
private String data;
/**
* AES密钥(用对方公钥RSA加密)
*/
private String encryptKey;
/**
* 签名(对指定字段签名)
*/
private String sign;
}
public static void main(String[] args) throws Exception {
// ====== 1. 模拟 A 和 B 各自生成密钥对 ======
KeyPair A = generateKeyPair();
KeyPair B = generateKeyPair();
// A的公私钥
PublicKey A_pub = A.getPublic();
PrivateKey A_pri = A.getPrivate();
// B的公私钥
PublicKey B_pub = B.getPublic();
PrivateKey B_pri = B.getPrivate();
// ====== 2. A -> B:发送数据(加密 + 签名) ======
String original = "hello,我是A";
// 生成 AES key
SecretKey aesKey = generateAESKey();
// 生成扰动因子IV
byte[] iv = generateIV();
// AES加密数据(IV 会拼接在密文前面)
String encryptedData = aesEncrypt(original, aesKey, iv);
// 用 B公钥 加密 AES key
String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
// 构建请求
SecureRequest request = new SecureRequest();
request.setAppId("appA");
request.setTimestamp(String.valueOf(System.currentTimeMillis()));
request.setNonce(String.valueOf(new SecureRandom().nextInt()));
request.setData(encryptedData);
request.setEncryptKey(encryptedKey);
// 签名
String signature = signRequest(request, A_pri);
request.setSign(signature);
System.out.println("加密数据: " + encryptedData);
System.out.println("加密AES key: " + encryptedKey);
System.out.println("签名: " + signature);
// ====== 3. B 接收数据 ======
// 用 B私钥 解密 AES key
String aesKeyStr = decrypt(encryptedKey, B_pri);
byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
// 用 AES 解密数据
String decrypted = aesDecrypt(encryptedData, originAesKey);
// 验签
boolean verify = verifyRequest(request, A_pub);
System.out.println("解密后: " + decrypted);
System.out.println("验签结果: " + verify);
}
// ====== 生成密钥对 ======
public static KeyPair generateKeyPair() throws Exception {
// 1. KeyPairGenerator秘钥生成器,使用RSA算法
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化生成器,设置密钥长度为 2048 位
generator.initialize(2048);
// 3. 生成密钥对(包含公钥和私钥)
return generator.generateKeyPair();
}
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(bytes);
}
// ====== 解密(私钥) ======
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = Base64.getDecoder().decode(data);
return new String(cipher.doFinal(bytes));
}
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128位足够
return keyGen.generateKey();
}
// ====== AES 加密(CBC + PKCS5Padding) ======
public static String aesEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
// 拼接 IV + 密文,一起传输
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(result);
}
// ====== AES 解密(CBC + PKCS5Padding) ======
public static String aesDecrypt(String data, SecretKey key) throws Exception {
byte[] decoded = Base64.getDecoder().decode(data);
// 拆分出 IV 和密文
byte[] iv = new byte[16];
byte[] ciphertext = new byte[decoded.length - 16];
System.arraycopy(decoded, 0, iv, 0, 16);
System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted);
}
// ====== 生成随机 IV ======
public static byte[] generateIV() {
byte[] iv = new byte[16]; // AES block size = 16 bytes
new SecureRandom().nextBytes(iv);
return iv;
}
// ====== 构建待签名字符串 ======
public static String buildSignContent(SecureRequest request) {
Map<String, String> map = new TreeMap<>();
map.put("appId", request.getAppId());
map.put("timestamp", request.getTimestamp());
map.put("nonce", request.getNonce());
map.put("data", request.getData());
map.put("encryptKey", request.getEncryptKey());
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString();
}
// ====== 签名 ======
public static String signRequest(SecureRequest request, PrivateKey privateKey) throws Exception {
// 构建待签名字符串
String content = buildSignContent(request);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
// ====== 验签 ======
public static boolean verifyRequest(SecureRequest request, PublicKey publicKey) throws Exception {
// 构建待签名字符串
String content = buildSignContent(request);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes());
return signature.verify(Base64.getDecoder().decode(request.getSign()));
}
}
4-2.GCM混合加密
之前加解密用的AES/CBC/PKCS5Padding,其实已经不太推荐,安全性存在问题,CBC可能存在篡改问题,目前主流是AES/GCM/NoPadding
CBC与GCM的区别:
| 特性 | CBC (Cipher Block Chaining) | GCM (Galois/Counter Mode) |
|---|---|---|
| 安全性 | 只保证机密性,需要额外签名来保证完整性 | 同时保证机密性 + 完整性(内置认证标签) |
| IV 要求 | 必须随机且唯一,重复会导致安全问题 | 必须唯一(通常用计数器或随机数),重复也会导致严重问题 |
| 并行性能 | 不支持并行加解密(每块依赖前一块) | 支持并行加解密,速度更快 |
| 认证数据 | 需要额外签名机制(如 HMAC 或 RSA 签名) | 可以直接把元数据(如 appId、timestamp)作为 AAD,自动校验 |
| 主流性 | 过去常用,但现在逐渐被淘汰 | 已成为主流推荐,TLS 1.3、JWT、现代 API 都用 GCM |
代码实现:
java
import lombok.Data;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
public class RSADemo {
@Data
public static class SecureRequest {
/**
* 应用ID(标识调用方)
*/
private String appId;
/**
* 时间戳(防重放)
*/
private String timestamp;
/**
* 随机串(防重放)
*/
private String nonce;
/**
* 业务数据(AES加密后的Base64)
*/
private String data;
/**
* AES密钥(用对方公钥RSA加密)
*/
private String encryptKey;
/**
* 签名(对指定字段签名)
*/
private String sign;
}
public static void main(String[] args) throws Exception {
// ====== 1. 模拟 A 和 B 各自生成密钥对 ======
KeyPair A = generateKeyPair();
KeyPair B = generateKeyPair();
// A的公私钥
PublicKey A_pub = A.getPublic();
PrivateKey A_pri = A.getPrivate();
// B的公私钥
PublicKey B_pub = B.getPublic();
PrivateKey B_pri = B.getPrivate();
// ====== 2. A -> B:发送数据(加密 + 签名) ======
String original = "hello,我是A";
// 生成 AES key
SecretKey aesKey = generateAESKey();
// 生成GSM扰动因子IV
byte[] iv = generateGcmIV();
// AES加密数据(IV 会拼接在密文前面)
String encryptedData = aesGcmEncrypt(original, aesKey, iv);
// 用 B公钥 加密 AES key
String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
// 构建请求
SecureRequest request = new SecureRequest();
request.setAppId("appA");
request.setTimestamp(String.valueOf(System.currentTimeMillis()));
request.setNonce(String.valueOf(new SecureRandom().nextInt()));
request.setData(encryptedData);
request.setEncryptKey(encryptedKey);
// 签名
String signature = signRequest(request, A_pri);
request.setSign(signature);
System.out.println("加密数据: " + encryptedData);
System.out.println("加密AES key: " + encryptedKey);
System.out.println("签名: " + signature);
// ====== 3. B 接收数据 ======
// 用 B私钥 解密 AES key
String aesKeyStr = decrypt(encryptedKey, B_pri);
byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
// 用 AES 解密数据
String decrypted = aesGcmDecrypt(encryptedData, originAesKey);
// 验签
boolean verify = verifyRequest(request, A_pub);
System.out.println("解密后: " + decrypted);
System.out.println("验签结果: " + verify);
}
// ====== 生成密钥对 ======
public static KeyPair generateKeyPair() throws Exception {
// 1. KeyPairGenerator秘钥生成器,使用RSA算法
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化生成器,设置密钥长度为 2048 位
generator.initialize(2048);
// 3. 生成密钥对(包含公钥和私钥)
return generator.generateKeyPair();
}
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(bytes);
}
// ====== 解密(私钥) ======
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = Base64.getDecoder().decode(data);
return new String(cipher.doFinal(bytes));
}
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128位足够
return keyGen.generateKey();
}
// ====== 构建待签名字符串 ======
public static String buildSignContent(SecureRequest request) {
Map<String, String> map = new TreeMap<>();
map.put("appId", request.getAppId());
map.put("timestamp", request.getTimestamp());
map.put("nonce", request.getNonce());
map.put("data", request.getData());
map.put("encryptKey", request.getEncryptKey());
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString();
}
// ====== 签名 ======
public static String signRequest(SecureRequest request, PrivateKey privateKey) throws Exception {
// 构建待签名字符串
String content = buildSignContent(request);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
// ====== 验签 ======
public static boolean verifyRequest(SecureRequest request, PublicKey publicKey) throws Exception {
// 构建待签名字符串
String content = buildSignContent(request);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes());
return signature.verify(Base64.getDecoder().decode(request.getSign()));
}
public static String aesGcmEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
// 使用 AES 算法,模式是 GCM(Galois/Counter Mode),不需要额外的填充(NoPadding)
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// 这里的 128 表示认证标签(Authentication Tag)的长度是 128 位(16 字节)
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
// 执行加密
byte[] encrypted = cipher.doFinal(data.getBytes());
// 拼接格式为: [iv] + [密文]
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(result);
}
public static String aesGcmDecrypt(String data, SecretKey key) throws Exception {
// 把传入的 Base64 字符串还原成字节数组
byte[] decoded = Base64.getDecoder().decode(data);
// GCM IV 12字节
byte[] iv = new byte[12];
// 前 12 字节是 IV,后面剩下的就是密文
byte[] ciphertext = new byte[decoded.length - 12];
System.arraycopy(decoded, 0, iv, 0, 12);
System.arraycopy(decoded, 12, ciphertext, 0, ciphertext.length);
// 用同样的密钥和 IV 初始化解密器
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
// 执行解密
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted);
}
public static byte[] generateGcmIV() {
// GCM 模式推荐使用 12 字节的 IV,因为这是标准且性能最佳
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
return iv;
}
}
5-生产级最佳实践
在实际系统对接中,一套完整的安全方案通常包括以下几个方面:
5-1.数据加密(机密性)
使用 AES-GCM 进行数据加密(推荐)
原因:
- 同时提供机密性 + 完整性(内置认证标签)
- 支持并行计算,性能更好
注意:
- IV 必须唯一(推荐 12 字节随机数)
5-2.密钥交换
使用 RSA-OAEP(SHA-256) 加密 AES 密钥
原因:
- 相比 PKCS1Padding,更安全(抗选择密文攻击)
注意:
- RSA 只用于加密"短数据"(如 AES Key),不要直接加密业务数据
5-3.签名机制(身份认证 + 防篡改)
使用 SHA256withRSA 进行签名
签名内容建议包含:
- appId
- timestamp
- nonce
- ciphertext
- encryptKey
作用:
- 验证请求来源(是谁发的)
- 防止数据被篡改
- 防止接口参数被替换攻击
5-4.防重放攻击
必须包含:
- timestamp(时间戳)
- nonce(随机数)
服务端需要:
- 校验时间窗口(如 ±5分钟)
- nonce 做去重(可结合 Redis)
5-5.参数规范(签名一致性)
推荐使用 排序签名(Canonicalization)
- 按 key 字典序排序
- 拼接为:key=value&key=value
原因:
- 避免 JSON 顺序导致签名不一致
- 提高跨语言兼容性
5-6.传输安全(必须)
全部接口必须走 HTTPS
原因:
- 防止中间人攻击(MITM)
- TLS 已提供一层基础加密