Java實現(xiàn)后端和前端的接口數(shù)據(jù)加密方案詳解
1.解決問題
前后端在交互過程中,請求和響應(yīng)的數(shù)據(jù)需要加密處理,并保證安全和性能。
方案名稱:ECIES (Elliptic Curve Integrated Encryption Scheme,橢圓曲線集成加密方案)
2.核心思路
服務(wù)端準備
- 生成密鑰對(EC-橢圓曲線),私鑰servPriKey放在服務(wù)端。
- 公鑰servPubKey安全地預(yù)置或下發(fā)給客戶端(如通過初始化接口)。
客戶端加密(每次請求)
- 生成臨時密鑰對(EC-橢圓曲線),公鑰pubKey,私鑰priKey
- 用私鑰priKey + 服務(wù)端公鑰servPubKey計算共享密鑰Key(ECDH-協(xié)商+sha256)
- 加密請求業(yè)務(wù)數(shù)據(jù)(AES):encryptData = AES.encrypt(data, Key)
- 發(fā)送加密后的業(yè)務(wù)數(shù)據(jù)encryptData 和公鑰pubKey給服務(wù)端
服務(wù)端解密
- 用私鑰servPriKey + 客戶端公鑰pubKey計算共享密鑰Key(ECDH-協(xié)商+sha256)
- 解密請求數(shù)據(jù),data = AES.decrypt(encryptData, Key)
服務(wù)端加密:通過3中計算的Key直接加密數(shù)據(jù)即可
客戶端解密:通過2中計算的Key直接解密數(shù)據(jù)即可
3.代碼示例
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.Consumer;
/**
* ECIES (Elliptic Curve Integrated Encryption Scheme,橢圓曲線集成加密方案)
* 這個方案是橢圓曲線密碼學(xué)中的密碼學(xué)方案,用于實現(xiàn)前后端間通信的數(shù)據(jù)加密,是應(yīng)用內(nèi)的加密機制。
* 核心是密鑰協(xié)商:使用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. 指定橢圓曲線參數(shù),例如 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 myPrivateKey = toPrivateKey(myPriKey);
PublicKey otherPublicKey = toPublicKey(otherPubKey);
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(myPrivateKey);
ka.doPhase(otherPublicKey, true);
byte[] rawSecret = ka.generateSecret();
//用SHA-256哈希一次,得到32字節(jié)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("【共享密鑰】計算-結(jié)果:"+shearedSecret);
System.out.println("---------------------------------");
return shearedSecret;
}
private static PublicKey toPublicKey(String pubKey) throws Exception {
byte[] decodedBytes = BASE64.decode(pubKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedBytes);
return keyFactory.generatePublic(keySpec);
}
private static PrivateKey toPrivateKey(String priKey) throws Exception {
byte[] decodedBytes = BASE64.decode(priKey);
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 {
//服務(wù)端公鑰,固定值,后續(xù)不再更新
private final String servPubKey;
public Client(String servPubKey){
this.servPubKey = servPubKey;
System.out.println("【客戶端】初始化完成,服務(wù)端公鑰: " + servPubKey);
System.out.println("---------------------------------");
}
public void request(Server server, String message, Consumer<String> callback) throws Exception {
//生成密鑰對
StrKeyPair keyPair = genKeyPair();
//計算共享密鑰
String sharedSecret = getSharedSecret(keyPair.getPriKey());
//對稱加密數(shù)據(jù)
String reqData = Aes.encrypt(message, sharedSecret);
//封裝傳輸通道
Channel channel = new Channel(keyPair.getPubKey(), reqData, resData -> {
//解密數(shù)據(jù)
String data = Aes.decrypt(resData, sharedSecret);
//將數(shù)據(jù)傳給回調(diào)
callback.accept(data);
});
//發(fā)送給服務(wù)端
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);
}
}
//模擬后臺服務(wù)端
static class Server {
//服務(wù)端密鑰對,只生成一次
private final StrKeyPair keyPair;
public Server() throws Exception {
this.keyPair = ECDH.generateKeyPair("secp256r1");
System.out.println("【服務(wù)端】公鑰: " + this.keyPair.getPubKey());
System.out.println("【服務(wù)端】私鑰: " + this.keyPair.getPriKey());
System.out.println("【服務(wù)端】初始化完成!");
System.out.println("---------------------------------");
}
public String getPubKey(){
return this.keyPair.getPubKey();
}
public void receive(Channel channel) throws Exception {
//客戶端公鑰
String clientPubKey = channel.getClientPubKey();
//計算共享密鑰
//因為服務(wù)端私鑰是固定的
//如果客戶端不是每次請求都刷新密鑰對,可以按clientPubKey直接緩存Key
//但是要注意內(nèi)存溢出,可以設(shè)置緩存有效時間,指定最大緩存?zhèn)€數(shù)等
String sharedSecret = getSharedSecret(clientPubKey);
//解密數(shù)據(jù)
String message = Aes.decrypt(channel.read(), sharedSecret);
System.out.println("【服務(wù)端】收到消息: " + message);
System.out.println("【服務(wù)端】客戶端公鑰:" + clientPubKey);
System.out.println("---------------------------------");
//響應(yīng)消息
String resMsg = "服務(wù)端已收到消息->"+ message;
//加密
String data = Aes.encrypt(resMsg, sharedSecret);
//發(fā)送
channel.write(data);
}
/**
* 計算共享密鑰(服務(wù)端私鑰 + 客戶端公鑰)
*/
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;
}
//讀取數(shù)據(jù)
public String read() {
return this.reqData;
}
//寫入數(shù)據(jù)
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 {
//創(chuàng)建服務(wù)端
Server server = new Server();
//創(chuàng)建客戶端
Client client = new Client(server.getPubKey());
//向服務(wù)端發(fā)送請求
client.request(server, "hello world", data -> {
System.out.println("【客戶端】收到消息: " + data);
});
System.out.println("============================================");
//向服務(wù)端發(fā)送請求
client.request(server, "{\"name\": \"張三\"}", data -> {
System.out.println("【客戶端】收到消息: " + data);
});
System.out.println("============================================");
}
}
4.輸出
【服務(wù)端】公鑰: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
【服務(wù)端】私鑰: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAaJ/UnsK4Tt+1Vyt9vyAnYqinr8uobgPZOlvCMeihKkg==
【服務(wù)端】初始化完成!
---------------------------------
【客戶端】初始化完成,服務(wù)端公鑰: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
---------------------------------
【客戶端】公鑰: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDz5YRdfRm1l0Wj0BqIBMEq2zLca96eidX/DEIZipQol8gzV4YKiLwtWxmS2rplAay1/4stiuofvLFZZAJ0rM/w==
【客戶端】私鑰: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDLGtJ2Lxm+8cd+o1iDbxkigojjFdy+9k/wVhQ0XJTRQw==
【客戶端】已刷新密鑰對!
---------------------------------
【共享密鑰】計算-私鑰:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDLGtJ2Lxm+8cd+o1iDbxkigojjFdy+9k/wVhQ0XJTRQw==
【共享密鑰】計算-公鑰:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
【共享密鑰】計算-結(jié)果:fIPyLCxiu26stP2C4bGyCU8Eh+uYUWp3w8PjRWdxh8k=
---------------------------------
【共享密鑰】計算-私鑰:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAaJ/UnsK4Tt+1Vyt9vyAnYqinr8uobgPZOlvCMeihKkg==
【共享密鑰】計算-公鑰:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDz5YRdfRm1l0Wj0BqIBMEq2zLca96eidX/DEIZipQol8gzV4YKiLwtWxmS2rplAay1/4stiuofvLFZZAJ0rM/w==
【共享密鑰】計算-結(jié)果:fIPyLCxiu26stP2C4bGyCU8Eh+uYUWp3w8PjRWdxh8k=
---------------------------------
【服務(wù)端】收到消息: hello world
【服務(wù)端】客戶端公鑰:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDz5YRdfRm1l0Wj0BqIBMEq2zLca96eidX/DEIZipQol8gzV4YKiLwtWxmS2rplAay1/4stiuofvLFZZAJ0rM/w==
---------------------------------
【客戶端】收到消息: 服務(wù)端已收到消息->hello world
============================================
【客戶端】公鑰: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGbyRS8JTE32TLAMXYaFpi8pY8Cf2R7u9gdta+PnB7thgyAZ5FEBGjgRhNEL2MFD7g5B8yLX3cTghpjisajs4Tw==
【客戶端】私鑰: MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCADaXXP8AmBXXhrDJIXpFBH9byFr+ak38DAY1J1+zCktQ==
【客戶端】已刷新密鑰對!
---------------------------------
【共享密鑰】計算-私鑰:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCADaXXP8AmBXXhrDJIXpFBH9byFr+ak38DAY1J1+zCktQ==
【共享密鑰】計算-公鑰:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUutqZsAN1Kh+FFvS95lKFob0zKZZY0mtBKNOYFSZ/WzaLO8CHH1zjcH3nV82MxXwyj0t+yhP/Dugfriy8VIWbg==
【共享密鑰】計算-結(jié)果:7ngHo0nz8W2Zv9vMKORu5jC7uPIhgsZaS/657vCpcUs=
---------------------------------
【共享密鑰】計算-私鑰:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAaJ/UnsK4Tt+1Vyt9vyAnYqinr8uobgPZOlvCMeihKkg==
【共享密鑰】計算-公鑰:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGbyRS8JTE32TLAMXYaFpi8pY8Cf2R7u9gdta+PnB7thgyAZ5FEBGjgRhNEL2MFD7g5B8yLX3cTghpjisajs4Tw==
【共享密鑰】計算-結(jié)果:7ngHo0nz8W2Zv9vMKORu5jC7uPIhgsZaS/657vCpcUs=
---------------------------------
【服務(wù)端】收到消息: {"name": "張三"}
【服務(wù)端】客戶端公鑰:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGbyRS8JTE32TLAMXYaFpi8pY8Cf2R7u9gdta+PnB7thgyAZ5FEBGjgRhNEL2MFD7g5B8yLX3cTghpjisajs4Tw==
---------------------------------
【客戶端】收到消息: 服務(wù)端已收到消息->{"name": "張三"}
============================================
5.落地建議
服務(wù)端的加解密數(shù)據(jù)操作應(yīng)該添加全局處理,比如攔截器,AOP切面等。
客戶端的加解密數(shù)據(jù)操作同樣應(yīng)該全局處理,比如 axios的攔截器內(nèi)。
全局處理加解密數(shù)據(jù)可以讓編寫業(yè)務(wù)的人不用關(guān)心加解密,使用起來是無感的(就像不加解密之前一樣)
前后端應(yīng)該協(xié)商好傳輸?shù)母袷剑热纾?/p>
POST提交,JSON格式:
{
"key": "客戶端公鑰",
"data": "加密數(shù)據(jù)"
}GET提交:url?key=客戶端公鑰&data=加密數(shù)據(jù)
6.結(jié)合前端完整示例
本示例中為了和前端js插件統(tǒng)一,所有密鑰均用十六進制(hex)表示,不再用Base64的形式
6.1后端代碼
package com.visy.utils;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import javax.crypto.KeyAgreement;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.*;
import java.util.Arrays;
import java.util.Base64;
/**
* Hex 十六進制版本的ECDH工具
*/
public class HexECDHDemo {
static class Hex {
/**
* BigInteger轉(zhuǎn)64字符hex(補零)
*/
private static String to64Hex(BigInteger num) {
String hex = num.toString(16);
if (hex.length() < 64) {
return repeatedStr('0', 64 - hex.length()) + hex;
}
return hex;
}
private static String bytesToHex(byte[] bytes) {
return HexUtil.encodeHexStr(bytes);
}
public static byte[] hexToBytes(String hex){
return HexUtil.decodeHex(hex);
}
private static String repeatedStr(char ch, int count) {
if (count <= 0) {
return "";
}
char[] chars = new char[count];
Arrays.fill(chars, ch);
return new String(chars);
}
}
public static class StrKeyPair {
private final String priKey;
private final String pubKey;
private StrKeyPair(String priKey, String pubKey) {
this.priKey = priKey;
this.pubKey = pubKey;
}
public static StrKeyPair of(KeyPair keyPair) {
// 1. 提取公鑰坐標
ECPublicKey ecPubKey = (ECPublicKey) keyPair.getPublic();
ECPoint point = ecPubKey.getW(); //返回橢圓曲線上的點 (x, y坐標)
// 2. 格式化為04+x+y格式
BigInteger x = point.getAffineX(), y = point.getAffineY();
String xHex = Hex.to64Hex(x), yHex = Hex.to64Hex(y);
String publicKey = "04" + xHex + yHex;
// 3. 提取私鑰原始值
ECPrivateKey ecPriKey = (ECPrivateKey) keyPair.getPrivate();
String privateKey = Hex.to64Hex(ecPriKey.getS()); //返回私鑰的標量值(大整數(shù))
return new StrKeyPair(privateKey, publicKey);
}
public String getPriKey() {
return this.priKey;
}
public String getPubKey() {
return this.pubKey;
}
}
public static class ECDH {
private static final String CURVE_NAME = "secp256r1";
/**
* 生成密鑰對(返回hex格式)
*/
public static StrKeyPair genKeyPair() throws Exception {
// 1. 生成標準密鑰對
ECGenParameterSpec ecSpec = new ECGenParameterSpec(CURVE_NAME);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(ecSpec, new SecureRandom());
KeyPair keyPair = kpg.generateKeyPair();
//轉(zhuǎn)換成Hex字符串的密鑰對
return StrKeyPair.of(keyPair);
}
public static String derive(String myPriKey, String otherPubKey) throws Exception {
PrivateKey myPrivateKey = toPrivateKey(myPriKey);
PublicKey otherPublicKey = toPublicKey(otherPubKey);
// 3. 執(zhí)行ECDH
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(myPrivateKey);
keyAgreement.doPhase(otherPublicKey, true);
// 4. 獲取共享密鑰(原始字節(jié))
byte[] sharedSecret = keyAgreement.generateSecret();
// 5. 返回hex
return Hex.bytesToHex(sharedSecret);
}
/**
* 將04+x+y格式的hex公鑰轉(zhuǎn)換為Java PublicKey
*/
private static PublicKey toPublicKey(String pubKey) throws Exception {
if (!pubKey.startsWith("04")) {
throw new IllegalArgumentException("公鑰必須以04開頭");
}else if(pubKey.length() != 130){
throw new IllegalArgumentException("公鑰格式有誤");
}
// 提取x, y坐標
String xHex = pubKey.substring(2, 66), yHex = pubKey.substring(66, 130);
BigInteger x = new BigInteger(xHex, 16), y = new BigInteger(yHex, 16);
// 獲取曲線參數(shù)
ECParameterSpec ecParams = getECParameterSpec();
// 創(chuàng)建EC點
ECPoint point = new ECPoint(x, y);
// 創(chuàng)建公鑰規(guī)范
ECPublicKeySpec keySpec = new ECPublicKeySpec(point, ecParams);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePublic(keySpec);
}
/**
* 將hex私鑰轉(zhuǎn)換為Java PrivateKey
*/
private static PrivateKey toPrivateKey(String priKey) throws Exception {
BigInteger s = new BigInteger(priKey, 16);
// 獲取曲線參數(shù)
ECParameterSpec ecParams = getECParameterSpec();
// 創(chuàng)建私鑰規(guī)范
ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, ecParams);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(keySpec);
}
/**
* 獲取橢圓曲線參數(shù)
*/
private static ECParameterSpec getECParameterSpec() throws Exception {
// 通過生成臨時密鑰對獲取曲線參數(shù)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(new ECGenParameterSpec(CURVE_NAME));
KeyPair tempKeyPair = kpg.generateKeyPair();
return ((ECPublicKey) tempKeyPair.getPublic()).getParams();
}
}
//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);
}
}
//AES加解密工具
static class Aes {
public static String encrypt(String data, String key, String iv){
AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, Hex.hexToBytes(key), Hex.hexToBytes(iv));
return aes.encryptBase64(data);
}
public static String decrypt(String data, String key, String iv){
AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, Hex.hexToBytes(key), Hex.hexToBytes(iv));
return aes.decryptStr(BASE64.decode(data));
}
}
public static void main(String[] args) throws Exception {
// 生成密鑰對,只生成一次,將公鑰和前端共享
//StrKeyPair strKeyPair = ECDH.genKeyPair();
//04122670c45aa251ecef1528f76c248e288cd35dd728538764cbdaf3efa1c91bccc184daec61cc9abd660f947a530da82e3b4a76a07271d0d96a92660592722d39
//System.out.println("服務(wù)端公鑰:" + strKeyPair.getPubKey());
//9353c717bb2d65b5516e0e6c56fa7574592767d8abe70e796c1719e8f9d4d55b
//System.out.println("服務(wù)端私鑰:" + strKeyPair.getPriKey());
//服務(wù)端私鑰(需保密)
String servPrivateKey = "9353c717bb2d65b5516e0e6c56fa7574592767d8abe70e796c1719e8f9d4d55b";
//客戶端公鑰,來自前端請求參數(shù)
String clientPublicKey = "04dd42dadd5d99539c15410aede7943a9c55ecb175d687feac37a602293e8a90fc76daa2d1d0186147628cb8ca24e86e7710b5e024e5a0d7ff55d0b342cf4f2d6e";
//計算共享秘密(Aes 加密密鑰)
String secret = ECDH.derive(servPrivateKey, clientPublicKey);
System.out.println("共享秘密:"+ secret);
//來自前端的向量iv和加密后的數(shù)據(jù)
String iv = "34f5653b77a3b85db7826a40768d12a3";
String encryptData = "pBkwIDR15BWS7xk2R5YPVmnXasrQpyybTq+C1N8/XTU=";
//解密并輸出結(jié)果
System.out.println("解密結(jié)果:"+Aes.decrypt(encryptData, secret, iv));
}
}
6.2 前端代碼
<!doctype html>
<html>
<head >
<title>ECDH Test</title>
</head>
<body >
<div style="text-align: center">
<div style = "margin-top:200px;display:flex;flex-direction: column;align-items: center;gap: 10px;">
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">服務(wù)端公鑰: </div>
<textarea rows="3" cols="80" id="serv_pub_key_ipt" style="font-size: 20px"></textarea>
<button id="compute" style="margin-left:20px">生成并計算共享密鑰</button>
</div>
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">本地公鑰: </div>
<textarea rows="3" cols="80" id="pub_key_ipt" style="font-size: 20px"></textarea>
</div>
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">本地私鑰: </div>
<textarea rows="3" cols="80" id="pri_key_ipt" style="font-size: 20px"></textarea>
</div>
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">共享秘密: </div>
<textarea rows="3" cols="80" id="sheared_key_ipt" style="font-size: 20px; font-weight:bold"></textarea>
</div>
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">發(fā)送內(nèi)容: </div>
<textarea rows="3" cols="80" id="data_ipt" style="font-size: 20px"></textarea>
<button id="do_encrypt" style="margin-left:20px">加密</button>
</div>
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">IV: </div>
<textarea rows="1" cols="80" id="iv_ipt" style="font-size: 20px; font-weight:bold"></textarea>
</div>
<div style="width: 800px;display:flex;flex-direction: row">
<div style="width:150px;text-align:right;margin-right:10px">加密結(jié)果: </div>
<textarea rows="3" cols="80" id="encrypt_data_ipt" style="font-size: 20px; font-weight:bold"></textarea>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- ecdh用到的插件,生成密鑰對,計算共享秘密 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/elliptic/6.6.1/elliptic.min.js"></script>
<!-- 數(shù)據(jù)加密插件,需用到AES -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
<script>
(function(){
const EC = elliptic.ec;
const ec = new EC('p256'); // 選擇橢圓曲線secp256k1的別名是p256
//生成共享秘密
$("#compute").on("click", function (){
const servPubKey = $("#serv_pub_key_ipt").val();
if(servPubKey==null || !(servPubKey+"").trim()){
alert("請先輸入服務(wù)端公鑰!");
return;
}
const keyPair = ec.genKeyPair(); //生成密鑰對
const priKey = keyPair.getPrivate("hex");
const pubKey = keyPair.getPublic("hex");
$("#pub_key_ipt").val(pubKey);
$("#pri_key_ipt").val(priKey);
const secret = derive(priKey, servPubKey);
$("#sheared_key_ipt").val(secret);
})
//加密
$("#do_encrypt").on("click", function(){
const data = $("#data_ipt").val();
if(data==null || !(data+"").trim()){
alert("請先輸入發(fā)送內(nèi)容!");
return;
}
const secret = $("#sheared_key_ipt").val();
if(secret==null || !(secret+"").trim()){
alert("請先生成共享秘密!");
return;
}
const iv = generateIV();
$("#iv_ipt").val(iv);
const encryptData = aesEncrypt(data, secret, iv);
$("#encrypt_data_ipt").val(encryptData);
})
// 2. 使用對方公鑰和自己的私鑰生成共享密鑰
function derive(myPriKey, otherPubKey) {
try {
// 從十六進制字符串加載自己的私鑰
const myPrivateKey = ec.keyFromPrivate(myPriKey, 'hex');
// 從十六進制字符串加載對方的公鑰
const otherPublicKey = ec.keyFromPublic(otherPubKey, 'hex').getPublic();
// 計算共享密鑰
const sharedSecret = myPrivateKey.derive(otherPublicKey);
return sharedSecret.toString(16);
} catch (error) {
console.error('生成共享密鑰失敗:', error);
return null;
}
}
/**
* AES加密函數(shù)
* @param {string} plaintext - 明文
* @param {string} keyHex - 密鑰(64字符hex,對應(yīng)32字節(jié))
* @param {string} ivHex - 初始向量(32字符hex,對應(yīng)16字節(jié))
* @returns {string} Base64格式的加密結(jié)果
*/
function aesEncrypt(plaintext, keyHex, ivHex) {
// 將hex字符串轉(zhuǎn)換為CryptoJS格式
var key = CryptoJS.enc.Hex.parse(keyHex);
var iv = CryptoJS.enc.Hex.parse(ivHex);
// 執(zhí)行AES-256-CBC加密
var encrypted = CryptoJS.AES.encrypt(plaintext, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 返回Base64字符串
return encrypted.toString();
}
/**
* AES解密函數(shù)
* @param {string} ciphertextBase64 - Base64格式的密文
* @param {string} keyHex - 密鑰(64字符hex)
* @param {string} ivHex - 初始向量(32字符hex)
* @returns {string} 解密后的明文
*/
function aesDecrypt(ciphertextBase64, keyHex, ivHex) {
// 將hex字符串轉(zhuǎn)換為CryptoJS格式
var key = CryptoJS.enc.Hex.parse(keyHex);
var iv = CryptoJS.enc.Hex.parse(ivHex);
// 執(zhí)行AES-256-CBC解密
var decrypted = CryptoJS.AES.decrypt(ciphertextBase64, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 轉(zhuǎn)換為UTF-8字符串
return decrypted.toString(CryptoJS.enc.Utf8);
}
/**
* 生成隨機IV(16字節(jié))
* @returns {string} 32字符的hex IV
*/
function generateIV() {
// 生成16字節(jié)隨機數(shù)
var randomBytes = CryptoJS.lib.WordArray.random(16);
// 轉(zhuǎn)換為hex字符串
return randomBytes.toString(CryptoJS.enc.Hex);
}
})();
</script>
</body>
</html>前端頁面

前端傳參
{
"clientPublicKey": "04dd42dadd5d99539c15410aede7943a9c55ecb175d687feac37a602293e8a90fc76daa2d1d0186147628cb8ca24e86e7710b5e024e5a0d7ff55d0b342cf4f2d6e",
"iv": "34f5653b77a3b85db7826a40768d12a3",
"encryptData": "pBkwIDR15BWS7xk2R5YPVmnXasrQpyybTq+C1N8/XTU="
}后端輸出
后端需接收前端的本地公鑰 + IV + 加密結(jié)果,然后計算出共享秘密,解密數(shù)據(jù)
共享秘密:f2e5a1b995524d818c9b6c5076e5bcace40641a6244f91dfe72b560f3723fc3d
解密結(jié)果:{"name":"張三","age":25}
到此這篇關(guān)于Java實現(xiàn)后端和前端的接口數(shù)據(jù)加密方案詳解的文章就介紹到這了,更多相關(guān)Java接口數(shù)據(jù)加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java分析html算法(java網(wǎng)頁蜘蛛算法示例)
近來有些朋友在做蜘蛛算法,或者在網(wǎng)頁上面做深度的數(shù)據(jù)挖掘,下面使用示例2014-03-03
關(guān)于JpaRepository的關(guān)聯(lián)查詢和@Query查詢
這篇文章主要介紹了JpaRepository的關(guān)聯(lián)查詢和@Query查詢,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
mybatis 多表關(guān)聯(lián)mapper文件寫法操作
這篇文章主要介紹了mybatis 多表關(guān)聯(lián)mapper文件寫法操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
如何使用spring?boot的程序主線程中異步訪問外部接口
CompletableFuture.supplyAsync提供了一種強大的工具,使您能夠以異步方式執(zhí)行操作,充分利用多核處理器和提高程序性能,同時保持代碼的清晰性和可維護性,本文給大家介紹使用spring?boot的程序主線程中異步訪問外部接口,感興趣的朋友一起看看吧2023-10-10
解決springmvc使用@PathVariable路徑匹配問題
這篇文章主要介紹了解決springmvc使用@PathVariable路徑匹配問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
springboot項目mapper無法自動裝配未找到?UserMapper?類型的Bean解決辦法
這篇文章給大家介紹了springboot項目mapper無法自動裝配,未找到?‘userMapper‘?類型的?Bean解決辦法(含報錯原因),文章通過圖文結(jié)合的方式介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下2024-02-02

