当前位置: 首页 > 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…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...