简介
对称加解密算法都必要一把秘钥,但是很多情况下,互联网环境不适合传输这把对称密码,有密钥泄露的风险,为了解决这个问题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向量为同一值
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |