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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#渗透测试#红蓝攻防#红队打点web服务突破口总结01

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…

25年1月更新。Windows 上搭建 Python 开发环境:PyCharm 安装全攻略(文中有安装包不用官网下载)

python环境没有安装的可以点击这里先安装好python环境,python环境安装教程 安装 PyCharm IDE 获取 PyCharm PyCharm 提供两种主要版本——社区版(免费)和专业版(付费)。对于初学者和个人开发者而言,社区…

js 文档注释

在 JavaScript 中,文档注释(也叫 JSDoc 注释)是一种用于为代码提供结构化说明的注释方式。JSDoc 注释通常用于生成 API 文档、提高代码可读性,并为 IDE 提供智能提示。下面是如何在 JavaScript 中编写文档注释的基本指南。 基本语…

JavaScript中Map与Object的区别

在JavaScript中,Map和Object是用于存储键值对数据的两种不同的数据结构(Map是ES6新增的数据结构),它们在构造方式、键的类型以及原型继承等方面存在区别。 一、主要区别 1.构造方式 Map:Map只能通过构造函数new Map…

【开源社区openEuler实践】compass-ci

title: 走进 Compass-CI:持续集成与测试的智能领航员 date: ‘2024-12-30’ category: blog tags: Compass-CI持续集成自动化测试软件开发流程 sig: CICD archives: ‘2024-12’ author:way_back summary: Compass-CI 作为一款强大的持续集成与测试平台&#xff0c…

双目视觉:reprojectImageTo3D函数

前言 reprojectImageTo3D 是 OpenCV 中用于从视差图生成三维点云的函数。它的原理是利用视差图和相机的校准参数,通过三角测量法,计算每个像素对应的三维坐标。以下内容根据源码分析所写,觉得可以的话,点赞收藏哈!&am…

pyspark执行group by操作

前情提要 在处理亿级别数据时,常常输入是hive表,因此需要在pypark流程中引入一些场景sql操作,其中group by就是比较常见的操作。 基础步骤 创建SparkSession:通过enableHiveSupport()方法启用Hive支持,确保能够访问…

Megatron - LM; DistBelief; Mesh - Tensorflow

DistBelief、Mesh - Tensorflow、Megatron - LM DistBelief、Mesh - Tensorflow、Megatron - LM 均是在深度学习模型并行训练领域发挥重要作用的框架或技术: DistBelief:是早期支持模型并行的深度神经网络框架之一,采用参数服务器架构实现计算节点之间的输出同步。在深度神…

Spring Boot(快速上手)

Spring Boot 零、环境配置 1. 创建项目 2. 热部署 添加依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional> </dependency&…

30.Marshal.AllocHGlobal C#例子

Marshal.AllocHGlobal 这个代码为IntPtr分配非托管内存。当访问非托管代码时有时候会需要一个指向非托管内存的指针。因此需要对IntPtr分配非托管内存。分配的大小可以是8个字节。不够可以写更大。 用完要用Free释放内存&#xff0c;否则这个非托管内存不会被C#自动释放。 u…

TTL 传输中过期问题定位

问题&#xff1a; 工作环境中有一个acap的环境&#xff0c;ac的wan口ip是192.168.186.195/24&#xff0c;ac上lan上有vlan205&#xff0c;其ip子接口地址192.168.205.1/24&#xff0c;ac采用非nat模式&#xff0c;而是路由模式&#xff0c;在上级路由器上有192.168.205.0/24指向…

MySQL中distinct和group by去重的区别

MySQL中distinct和group by去重的区别 在MySQL中&#xff0c;我们经常需要对查询结果进行去重&#xff0c;而DISTINCT和GROUP BY是实现这一功能的两种常见方法。虽然它们在很多情况下可以互换使用&#xff0c;但它们之间还是存在一些差异的。接下来&#xff0c;我们将通过创建测…

AI 驱动研发模式升级,蓝凌软件探索效率提升之道

深圳市蓝凌软件股份有限公司&#xff08;以下简称蓝凌软件&#xff09;&#xff0c;自2001年成立以来&#xff0c;一直走在数智化办公领域的前沿。作为国家认定的高新技术企业、知识管理国家标准的参编者以及信创供应商10强之一&#xff0c;蓝凌软件始终以“让组织更智慧”为使…

GPU 进阶笔记(四):NVIDIA GH200 芯片、服务器及集群组网

大家读完觉得有意义记得关注和点赞&#xff01;&#xff01;&#xff01; 1 传统原厂 GPU 服务器&#xff1a;Intel/AMD x86 CPU NVIDIA GPU2 新一代原厂 GPU 服务器&#xff1a;NVIDIA CPU NVIDIA GPU 2.1 CPU 芯片&#xff1a;Grace (ARM)2.2 GPU 芯片&#xff1a;Hopper/B…

SpringMVC(二)原理

目录 一、配置Maven&#xff08;为了提升速度&#xff09; 二、流程&&原理 SpringMVC中心控制器 完整流程&#xff1a; 一、配置Maven&#xff08;为了提升速度&#xff09; 在SpringMVC&#xff08;一&#xff09;配置-CSDN博客的配置中&#xff0c;导入Maven会非…

jest使用__mocks__设置模拟函数不生效 解决方案

模拟文件 // __mocks__/axios.js const axios jest.fn(); axios.get jest.fn(); axios.get.mockResolvedValue({data: {undoList: [get data],}, }); export default axios; 测试文件 jest.mock(axios); import Axios from axios;test(mytest, () > {console.log("…

在K8S中,节点状态notReady如何排查?

在kubernetes集群中&#xff0c;当一个节点&#xff08;Node&#xff09;的状态变为NotReady时&#xff0c;意味着该节点可能无法运行Pod或不能正确相应kubernetes控制平面。排查NotReady节点通常涉及以下步骤&#xff1a; 1. 获取基本信息 使用kubectl命令行工具获取节点状态…

python3中推导式:列表推导式

一. 简介 python中的推导式&#xff0c;是一种简洁创建列表&#xff0c;字典&#xff0c;集合&#xff0c;元组等数据类型的方式。本文简单来学习一下&#xff0c;python中的列表推导式。 Python 支持各种数据结构的推导式&#xff1a;列表(list)推导式&#xff0c;字典(dict…

springboot集成qq邮箱服务

springboot集成qq邮箱服务 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域&#xff0c;如果当前账号未开启的话自己手动开启。 1.3 获取授权码 进入上图中的【管理服务】后&#xff1a;在【安全设置中生成授权码】,也可以直接点击【继续生成…

UE4.27 Android环境下获取手机电量

获取电量方法 使用的方法时FAndroidMisc::GetBatteryLevel(); 出现的问题 但是在电脑上编译时发现&#xff0c;会发现编译无法通过。 因为安卓环境下编译时&#xff0c;包含 #include "Android/AndroidPlatformMisc.h" 头文件是可以正常链接的&#xff0c;但在电…