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加密时的密钥

实现

生成密钥对

后端

    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)

    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');

共享密钥计算

后端

    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();
    }    

前端

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

AES加密

后端

    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)

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向量为同一值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/946740.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单个变量a的妙用

一道清华大学复试上机题 问题:为什么只需要定义一个整数变量a,而不是定义一个数组a[]? 回答 在这段代码中,只需要定义一个整数变量 a,而不是一个数组 a[],是因为程序的逻辑是逐个处理输入的整数并立即输出…

【YOLOv8模型网络结构图理解】

YOLOv8模型网络结构图理解 1 YOLOv8的yaml配置文件2 YOLOv8网络结构2.1 Conv2.2 C3与C2f2.3 SPPF2.4 Upsample2.5 Detect层 1 YOLOv8的yaml配置文件 YOLOv8的配置文件定义了模型的关键参数和结构,包括类别数、模型尺寸、骨干(backbone)和头部…

手机租赁平台开发助力智能设备租赁新模式

内容概要 手机租赁平台开发,简单说就是让你用得起高大上的智能设备,不管是最新款的手机、平板,还是那些炫酷的智能耳机,这个平台应有尽有。想要体验但又不希望花大钱?那你就找对地方了!通过灵活的租赁方案…

「Mac畅玩鸿蒙与硬件48」UI互动应用篇25 - 简易购物车功能实现

本篇教程将带你实现一个简易购物车功能。通过使用接口定义商品结构,我们将创建一个动态购物车,支持商品的添加、移除以及实时总价计算。 关键词 UI互动应用接口定义购物车功能动态计算商品管理列表操作 一、功能说明 简易购物车功能包含以下交互&#…

Datawhale AI冬令营(第二期)动手学AI Agent task2--学Prompt工程,优化Agent效果

目录 如何写好Prompt? 工具包神器1:Prompt框架——CO-STAR 框架 工具包神器2:Prompt结构优化 工具包神器3:引入案例 案例:构建虚拟女友小冰 1. 按照 CO-STAR框架 梳理目标 2. 撰写Prompt 3. 制作对话生成应用&…

SpringBoot整合springmvc

文章目录 1.SpringMVC的自动管理1.1中央转发器1.1.1Spring boot配置多个DispatcherServlet 1.2控制器1.2.1找到启动类的位置1.2.1.1SpringApplication.run()1.2.1.2SpringApplication 构造方法1.2.1.3deduceMainApplicationClass() 1.2.2ComponentScan 注解 1.3视图解析器自动管…

常见的排序算法过程和比较分析

比较分析 排序类别排序算法时间复杂度(最好)时间复杂度(最坏)时间复杂度(平均)辅助空间复杂度稳定性插入排序直接插入排序O(n)O(n)O(n)O(1)稳定插入排序折半插入排序O(n)O(n)O(n)O(1)稳定插入排序希尔排序…

[MySQL报错]关于发生net start mysql 服务无法启动,服务没有报告任何错误的五种解决方案。

咋直接进入主题。 我遇到的问题是net start mysql 服务无法启动,服务没有报告任何错误 其问题出在哪里呢 一.ini文件配置问题 在于你没有给你下载好的mysql文件中配置.ini文件。 该如何配置呢。那就是先在文件夹中创建一个文本文件,把下面内容复制进去…

Unity网络通信相关

Socket 通信一张图搞定 谁提供服务谁绑定端口,建立Listener,写Host

小波与傅里叶变换在去噪效果上的对比分析-附Matlab源程序

👨‍🎓 博主简介:博士研究生 🔬 超级学长:超级学长实验室(提供各种程序开发、实验复现与论文指导) 📧 个人邮箱:easy_optics126.com 🕮 目 录 摘要一、…

如何利用 ClickHouse 实现高级分析:MySQL 到 ClickHouse 实时数据同步指南

在数据驱动的时代,企业必须依靠先进的数据分析能力来提升竞争力。随着数据量的激增和业务需求的复杂化,传统的关系型数据库已经无法满足高效处理和实时分析的需求。ClickHouse 作为一款高性能的列式数据库,凭借其卓越的查询性能和可扩展性&am…

计算机网络 (12)物理层下面的传输媒体

前言 计算机网络物理层下面的传输媒体是计算机网络设备之间的物理通路,也称为传输介质或传输媒介,并不包含在计算机网络体系结构中,而是处于物理层之下。 一、传输媒体的分类 导向型媒体:电磁波被导引沿着固体媒体传播。常见的导向…

rouyi(前后端分离版本)配置

从gitee上下载,复制下载地址,到 点击Clone,下载完成, 先运行后端,在运行前端 运行后端: 1.配置数据库,在Navicat软件中,连接->mysql->名字自己起(rouyi-vue-blog),用户名roo…

深度学习在光学成像中是如何发挥作用的?

深度学习在光学成像中的作用主要体现在以下几个方面: 1. **图像重建和去模糊**:深度学习可以通过优化图像重建算法来处理模糊图像或降噪,改善成像质量。这涉及到从低分辨率图像生成高分辨率图像,突破传统光学系统的分辨率限制。 …

svn不能添加.a文件

解决办法 在home目录下有一个.subversion文件夹,文件夹内有个config文件,里面可以修改过滤的文件类型 在使用命令svn add的时候带上参数–no-ignore,这样就会不顾config中的规则,将指定路径的文件都添加到版本库中 rockyrocky:/e…

【蓝桥杯选拔赛真题87】python输出字符串 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python输出字符串 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python输出字符串 第十五届蓝桥杯青少年组python比赛选拔赛真题详细解析…

基于SpringBoot的题库管理系统的设计与实现(源码+SQL+LW+部署讲解)

文章目录 摘 要1. 第1章 选题背景及研究意义1.1 选题背景1.2 研究意义1.3 论文结构安排 2. 第2章 相关开发技术2.1 前端技术2.2 后端技术2.3 数据库技术 3. 第3章 可行性及需求分析3.1 可行性分析3.2 系统需求分析 4. 第4章 系统概要设计4.1 系统功能模块设计4.2 数据库设计 5.…

【系统配置】3种方式修改用户登录显示名|统信|麒麟|方德

原文链接:【系统配置】3种方式修改用户登录显示名|统信|麒麟|方德 Hello,大家好啊!今天给大家带来一篇关于 通过修改 /etc/passwd 文件、usermod 命令,以及图形化界面三种方式修改用户登录名 的…

Github - 如何提交一个带有“verified”标识的commit

Github - 如何提交一个带有“verified”标识的commit 前言(Why) 今天在Github上浏览某项目的commit记录的时候发现,有的commit记录带有verified绿色标识,有的带有橘色的Unverified标识,还有的什么都不显示。 既然我是根正苗红的作者(bushi)…

操作系统课后题总复习

目录 一、第一章 1.1填空题 1.2单项选择题 1.3多项选择题 1.4判断题 1.5名词解释 1.6简答题 二、第二章 2.1填空题 2.2单项选择题 2.3 多项选择题 2.4判断题 2.5名词解释 2.6简答题 三、第三章 3.1填空题 3.2单项选择题 3.3多项选择题 3.4判断题 3.5名词解…