SpringBoot集成ECDH密钥交换

简介

对称加解密算法都需要一把秘钥,但是很多情况下,互联网环境不适合传输这把对称密码,有密钥泄露的风险,为了解决这个问题ECDH密钥交换应运而生

EC:Elliptic Curve ——椭圆曲线,生成密钥的方法

DH:Diffie-Hellman Key Exchange ——交换密钥的方法

设计

数据传输的两方服务端(Server)和客户端(Client)

服务端生成密钥对Server-Public和Servier-Private

客户端生成密钥对Client-Public和Client-Private

客户端获取服务端的公钥和客户端的私钥进行计算CaculateKey(Server-Public,Client-Private)出共享密钥ShareKey1

服务端获取客户端的公钥和服务端的私钥进行计算CaculateKey(Client-Public,Server-Private)出共享密钥ShareKey2

ShareKey1和ShareKey2必定一致,ShareKey就是双方传输数据进行AES加密时的密钥

实现

生成密钥对

后端

```java
public static ECDHKeyInfo generateKeyInfo(){
        ECDHKeyInfo keyInfo = new ECDHKeyInfo();
        try{
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
            ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
            keyPairGenerator.initialize(ecSpec, new SecureRandom());
            KeyPair kp = keyPairGenerator.generateKeyPair();
            ECPublicKey ecPublicKey = (ECPublicKey) kp.getPublic();
            ECPrivateKey ecPrivateKey = (ECPrivateKey) kp.getPrivate();
            // 获取公钥点的x和y坐标
            BigInteger x = ecPublicKey.getW().getAffineX();
            BigInteger y = ecPublicKey.getW().getAffineY();
            // 将x和y坐标转换为十六进制字符串
            String xHex = x.toString(16);
            String yHex = y.toString(16);
            String publicKey = xHex + "|" + yHex;
            String privateKey = Base64.getEncoder().encodeToString(ecPrivateKey.getEncoded());
            keyInfo.setPublicKey(publicKey);
            keyInfo.setPrivateKey(privateKey);
        }catch (Exception e){
            e.printStackTrace();
        }
        return keyInfo;
    }

    public static class ECDHKeyInfo{
        private String publicKey;
        private String privateKey;

        public String getPublicKey() {
            return publicKey;
        }

        public void setPublicKey(String publicKey) {
            this.publicKey = publicKey;
        }

        public String getPrivateKey() {
            return privateKey;
        }

        public void setPrivateKey(String privateKey) {
            this.privateKey = privateKey;
        }
    }
```

前端

引入elliptic.js(https://cdn.bootcdn.net/ajax/libs/elliptic/6.5.6/elliptic.js)

```javascript
const EC = elliptic.ec;
    const ec = new EC('p256'); // P-256曲线

    // 生成密钥对
    const keyPair = ec.genKeyPair();
    const publicKey = keyPair.getPublic().getX().toString('hex') + "|" + keyPair.getPublic().getY().toString('hex');
```

共享密钥计算

后端

```java
public static String caculateShareKey(String serverPrivateKey,String receivePublicKey){
        String shareKey = "";
        try{
            // 1. 后端私钥 Base64 字符串
            // 2. 从 Base64 恢复后端私钥
            ECPrivateKey privKey = loadPrivateKeyFromBase64(serverPrivateKey);
            // 3. 前端传递的公钥坐标 (x 和 y 坐标,假设为十六进制字符串)
            // 假设这是从前端接收到的公钥的 x 和 y 坐标
            String xHex = receivePublicKey.split("\\|")[0];  // 用前端传递的 x 坐标替换
            String yHex = receivePublicKey.split("\\|")[1];  // 用前端传递的 y 坐标替换
            // 4. 将 x 和 y 转换为 BigInteger
            BigInteger x = new BigInteger(xHex, 16);
            BigInteger y = new BigInteger(yHex, 16);
            // 5. 创建 ECPoint 对象 (公钥坐标)
            ECPoint ecPoint = new ECPoint(x, y);
            // 6. 获取 EC 参数(例如 secp256r1)
            ECParameterSpec ecSpec = getECParameterSpec();
            // 7. 恢复公钥
            ECPublicKey pubKey = recoverPublicKey(ecPoint, ecSpec);
            // 8. 使用 ECDH 计算共享密钥
            byte[] sharedSecret = calculateSharedSecret(privKey, pubKey);
            // 9. 打印共享密钥
            shareKey = bytesToHex(sharedSecret);
        }catch (Exception e){
            e.printStackTrace();
        }
        return shareKey;
    }

    // 从 Base64 加载 ECPrivateKey
    private static ECPrivateKey loadPrivateKeyFromBase64(String privateKeyBase64) throws Exception {
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyBase64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        return (ECPrivateKey) keyFactory.generatePrivate(keySpec);
    }
    // 获取 EC 参数(例如 secp256r1)
    private static ECParameterSpec getECParameterSpec() throws Exception {
        // 手动指定 EC 曲线(例如 secp256r1)
        AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
        params.init(new ECGenParameterSpec("secp256r1"));  // 使用标准的 P-256 曲线
        return params.getParameterSpec(ECParameterSpec.class);
    }
    // 恢复公钥
    private static ECPublicKey recoverPublicKey(ECPoint ecPoint, ECParameterSpec ecSpec) throws Exception {
        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(ecPoint, ecSpec);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        return (ECPublicKey) keyFactory.generatePublic(pubKeySpec);
    }
    // 使用 ECDH 计算共享密钥
    private static byte[] calculateSharedSecret(ECPrivateKey privKey, ECPublicKey pubKey) throws Exception {
        KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
        keyAgreement.init(privKey);
        keyAgreement.doPhase(pubKey, true);
        return keyAgreement.generateSecret();
    }

    // 将字节数组转换为十六进制字符串
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            hexString.append(String.format("%02x", b));
        }
        return hexString.toString();
    }
```

前端

```javascript
var keyArray = serverPublicPointKey.split("|")
    const otherKey = ec.keyFromPublic({ x: keyArray[0], y: keyArray[1] }, 'hex');
    const sharedSecret = keyPair.derive(otherKey.getPublic());
```

AES加密

后端

```java
public static String encryptData(String data,String shareKey){
        String result = "";
        try{
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] aesKey = digest.digest(shareKey.getBytes());  // 获取 256 位密钥
            SecretKey key = new SecretKeySpec(aesKey, "AES");
            byte[] resultData = encrypt(data,key);
            result = Base64.getEncoder().encodeToString(resultData);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    public static String decryptData(String data,String shareKey){
        String result = "";
        try{
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] aesKey = digest.digest(shareKey.getBytes());  // 获取 256 位密钥
            SecretKey key = new SecretKeySpec(aesKey, "AES");
            byte[] resultData = decrypt(Base64.getDecoder().decode(data),key);
            result = new String(resultData);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    private static final String KEY_ALGORITHM = "AES";
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final String IV = "0102030405060708"; // 16 bytes key

    // 使用AES密钥加密数据
    private static byte[] encrypt(String plaintext, SecretKey aesKey) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(aesKey.getEncoded(), KEY_ALGORITHM);
        IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
        byte[] encrypted = cipher.doFinal(plaintext.getBytes());
        return encrypted;
    }

    // 使用AES密钥解密数据
    private static byte[] decrypt(byte[] encryptedData, SecretKey aesKey) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(aesKey.getEncoded(), KEY_ALGORITHM);
        IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
        byte[] original = cipher.doFinal(encryptedData);
        return original;
    }
```

前端

引入crypto-js.min.js(https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js)

```javascript
function encryptByECDH(message, shareKey) {
    const aesKey = CryptoJS.SHA256(shareKey);
    const key = CryptoJS.enc.Base64.parse(aesKey.toString(CryptoJS.enc.Base64));
    return encryptByAES(message,key)
}

function decryptByECDH(message, shareKey) {
    const aesKey = CryptoJS.SHA256(shareKey);
    const key = CryptoJS.enc.Base64.parse(aesKey.toString(CryptoJS.enc.Base64));
    return decryptByAES(message,key)
}

function encryptByAES(message, key) {
    const iv = CryptoJS.enc.Utf8.parse("0102030405060708");
    const encrypted = CryptoJS.AES.encrypt(message, key, { iv: iv , mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    return encrypted.toString();
}

function decryptByAES(message, key) {
    const iv = CryptoJS.enc.Utf8.parse("0102030405060708");
    const bytes = CryptoJS.AES.decrypt(message, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    const originalText = bytes.toString(CryptoJS.enc.Utf8);
    return originalText;
}
```

注意

  • 前端生成的密钥对和后端生成的密钥对形式不一致,需要将前端的公钥拆解成坐标点到后端进行公钥还原
  • 同理后端的公钥也要拆分成坐标点传输到前端进行计算
  • 生成的ShareKey共享密钥为了满足AES的密钥长度要求需要进行Share256计算
  • 前后端AES互通需要保证IV向量为同一值

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/5326.html

(0)
LomuLomu
上一篇 2025 年 1 月 6 日
下一篇 2025 年 1 月 6 日

相关推荐

  • 2024Java零基础自学路线(Java基础、Java高并发、MySQL、Spring、Redis、设计模式、Spring Cloud)

    目录 一、Java基础 1、Java 基础 3、Java8新特性 4、Java集合 5、Java高并发 6、Java代码实例 二、MySQL数据库 三、Spring Boot框架(35天) 四、微服务Spring Cloud 四、Redis中间件 五、MongoDB数据库 六、Netty网络编程 七、23种设计模式 八、Dubbo 九、JavaScript零…

    2024 年 12 月 27 日
    37300
  • 【永久激活】IDEA 2024激活破解保姆级教程,附激活码+工具,亲测可用

    IntelliJ IDEA 是 Java 编程语言的集成开发环境,被公认为最好的 Java 开发工具之一。本文分享通过脚本免费激活 IDEA 等 Jetbrains 全家桶工具,支持 2021 以上的版本包括最新版本。 一、下载并安装 IDEA 大家可以直接在 JetBrains 官网下载最新版本的 IDEA。安装步骤非常简单,按照提示一步一步进行即可。 二…

    未分类 2024 年 6 月 23 日
    1.7K00
  • Java怎样实现将数据导出为Word文档

    文章首发于我的博客:Java怎样实现将数据导出为Word文档 – Liu Zijian’s Blog 我们在开发一些系统的时候,例如OA系统,经常能遇到将审批单数据导出为word和excel文档的需求,导出为excel是比较简单的,因为excel有单元格来供我们定位数据位置,但是word文档的格式不像表格那样可以轻松的定位,要想将数据导出为一些带有图片和表格…

    2025 年 1 月 14 日
    44500
  • 深入解析JSP技术:从基础到实战应用

    JSP(JavaServer Pages)作为Java EE体系中的核心组件,为动态网页开发提供了高效解决方案。其设计初衷在于简化服务端编程,特别适用于需要动态生成HTML、XML等内容的Web项目开发。 一、JSP技术概览 技术定义: JSP是基于Java语言的动态网页技术标准 采用HTML嵌入Java代码的模式,服务端执行后返回处理结果 运行机制: 用户…

    未分类 2025 年 5 月 19 日
    26600
  • 高效灵活!企业级IT资产配置管理数据库解决方案

    在现代企业IT运维中,基础设施规模庞大且变动频繁,传统管理方式往往难以应对复杂的资产配置需求。本文为您推荐一款模块化设计的运维配置管理数据库系统,它能有效提升企业IT团队对硬件设备和软件服务的管控效率。 产品概述 CMDB Pro是一款采用现代化架构的配置管理数据库,具备模型自定义和智能资源探测能力,专为解决企业级IT资产管理难题而设计。核心优势:- 智能探…

    2025 年 5 月 11 日
    23600

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信