当前位置: 首页 > news >正文

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 转换为 BigIntegerBigInteger 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 加载 ECPrivateKeyprivate 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向量为同一值

相关文章:

SpringBoot集成ECDH密钥交换

简介 对称加解密算法都需要一把秘钥,但是很多情况下,互联网环境不适合传输这把对称密码,有密钥泄露的风险,为了解决这个问题ECDH密钥交换应运而生 EC:Elliptic Curve——椭圆曲线,生成密钥的方法 DH&…...

python文件操作相关(excel)

python文件操作相关(excel) 1. openpyxl 库openpyxl其他用法创建与删除操作单元格追加数据格式化单元格合并单元格插入图片公式打印设置保护工作表其他功能 2. pandas 库3. xlrd 和 xlwt 库4. xlsxwriter 库5. pyxlsb 库应用场景参考资料 在 Python 中&a…...

探索React与Microi吾码的完美结合:快速搭建项目,低代码便捷开发教程

一、摘要 在当今的数字化时代,软件开发就像是一场探险,每个开发者都是探险家,探索着代码的奥秘。React作为前端开发的领军框架,其组件化和高效的渲染机制为开发者提供了强大的工具。而Microi吾码低代码平台的出现,则为…...

【面试系列】深入浅出 Spring Boot

熟悉SpringBoot,对常用注解、自动装配原理、Jar启动流程、自定义Starter有一定的理解; 面试题 Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?Spring Boot的自动配置原理是什么?你如何理解 Spring Boot 配置…...

@colyseus/social 模块详解

@colyseus/social 模块介绍 @colyseus/social 是一个适用于 Colyseus 游戏框架的扩展模块,提供了社交功能的支持,帮助开发者在多人游戏中快速实现玩家之间的社交互动。它主要提供了玩家账户管理、好友系统、好友请求、组队和聊天功能等,旨在简化游戏中社交功能的实现。 核心…...

石岩路边理发好去处

周末带娃去罗租公园玩,罗租公园旁边就是百佳华和如意豪庭小区,发现如意豪庭小区对面挺多路边理发摊点 理发摊点聚焦在这里的原因是刚好前面城管来了暂时避避,例如还有一个阿姨剪到一半就跟着过来。这里的城管只是拍了一处没有摊位的地方&…...

ROS 2中的DDS中间件

文章目录 一、简介二、默认支持的 DDS (Data Distribution Service) 实现三、切换DDS实现小结 一、简介 中间件是一个软件层,通常用于连接不同的应用程序、服务或系统,以便它们能够相互通信和交换数据。中间件并不直接向用户暴露,而是在系统…...

「下载」智慧文旅运营综合平台解决方案:整体架构,核心功能设计

智慧文旅运营综合平台,旨在通过集成大数据、云计算、物联网、人工智能等先进技术,为景区、旅游企业及相关管理机构提供一站式的智慧化运营服务。 智慧文旅运营综合平台不仅能够提升游客的游览体验,还能帮助景区管理者实现资源的优化配置和业务…...

NVR小程序接入平台EasyNVR使用FFmpeg取流时提示错误是什么原因呢?

在视频监控系统中,FFmpeg常用于从各种源(如摄像头、文件、网络流等)获取流媒体数据,这个过程通常称为“取流”。 在EasyNVR平台中,使用FFmpeg取流是一种常见的操作。FFmpeg作为一款强大的开源多媒体处理工具&#xff…...

计算机因进程结束导致白屏

问题场景: 计算机卡顿利用(右击计算机桌面底部任务栏->打开任务管理器->结束任务->或进程被意外结束导致白屏) 问题描述 白屏 原因分析: 在结束进程时,导致 文件资源管理器 进程崩溃。 解决方案&#xf…...

OpenGL入门最后一章观察矩阵(照相机)

前面的一篇文章笔者向大家介绍了模型变化矩阵,投影矩阵。现在只剩下最后一个观察矩阵没有和大家讲了。此片文章就为大家介绍OpenGL入门篇的最后一个内容。 观察矩阵 前面的篇章当中,我们看到了即使没有观察矩阵,我们也能对绘制出来的模型有一…...

ES6中定义私有属性详解

在ES6中,定义私有属性的方式相对传统的JavaScript有所不同。ES6并没有提供直接的语法来定义私有属性,但可以通过几种方法间接实现私有属性。 1. 使用Symbol来模拟私有属性 Symbol是一种新的数据类型,可以作为对象的键,并且它的值…...

工业5G路由器让无人机数据传输 “飞” 起来

无人机上搭载5G通信模块,该模块与工业5G路由器通过5G网络建立连接。无人机的飞控系统、传感器以及摄像头等设备采集到的数据,如飞行姿态、高度、速度、环境图像、温度湿度等,经过编码、加密、调制等处理后转换为适合5G网络传输的信号形式。 …...

面试经典150题——滑动窗口

文章目录 1、长度最小的子数组1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、无重复字符的最长子串2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、串联所有单词的子串3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、最小覆盖子串4.1 题目链接4.2 题目描…...

MiFlash 线刷工具下载合集

MiFlash 线刷工具下载合集 MiFlash 线刷工具下载合集 – MIUI历史版本相较于小米助手的刷机功能,线刷还是偏好使用 MiFlash。特点是界面简单纯粹,有自定义高级选项,可以选择刷机不上 BL 锁,自定义刷机脚本,EDL 刷机模…...

【MySQL高级】第1-4章

第1章 存储过程 1.1 什么是存储过程? 存储过程可称为过程化SQL语言,是在普通SQL语句的基础上增加了编程语言的特点,把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中,通过逻辑判断、循环等操作实现复杂计算的程序语言。 换…...

课程设计项目之基于Python实现围棋游戏代码

项目介绍 游戏进去默认为九路玩法,当然也可以选择十三路或是十九路玩法 使用pycharam打开项目,pip安装模块并引用,然后运行即可, 代码每行都有详细的注释,可以做课程设计或者毕业设计项目参考 效果预览 源码下载 h…...

uni-app tab 双击事件监听

1、data中定义属性,用于临时记录点击次数 tabClick: {touchNum: 0 },2、添加页面事件监听方法 onTabItemTap(e) {this.tabClick.touchNumsetTimeout(()>{if(this.tabClick.touchNum > 2){// 双击执行代码区}this.tabClick.touchNum 0}, 250) },个人博客&am…...

如何在Maxscript脚本中检查磁盘可用空间?

在我们实际工作中,有时需要在工作开始之前检查磁盘的可用空间,比如渲染。 当然,我们可以人工很容易查看电脑中各个磁盘的空间使用情况,但是,如果是Maxscript插件完成的工作,那么如何才能实现其工作之前对磁…...

pytorch梯度上下文管理器介绍

PyTorch 提供了多种梯度上下文管理器,用于控制自动梯度计算 (autograd) 的行为。这些管理器在训练、推理和特殊需求场景中非常有用,可以通过显式地启用或禁用梯度计算,优化性能和内存使用。 主要梯度上下文管理器 torch.no_grad(): 功能&am…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

Selenium常用函数介绍

目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...