1.解决问题
前后端在交互过程中,请求和响应的数据需要加密处理,并保证安全和性能。
方案名称:ECIES (Elliptic Curve Integrated Encryption Scheme,椭圆曲线集成加密方案)
2.核心思路
- 服务端准备
- 生成密钥对(EC-椭圆曲线),私钥servPriKey放在服务端。
- 公钥servPubKey安全地预置或下发给客户端(如通过初始化接口)。
- 客户端加密(每次请求)
- 生成临时密钥对(EC-椭圆曲线),公钥pubKey ,私钥priKey
- 计算共享密钥Key (ECDH-协商+sha256),用私钥priKey + 服务端公钥servPubKey
- 加密请求业务数据(AES):encryptData = AES.encrypt(data, Key)
- 发送加密后的业务数据encryptData 和公钥pubKey给服务端
- 服务端解密
- 计算共享密钥Key (ECDH-协商+sha256),用私钥servPriKey + 客户端公钥pubKey
- 解密请求数据,data = AES.decrypt(encryptData, Key)
- 服务端加密
- 通过3中计算的Key直接加密数据即可
- 客户端解密
- 通过2中计算的Key直接解密数据即可
3.代码示例
java
package com.visy.utils;
import cn.hutool.crypto.symmetric.AES;
import javax.crypto.KeyAgreement;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* ECIES (Elliptic Curve Integrated Encryption Scheme,椭圆曲线集成加密方案)
* 这个方案是椭圆曲线密码学中的密码学方案,用于实现前后端间通信的数据加密,是应用内的加密机制。
* 核心是密钥协商:使用ECDH算法生成密钥对,并计算出共享密钥。
*/
public class ECIESDemo {
//Base64工具类
static class BASE64 {
public static String encode(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
public static byte[] decode(String base64Str) {
return Base64.getDecoder().decode(base64Str);
}
}
//ECDH工具
static class ECDH {
public static StrKeyPair generateKeyPair(String curveName) throws Exception {
// 1. 指定椭圆曲线参数,例如 secp256r1 (NIST P-256)
ECGenParameterSpec ecSpec = new ECGenParameterSpec(curveName);
// 2. 生成ECC密钥对
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(ecSpec, new SecureRandom());
KeyPair keyPair = kpg.generateKeyPair();
return StrKeyPair.of(keyPair);
}
/**
* ECDH核心方法:计算共享秘密
* @param myPriKey 己方的私钥(Base64字符串)
* @param otherPubKey 对方的公钥(Base64字符串)
* @return Base64编码的共享秘密字符串
*/
public static String deriveSharedSecret(String myPriKey, String otherPubKey) throws Exception {
PrivateKey _myPriKey = privateKeyFromBase64(myPriKey);
PublicKey _otherPubKey = publicKeyFromBase64(otherPubKey);
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(_myPriKey);
ka.doPhase(_otherPubKey, true);
byte[] rawSecret = ka.generateSecret();
//用SHA-256哈希一次,得到32字节AES-256密钥
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
String shearedSecret = BASE64.encode(sha256.digest(rawSecret));
System.out.println("【共享密钥】计算-私钥:"+myPriKey);
System.out.println("【共享密钥】计算-公钥:"+otherPubKey);
System.out.println("【共享密钥】计算-结果:"+shearedSecret);
System.out.println("---------------------------------");
return shearedSecret;
}
private static PublicKey publicKeyFromBase64(String publicKeyBase64) throws Exception {
byte[] decodedBytes = BASE64.decode(publicKeyBase64);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedBytes);
return keyFactory.generatePublic(keySpec);
}
private static PrivateKey privateKeyFromBase64(String privateKeyBase64) throws Exception {
byte[] decodedBytes = BASE64.decode(privateKeyBase64);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedBytes);
return keyFactory.generatePrivate(keySpec);
}
}
//AES加解密工具
static class Aes {
public static String encrypt(String data, String key){
AES aes = new AES(BASE64.decode(key));
return aes.encryptBase64(data);
}
public static String decrypt(String data, String key){
AES aes = new AES(BASE64.decode(key));
return aes.decryptStr(BASE64.decode(data));
}
}
//模拟客户端(如浏览器)
static class Client {
//服务端公钥,固定值,后续不再更新
private final String servPubKey;
public Client(String servPubKey){
this.servPubKey = servPubKey;
System.out.println("【客户端】初始化完成,服务端公钥: " + servPubKey);
System.out.println("---------------------------------");
}
public void request(Server server, String message, BiConsumer<String,String> callback) throws Exception {
//生成密钥对
StrKeyPair keyPair = genKeyPair();
//计算共享密钥
String sharedSecret = getSharedSecret(keyPair.getPriKey());
//对称加密数据
String reqData = Aes.encrypt(message, sharedSecret);
//封装传输通道
Channel channel = new Channel(keyPair.getPubKey(), reqData, resData -> {
callback.accept(resData, sharedSecret); //回调把请求的共享密钥带上,用于解密
});
//发送给服务端
server.receive(channel);
}
private StrKeyPair genKeyPair() throws Exception {
StrKeyPair keyPair = ECDH.generateKeyPair("secp256r1");
System.out.println("【客户端】公钥: " + keyPair.getPubKey());
System.out.println("【客户端】私钥: " + keyPair.getPriKey());
System.out.println("【客户端】已刷新密钥对!");
System.out.println("---------------------------------");
return keyPair;
}
private String getSharedSecret(String priKey) throws Exception {
return ECDH.deriveSharedSecret(priKey, this.servPubKey);
}
}
//模拟后台服务端
static class Server {
//服务端密钥对,只生成一次
private final StrKeyPair keyPair;
public Server() throws Exception {
this.keyPair = ECDH.generateKeyPair("secp256r1");
System.out.println("【服务端】公钥: " + this.keyPair.getPubKey());
System.out.println("【服务端】私钥: " + this.keyPair.getPriKey());
System.out.println("【服务端】初始化完成!");
System.out.println("---------------------------------");
}
public String getPubKey(){
return this.keyPair.getPubKey();
}
public void receive(Channel channel) throws Exception {
//客户端公钥
String clientPubKey = channel.getClientPubKey();
//计算共享密钥
String sharedSecret = getSharedSecret(clientPubKey);
//解密数据
String message = Aes.decrypt(channel.read(), sharedSecret);
System.out.println("【服务端】收到消息: " + message);
System.out.println("【服务端】客户端公钥:" + clientPubKey);
System.out.println("---------------------------------");
//响应消息
String resMsg = "服务端已收到消息->"+ message;
//加密
String data = Aes.encrypt(resMsg, sharedSecret);
//发送
channel.write(data);
}
/**
* 计算共享密钥(服务端私钥 + 客户端公钥)
*/
private String getSharedSecret(String clientPubKey) throws Exception {
return ECDH.deriveSharedSecret(this.keyPair.getPriKey(), clientPubKey);
}
}
//模拟请求通道
static class Channel {
private final String reqData;
private final String clientPubKey;
private final Consumer<String> callback;
public Channel(String clientPubKey, String reqData, Consumer<String> callback) {
this.reqData = reqData;
this.clientPubKey = clientPubKey;
this.callback = callback;
}
//获取客户端公钥
public String getClientPubKey(){
return this.clientPubKey;
}
//读取数据
public String read() {
return this.reqData;
}
//写入数据
public void write(String resData){
this.callback.accept(resData);
}
}
//密钥对(base64字符串)
static class StrKeyPair {
private final String priKey;
private final String pubKey;
private StrKeyPair(KeyPair keyPair) {
this.priKey = BASE64.encode(keyPair.getPrivate().getEncoded());
this.pubKey = BASE64.encode(keyPair.getPublic().getEncoded());
}
public static StrKeyPair of(KeyPair keyPair) {
return new StrKeyPair(keyPair);
}
public String getPriKey() {
return this.priKey;
}
public String getPubKey() {
return this.pubKey;
}
}
//测试
public static void main(String[] args) throws Exception {
//创建服务端
Server server = new Server();
//创建客户端
Client client = new Client(server.getPubKey());
//向服务端发送请求
client.request(server, "hello world", (resData, sharedSecret) -> {
//解密数据
String message = Aes.decrypt(resData, sharedSecret);
System.out.println("【客户端】收到消息: " + message);
});
System.out.println("============================================");
//向服务端发送请求
client.request(server, "{\"name\": \"张三\"}", (resData, sharedSecret) -> {
//解密数据
String message = Aes.decrypt(resData, sharedSecret);
System.out.println("【客户端】收到消息: " + message);
});
System.out.println("============================================");
}
}
4.输出
java
【服务端】公钥: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
【服务端】私钥: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAaJ/UnsK4Tt+1Vyt9vyAnYqinr8uobgPZOlvCMeihKkg==
【服务端】初始化完成!
---------------------------------
【客户端】初始化完成,服务端公钥: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
---------------------------------
【客户端】公钥: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDz5YRdfRm1l0Wj0BqIBMEq2zLca96eidX/DEIZipQol8gzV4YKiLwtWxmS2rplAay1/4stiuofvLFZZAJ0rM/w==
【客户端】私钥: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDLGtJ2Lxm+8cd+o1iDbxkigojjFdy+9k/wVhQ0XJTRQw==
【客户端】已刷新密钥对!
---------------------------------
【共享密钥】计算-私钥:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDLGtJ2Lxm+8cd+o1iDbxkigojjFdy+9k/wVhQ0XJTRQw==
【共享密钥】计算-公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
【共享密钥】计算-结果:fIPyLCxiu26stP2C4bGyCU8Eh+uYUWp3w8PjRWdxh8k=
---------------------------------
【共享密钥】计算-私钥:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAaJ/UnsK4Tt+1Vyt9vyAnYqinr8uobgPZOlvCMeihKkg==
【共享密钥】计算-公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDz5YRdfRm1l0Wj0BqIBMEq2zLca96eidX/DEIZipQol8gzV4YKiLwtWxmS2rplAay1/4stiuofvLFZZAJ0rM/w==
【共享密钥】计算-结果:fIPyLCxiu26stP2C4bGyCU8Eh+uYUWp3w8PjRWdxh8k=
---------------------------------
【服务端】收到消息: hello world
【服务端】客户端公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDz5YRdfRm1l0Wj0BqIBMEq2zLca96eidX/DEIZipQol8gzV4YKiLwtWxmS2rplAay1/4stiuofvLFZZAJ0rM/w==
---------------------------------
【客户端】收到消息: 服务端已收到消息->hello world
============================================
【客户端】公钥: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGbyRS8JTE32TLAMXYaFpi8pY8Cf2R7u9gdta+PnB7thgyAZ5FEBGjgRhNEL2MFD7g5B8yLX3cTghpjisajs4Tw==
【客户端】私钥: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCADaXXP8AmBXXhrDJIXpFBH9byFr+ak38DAY1J1+zCktQ==
【客户端】已刷新密钥对!
---------------------------------
【共享密钥】计算-私钥:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCADaXXP8AmBXXhrDJIXpFBH9byFr+ak38DAY1J1+zCktQ==
【共享密钥】计算-公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
【共享密钥】计算-结果:7ngHo0nz8W2Zv9vMKORu5jC7uPIhgsZaS/657vCpcUs=
---------------------------------
【共享密钥】计算-私钥:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAaJ/UnsK4Tt+1Vyt9vyAnYqinr8uobgPZOlvCMeihKkg==
【共享密钥】计算-公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGbyRS8JTE32TLAMXYaFpi8pY8Cf2R7u9gdta+PnB7thgyAZ5FEBGjgRhNEL2MFD7g5B8yLX3cTghpjisajs4Tw==
【共享密钥】计算-结果:7ngHo0nz8W2Zv9vMKORu5jC7uPIhgsZaS/657vCpcUs=
---------------------------------
【服务端】收到消息: {"name": "张三"}
【服务端】客户端公钥:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGbyRS8JTE32TLAMXYaFpi8pY8Cf2R7u9gdta+PnB7thgyAZ5FEBGjgRhNEL2MFD7g5B8yLX3cTghpjisajs4Tw==
---------------------------------
【客户端】收到消息: 服务端已收到消息->{"name": "张三"}
============================================