java使用ecdh密钥协商

本文详细介绍了如何在Java中使用ECDH(椭圆曲线 Diffie-Hellman)进行密钥协商,以便双方可以安全地生成共享密钥用于AES加密。示例代码展示了如何从XY坐标点表示的公私钥生成密钥,并提供了生成EC密钥对的方法。文章强调了Java对接C语言时使用XY坐标点表示法的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目中刚好用到了ecdh进行密钥协商. java系列的文章比较少.记录下

首先密钥协商的大致过程:

  • 双方A,B都使用EC算法(椭圆曲线算法)生成对应的公私钥.即A_PUB,A_PRI,B_PUB,B_PRI
  • A把自己的公钥A_PUB传递给B, B把公钥B_PUB传递给A
  • A用A_PRI和B_PUB 可以通过ecdh算法算出来一个密钥secret;
  • B同样可以使用B_PRI和A_PUB 算出来同一个密钥secret;
  • 这样AB之间就可以用这个secret 进行愉快的加解密了;

先声明一下, ec算法的密钥可以用XY坐标点表示, 也可以用Key.getEncoded()的字符串表示, 本文采用的更复杂的XY坐标点表示法, 因为是java语言对接的C语言,传过来就是XY坐标点表示法; 想用getEncoded的话 需要你自行百度; 应该也不难

//参考公私钥
    public static final String A_PUBLIC_KEY_HEX =
            "04939859a19e9bdc2989cae7915857f8186a72ff03563b463ec1f81fd7064ea1b9694f56c10e76a583a1d6a78226ad005872c0ef2279bd8b84a0c9e6072aad12d9";
    public static final String A_PRIVATE_KEY_HEX =
            "38ca6cb7c6bbdc43e9f1acd53d17da4d797a3881c0d047038d2c49eec75b3b49";
    public static final String B_PUBLIC_KEY_HEX =
            "046c6a8a366e15db90c91c37fdcf43808f79d110c9a8b3a4e2657343a7b904c3b26d4019f8af58d12e4dba331a786e61a7b76d2a99448bdf309cfbc7fe1eab1e32";
    public static final String B_PRIVATE_KEY_HEX =
            "db4f1b7c14c84fe5c654b889fb657576aebbd4cbfa4b2098bacf14e3eab86285";



废话不多说,开始撸代码

   //椭圆曲线名称,  标准的曲线有很多种, 这里选用secp256k1这个曲线.  两方要提前约定好
   public static final String ecCurveName = "secp256k1";

   
 /**
     * 通过ecdh密钥协商, 自身的私钥+对方的公钥,生成共同的密钥,用于aes加密
     * <p>
     * 密钥格式,base64/16进制都支持
     *
     * @param myPriHex
     * @param othPubHex
     * @return
     */
    public static String getCommonSecretFromMyPriAndOthPub(String myPriHex, String othPubHex) throws Exception {
        PrivateKey myPrivateKey = getPrivateKeyFromS(myPriHex);
        //分解, 获取坐标
        Map<String, String> XYMap = getHexXY(othPubHex);
        PublicKey othPublicKey = getPublicKeyFromXY(XYMap.get("X"), XYMap.get("Y"));
        KeyAgreement ecdhA = KeyAgreement.getInstance("ECDH");
        ecdhA.init(myPrivateKey);
        ecdhA.doPhase(othPublicKey, true);
        String commonSecret = HexUtil.encodeHexStr(ecdhA.generateSecret());
        log.info("ecdh协商后的密钥16进制:[{}]", commonSecret);
        //System.out.println("ecdh协商后的密钥16进制:"+ commonSecret);
        String substring1 = commonSecret.substring(commonSecret.length() / 2);
        log.info("截取后的密钥:[{}]", substring1);
        return substring1;
    }

    /**
     * ec 椭圆曲线算法中
     * 从私钥的坐标获取 ECPublicKey对象
     *
     * @param hexPrivateS 16进制的坐标点
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidParameterSpecException
     * @throws InvalidKeySpecException
     */
    public static ECPrivateKey getPrivateKeyFromS(String hexPrivateS) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            InvalidParameterSpecException,
            InvalidKeySpecException {
        byte[] privateKeyS = HexUtil.decodeHex(hexPrivateS);
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
        parameters.init(new ECGenParameterSpec(ecCurveName));
        ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
        ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyS), ecParameters);
        KeyFactory kf = KeyFactory.getInstance("EC");
        ECPrivateKey privateKey = (ECPrivateKey) kf.generatePrivate(privateSpec);
        return privateKey;
    }

    /**
     * 解析公钥坐标,  04开头, 剩余应该是128长度,  切割前后64个,获取X和Y
     *
     * @param othPubHex
     * @return
     */
    private static Map<String, String> getHexXY(String othPubHex) {
        Map<String, String> hashMap = MapUtil.newHashMap();
        if (!(othPubHex.length() == 130 && othPubHex.startsWith("04"))) {
            log.error("othPubHex格式异常,需要04开头,长度为130,othPubHex:[{}]", othPubHex);
            throw new RuntimeException("othPubHex格式异常,需要04开头,长度为130");
        }
        String sub = StrUtil.sub(othPubHex, 2, othPubHex.length());
        String subX = sub.substring(0, sub.length() / 2);
        String subY = sub.substring(sub.length() / 2);

        hashMap.put("X", subX);
        hashMap.put("Y", subY);

        return hashMap;
    }


    /**
     * ec 椭圆曲线算法中
     * 从公钥的xy坐标获取 ECPublicKey对象
     *
     * @param hexPublicKeyX 16进制的x坐标点
     * @param hexPublicKeyY 16进制的y坐标点
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidParameterSpecException
     * @throws InvalidKeySpecException
     */
    public static ECPublicKey getPublicKeyFromXY(String hexPublicKeyX, String hexPublicKeyY) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            InvalidParameterSpecException,
            InvalidKeySpecException {
        byte[] publicKeyX = HexUtil.decodeHex(hexPublicKeyX);
        byte[] publicKeyY = HexUtil.decodeHex(hexPublicKeyY);
        ECPoint pubPoint = new ECPoint(new BigInteger(1, publicKeyX), new BigInteger(1, publicKeyY));
        AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
        parameters.init(new ECGenParameterSpec(ecCurveName));
        ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
        ECPublicKeySpec pubSpec = new ECPublicKeySpec(pubPoint, ecParameters);
        //ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyS), ecParameters);
        KeyFactory kf = KeyFactory.getInstance("EC");
        //ECPrivateKey privateKey = (ECPrivateKey) kf.generatePrivate(privateSpec);
        ECPublicKey publicKey = (ECPublicKey) kf.generatePublic(pubSpec);
        return publicKey;
    }


```java
//如果需要自己生成密钥字符串
   /**
     * 生成EC密钥对
     *
     * @param ecCurveName 椭圆曲线 可选为:secp256k1
     *                    type:hex/base64
     * @return
     */
    public static KeyPairStrDto generatorEcKeyPair(String ecCurveName, String type) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
            if (StrUtil.isNotBlank(ecCurveName)) {
                ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(ecCurveName);
                keyPairGenerator.initialize(ecGenParameterSpec);
            }
            KeyPair ecKeyPair = keyPairGenerator.generateKeyPair();
            ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic();
            String s1 = ecPublicKey.getW().getAffineX().toString(16);
            String s2 = ecPublicKey.getW().getAffineY().toString(16);
            System.out.println("公钥:04" + s1 + s2);
            ECPrivateKey ecPrivateKey = (ECPrivateKey) ecKeyPair.getPrivate();
            String s = ecPrivateKey.getS().toString(16);
            System.out.println("私钥:" + s);


            String publicKeyStr = new String(Base64.getEncoder().encode(ecPublicKey.getEncoded()));
            String privateKeyStr = new String(Base64.getEncoder().encode(ecPrivateKey.getEncoded()));

            //使用16进制的
            if ("hex".equals(type)) {
                publicKeyStr = HexUtil.encodeHexStr(ecPublicKey.getEncoded());
                privateKeyStr = HexUtil.encodeHexStr(ecPrivateKey.getEncoded());
            }

            return new KeyPairStrDto(publicKeyStr, privateKeyStr);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return null;
    }


里面用到了hutool工具类
```java
        <dependencies>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.8.1</version>
            </dependency>
        </dependencies>

ok
打完收工

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值