基于Netty框架模拟ECDH算法的基本过程

ECDH算法

DH算法:

蒜老大和油大叔想秘密研制新菜式,但是因为特殊原因,两人不能直接见面。通过以下图示方法分别处理食材,然后让外卖小哥跑腿送给对方,然后二次加工,做出新菜式,这样隐秘安全的研制了新菜式。在DH算法中,排骨是公开的公钥,蒜是蒜老大的私人密钥,油是油大叔的私人密钥。黑客没有密钥,即使他捕获了中间传递的公钥也不知道如何处理排骨。这样就保证了安全性。

ECDH密钥协商算法

它是ECC算法和DH密钥交换原理结合使用,用于密钥磋商。交换双方可以在不共享任何秘密的情况下协商出一个密钥。ECC是建立在基于椭圆曲线的离散对数问题上的密码体制。

在ECC的加持下,黑客通过传递的公钥破解密钥的难度会极大地增加,几乎不可能反向解出密钥。

这里不详细解释椭圆曲线的原理。

提示: 

在 这次小实验中我没有生成P,并进行模运算。两端生成的G点相同,所以只互相发送公钥。

实现 ECDH

三步走:

  1. 学习ECDH算法,了解并使用BC库,在一个Java程序中实现ECDH密钥计算,生成相同的共享密钥
  2. 使用netty框架搭建项目,模拟ECDH算法交换密钥的流程。
  3. 在2的基础上实现ECDH算法

第一步

这里使用BC库指定使用ECDH算法生成密钥对和G,然后“粗糙”地计算共享公钥。最后打印G点、各自的私钥公钥、共享密钥,最后比较共享密钥是否相同,相同则试验成功。

这里最初结果不相同,后来观察BC库的代码发现我没有用normalize(),这是一个规范化函数。在点乘运算时添加normalize()

public class ECC_DH {
    public static void main(String[] args) throws Exception {
        // 添加 Bouncy Castle 作为安全提供程序
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // 选择椭圆曲线参数
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
        // 生成密钥对
        //创建一个KeyPairGenerator对象,使用参数"ECDH"和"BC"来指定生成ECDH密钥对的算法和提供者
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
        //初始化密钥对生成器
        keyPairGenerator.initialize(ecSpec);
        //KeyPair keyPairA = keyPairGenerator.generateKeyPair();
        //KeyPair keyPairB = keyPairGenerator.generateKeyPair();
        // 获取椭圆曲线参数
        //ECCurve curve = ecSpec.getCurve();
        ECPoint G = ecSpec.getG();
        BigInteger n = ecSpec.getN();
        // Alice 和 Bob 分别生成私钥和公钥
        BigInteger dA = new BigInteger(n.bitLength(), new SecureRandom()).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE); // 1 <= dA < n
        BigInteger dB = new BigInteger(n.bitLength(), new SecureRandom()).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE); // 1 <= dB < n

        ECPoint Qa = G.multiply(dA).normalize(); // 公钥 QA = dA * G
        ECPoint Qb = G.multiply(dB).normalize(); // 公钥 QB = dB * G

        // Alice 使用自己的私钥和 Bob 的公钥计算共享密钥
        ECPoint sharedKeyA = Qb.multiply(dA).normalize(); // 共享密钥 = dB * QA

        // Bob 使用自己的私钥和 Alice 的公钥计算共享密钥
        ECPoint sharedKeyB = Qa.multiply(dB).normalize(); // 共享密钥 = dA * QB

        System.out.println("Alice's key pair: " + dA);
        System.out.println("Bob's key pair:"+ dB);
        System.out.println("G="+G);
        System.out.println("\n");
        System.out.println("A的公钥:"+ Qa);
        System.out.println("B的公钥:"+ Qb);

        BigInteger a=sharedKeyA.getAffineXCoord().toBigInteger();
        BigInteger b=sharedKeyB.getAffineXCoord().toBigInteger();
        //输出共享密钥
        System.out.println("Shared key A: " + a);
        System.out.println("Shared key B: " + b);
        System.out.println("比较结果:" + sharedKeyA.equals(sharedKeyB));
    }
}

 第二步

使用netty框架搭建一个客户端(Alice)和一个服务端(Bob)。他们分别生成一个两位数,然后交换数字,让自己的数字与对方的数字相乘,输出结果。最后检查结果是否相同。

第三步

在第二步的基础上修改AliceHandler和BobHandler,加入ECDH算法,比如生成密钥,点乘计算。

这里遇见了几个问题,其中最重要的是传递公钥,公钥是ECPoint对象(BigInteger型变量),第二步传递的Int型变量,这里不能简单的把Int改为BigInteger。如何发送和接受公钥成了问题。

解决方法是在BC库中找到了编码解码。发送公钥时把ECPoint转换文字节数组编码,接收时将字节数组转换为16进制字符串,再将16进制字符串解码为字节数组,最后将字节数组解码为ECPoint对象。

基本流程:

  1. 启动服务端(Bob)、客户端(Alice)建立连接
  2. Alice发送生成的公钥Qa给Bob
  3. Bob收到Qa,计算共享密钥,打印输出
  4. Bob发送公钥Qb
  5. Alice接受Qb,计算共享密钥
public class AliceHandler extends ChannelInboundHandlerAdapter {
    private BigInteger dA;
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ECPoint Qa;
        try {
            Qa = ECDH();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //创建ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        //将ECPoint转换为字节数组
        byte[] QaBytes = Qa.getEncoded(true);//使用压缩格式编码
        buffer.writeBytes(QaBytes);
        ctx.writeAndFlush(buffer);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buffer = (ByteBuf) msg;
        //接受B的公钥Qb
        byte[] receivedBytes = new byte[buffer.readableBytes()];
        buffer.readBytes(receivedBytes);
        // 解码字节数组为ECPoint对象
        // 将字节数组转换为十六进制字符串
        String receivedHex = Hex.toHexString(receivedBytes);
        // 将十六进制字符串转换为字节数组
        byte[] decodedBytes = Hex.decode(receivedHex);
        // 选择椭圆曲线参数
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
        // 创建曲线对象
        ECCurve curve = ecSpec.getCurve();
        // 解码字节数组为ECPoint对象
        ECPoint receivedNum = curve.decodePoint(decodedBytes);
        //计算共享密钥
        ECPoint sharedKeyA = receivedNum.multiply(dA).normalize();
        BigInteger result = sharedKeyA.getAffineXCoord().toBigInteger();
        
        System.out.println("Bob公钥: " + receivedNum);
        System.out.println("Alice共享密钥: " + result);
        buffer.release();
        ctx.close();
    }
    public ECPoint ECDH() throws Exception{
        // 添加 Bouncy Castle 作为安全提供程序
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        // 选择椭圆曲线参数
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
        // 生成密钥对
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
        //初始化密钥对生成器
        keyPairGenerator.initialize(ecSpec);
        ECPoint G = ecSpec.getG();
        BigInteger n = ecSpec.getN();
        //私钥dA
        dA = new BigInteger(n.bitLength(),new SecureRandom()).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
        //公钥Qa
        return G.multiply(dA).normalize();
    }
}
public class BobHandler extends ChannelInboundHandlerAdapter {
    private BigInteger dB;
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buffer = (ByteBuf) msg;
        //接受公钥Qa
        byte[] receivedBytes = new byte[buffer.readableBytes()];
        buffer.readBytes(receivedBytes);
        // 解码字节数组为ECPoint对象
        // 将字节数组转换为十六进制字符串
        String receivedHex = Hex.toHexString(receivedBytes);
        // 将十六进制字符串转换为字节数组
        byte[] decodedBytes = Hex.decode(receivedHex);
        // 选择椭圆曲线参数
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
        // 创建曲线对象
        ECCurve curve = ecSpec.getCurve();
        // 解码字节数组为ECPoint对象
        ECPoint receivedPoint = curve.decodePoint(decodedBytes);
        //生成密钥dB
        ECPoint Qb;//将‘ECPoint Qb’引入作用域
        try {
            Qb = EC();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //计算共享公钥
        ECPoint sharedKeyB = receivedPoint.multiply(dB).normalize();
        BigInteger result = sharedKeyB.getAffineXCoord().toBigInteger();
        System.out.println("Alice的公钥" + receivedPoint);
        System.out.println("Bob共享密钥: " + result);
        buffer.clear();
        //发送公钥
        //将ECPoint转换为字节数组
        byte[] QbBytes = Qb.getEncoded(true);//使用压缩格式编码
        buffer.writeBytes(QbBytes);
        ctx.writeAndFlush(buffer);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)  {
        ctx.flush();
    }
    public ECPoint EC() throws Exception{
        // 添加 Bouncy Castle 作为安全提供程序
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        // 选择椭圆曲线参数
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
        // 生成密钥对
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
        //初始化密钥对生成器
        keyPairGenerator.initialize(ecSpec);
        ECPoint G = ecSpec.getG();
        BigInteger n = ecSpec.getN();
        //私钥dA
        dB = new BigInteger(n.bitLength(),new SecureRandom()).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
        ECPoint Qb = G.multiply(dB).normalize();
        return Qb;
    }
}

最后

此实验只是我学习ECDH算法期间的小小实验,实验过程很粗糙,有什么不严谨的地方请见谅,我会细心听取您的教训。期待您的宝贵意见。

特别感谢:我的朋友森哥

猜你喜欢

转载自blog.csdn.net/m0_74137767/article/details/134461518