ECDH算法
DH算法:
蒜老大和油大叔想秘密研制新菜式,但是因为特殊原因,两人不能直接见面。通过以下图示方法分别处理食材,然后让外卖小哥跑腿送给对方,然后二次加工,做出新菜式,这样隐秘安全的研制了新菜式。在DH算法中,排骨是公开的公钥,蒜是蒜老大的私人密钥,油是油大叔的私人密钥。黑客没有密钥,即使他捕获了中间传递的公钥也不知道如何处理排骨。这样就保证了安全性。
ECDH密钥协商算法
它是ECC算法和DH密钥交换原理结合使用,用于密钥磋商。交换双方可以在不共享任何秘密的情况下协商出一个密钥。ECC是建立在基于椭圆曲线的离散对数问题上的密码体制。
在ECC的加持下,黑客通过传递的公钥破解密钥的难度会极大地增加,几乎不可能反向解出密钥。
这里不详细解释椭圆曲线的原理。
提示:
在 这次小实验中我没有生成P,并进行模运算。两端生成的G点相同,所以只互相发送公钥。
实现 ECDH
三步走:
- 学习ECDH算法,了解并使用BC库,在一个Java程序中实现ECDH密钥计算,生成相同的共享密钥
- 使用netty框架搭建项目,模拟ECDH算法交换密钥的流程。
- 在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对象。
基本流程:
- 启动服务端(Bob)、客户端(Alice)建立连接
- Alice发送生成的公钥Qa给Bob
- Bob收到Qa,计算共享密钥,打印输出
- Bob发送公钥Qb
- 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算法期间的小小实验,实验过程很粗糙,有什么不严谨的地方请见谅,我会细心听取您的教训。期待您的宝贵意见。
特别感谢:我的朋友森哥