背景
该协议基于离散对数问题的困难性来实现密钥交换,涉及到一些公开的参数和双方各自的秘密参数。通常假设有两个通信参与者,分别记为 Alice 和 Bob,他们要在不安全的网络环境下协商出一个共享的密钥,且整个过程中第三方很难通过截获的信息推算出这个密钥。
步骤
- 选择公开参数:
首先,通信双方(Alice和Bob)共同选择一个大素数p
以及p的一个本原根g
,这两个参数p和g是公开的,任何参与通信或者潜在的窃听者都可以知道这两个值,它们可以通过事先约定或者在公开渠道进行公布等方式确定下来。 - 生成各自的秘密参数:
Alice选择一个秘密的整数a
,且0<a<p-1;Bob选择一个个秘密的整数b
,同样满足0<b<p-1。这里的a和b是各自严格保密的,只有Alice知道a,只有Bcb 知道b。 - 计算公开值并交换:
Alice根据自己选择的秘密整数a
以及公开参数p
和g
,计算出公开值A
=ga mod p,然后把A发送给Bob;与此同时,Bob根据自己的秘密整数b
以及公开参数p
和g
,计算掉出公开值B
= gb mod p,然后将B发送给Alice。在这个过程中,即便有窃听者截获了A和B的值,由于离散对数问题的困难性(很难由A反向推算出a,由B反向推算出b),窃听者很难获取到关键的秘密信息。 - 计算共享密钥:
Alice在收到Bob发送过来的B
后,利用自己的秘密整数a
和接收到的B
计算共享密钥,KA=Bamod p;Bob在收到Alice发送过来的A后,利用自己的秘密整数b和接收到的A计算共享密钥KB=Ab mod p。通过数学推导可以发现,KA和KB是相等的,即KA=KB= gab mod p。这个相等的值就是双方协商出来的共享密钥,后续双方就可以利用这个共享密钥进行对称加密等安全的通信操作。
代码
import java.util.HashSet;
import java.util.Set;
public class Diffie_Hellman密钥交换协议 {
public static void main(String[] args) {
/* 生成一个大素数p和它的本原根g */
int p = 997;
int g = PrimitiveRootsCalculator(p);
/* A和B各自生成一个秘密整数a和b,且a,b<p-1 */
int a = 5;
int b = 6;
/* A和B各自计算公开值A和B */
int A = fun(g, a, p);
int B = fun(g, b, p);
/* 计算A和B的共享密钥(即会话密钥) */
int Ka = fun(B, a, p);
int Kb = fun(A, b, p);
System.out.println("Ka = " + Ka + " Kb= " + Kb);
}
/* 计算一个数p的本原根 */
static int PrimitiveRootsCalculator(int p) {
/* 本原根满足 1< g < p */
int g = 2;
for (g = 2; g < p; g++) {
Set<Integer> residues = new HashSet<>();
/* 求p-1次模,看这p-1个模是否涵盖了1-p的所有数 */
for (int i = 1; i < p; i++) {
int residue = fun(g, i, p);
residues.add(residue);
}
if (residues.size() == p - 1) {
break;
}
}
return g;
}
/* 快速幂算法,求g的n次幂模p */
static int fun(int g, int n, int p) {
int res = 1;
g %= p;
while (n > 0) {
if ((n & 1) == 1) {
res = (res * g) % p;
}
g = (g * g) % p;
n >>= 1;
}
return res;
}
}
关于代码中的fun函数部分的算法解释,可以看我写的另一篇文章:快速指数算法和高阶求模溢出问题