1、DH密鑰交換概述
Diffie-Hellman由Whitfield Diffie和Martin Hellman在1976年公布的一種密鑰一致性算法。Diffie-Hellman是一種建立密鑰的方法,而不是加密方法。然而,它所產(chǎn)生的密鑰可用于加密、進(jìn)一步的密鑰管理或任何其它的加密方式。Diffie-Hellman密鑰交換算法及其優(yōu)化首次發(fā)表的公開密鑰算法出現(xiàn)在Diffie和Hellman的論文中,這篇影響深遠(yuǎn)的論文奠定了公開密鑰密碼編碼學(xué)。
2、DH密鑰交換算法原理
2.1、使用顏色形象描述
設(shè)想這樣一個(gè)場(chǎng)景,Alice(A)和Bob(B),他們想在不見面的情況下秘密約定出一種顏色,但他們互相溝通的信息都會(huì)被公開,應(yīng)該怎么辦呢?

秘密在于,顏色混合是一種“不可逆”的操作,當(dāng)雙方交換顏色時(shí),盡管我們知道他們交換的顏色都是由一份黃色和另一份其他顏色混合得到的,但我們還是無(wú)法或者很難得到他們的私密顏色。
2.2、數(shù)學(xué)算法
2.2.1 算法背景
乘方得逆運(yùn)算稱為對(duì)數(shù)運(yùn)算,比如已知 7^x = 49 那么可知 x=log(7,49)=2。 對(duì)數(shù)運(yùn)算非常容易,即使在數(shù)字很大的時(shí)候是,但如果是下面的情況 7^xmod13=8 。 求X的過(guò)程稱為“離散對(duì)數(shù)”,就不那么容易了,在數(shù)字很大時(shí)幾乎是一個(gè)不可能的運(yùn)算,而DH秘鑰交換就是利用了這種離散對(duì)數(shù)計(jì)算非常困難的特性來(lái)設(shè)計(jì)的。
2.2.2 取模運(yùn)算規(guī)律
公式里的mod是取模運(yùn)算,取模運(yùn)算有幾條基本的定律如下:
(a+b) mod P =(a mod P+b mod P) mod P
(a?b) mod P = (a mod P?b mod P) mod P
(a^b) mod P = (a mod P)^b) mod P
2.2.3 密鑰交換流程
根據(jù)上面的公式,可以推導(dǎo)出一個(gè)非常重要的公式。
(G^(a?b)) mod P=(G^a mod P)^b mod P=(G^b mod P)^a mod P
根據(jù)這個(gè)公式,我們可以向上面交換顏色那樣設(shè)計(jì)出一個(gè)秘密交換數(shù)字的流程出來(lái)。

最終兩個(gè)人得到的秘密數(shù)字都是g^(ab) mod p,而竊聽者僅從p,g,A,B四個(gè)公開信息,是無(wú)法得到這個(gè)秘密數(shù)字的!
2.2.4舉例說(shuō)明
第1步.愛麗絲與鮑伯協(xié)定使用p=23以及g=5.
第2步.愛麗絲選擇一個(gè)秘密整數(shù)a=6, 計(jì)算A = g^a mod p并發(fā)送給鮑伯。 A = 5^6 mod 23 = 8.
第3步.鮑伯選擇一個(gè)秘密整數(shù)b=15, 計(jì)算B = g^b mod p并發(fā)送給愛麗絲。
B = 5^15 mod 23 = 19.
第4步.愛麗絲計(jì)算
s = B a mod p
19^6 mod 23 = 2.
第5步.鮑伯計(jì)算s = A b mod p
8^15 mod 23 = 2.
3、DH密鑰交換算用途
可以用作對(duì)稱加密算法中,雙方約定的加密準(zhǔn)則的交換(對(duì)方的公鑰和自己的私鑰計(jì)算的到秘密整數(shù),可以作為雙方的加密準(zhǔn)則)。交換雙方可以在不共享任何秘密的情況下協(xié)商出一個(gè)密鑰。
4、中間人攻擊
由于密鑰交換本身并沒有提供通訊雙方的身份驗(yàn)證服務(wù),因此它很容易受到中間人攻擊。 一個(gè)中間人“丙”在信道的中央進(jìn)行兩次迪菲-赫爾曼密鑰交換,一次和甲,另一次和乙,就能夠成功的向甲假裝自己是乙,反之亦然。而攻擊者可以解密(讀取和存儲(chǔ))任何一個(gè)人的信息并重新加密信息,然后傳遞給另一個(gè)人。因此通常都需要一個(gè)能夠驗(yàn)證通訊雙方身份的機(jī)制來(lái)防止這類攻擊。
5、算法實(shí)現(xiàn)
5.1、JDK算法實(shí)現(xiàn)
package lzf.cipher.jdk;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
/**
* @author java小工匠
*/
public class JdkDHUtils {
public static final String ALGORITHM = "DH";
// 甲方初始化密鑰對(duì)
public static KeyPair initKey() {
try {
// 實(shí)例化密鑰對(duì)生成器
KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM);
// 初始化密鑰對(duì)生成器參數(shù) 默認(rèn)1024 512-1024之間64的倍數(shù)
generator.initialize(1024);
// 產(chǎn)生密鑰對(duì)
KeyPair keyPair = generator.genKeyPair();
return keyPair;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
// 乙方初始化密鑰生成對(duì)
public static KeyPair initKey(byte[] key) {
try {
// 公鑰從字節(jié)數(shù)組轉(zhuǎn)換為PublicKey
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
// 實(shí)例化密鑰工廠
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
// 還原甲方的公鑰
DHPublicKey dhPublicKey = (DHPublicKey) keyFactory.generatePublic(keySpec);
// 剖析甲方公鑰,得到其參數(shù)
DHParameterSpec dhParameterSpec = dhPublicKey.getParams();
// 實(shí)例化密鑰對(duì)生成器
KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM);
// 使用得到的參數(shù),初始化密鑰生成器
generator.initialize(dhParameterSpec);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
// 獲取公鑰
public static byte[] getPublicKey(KeyPair keyPair) {
byte[] bytes = keyPair.getPublic().getEncoded();
return bytes;
}
// 獲取公鑰
public static String getPublicKeyStr(KeyPair keyPair) {
byte[] bytes = keyPair.getPublic().getEncoded();
return encodeHex(bytes);
}
// 獲取私鑰
public static byte[] getPrivateKey(KeyPair keyPair) {
byte[] bytes = keyPair.getPrivate().getEncoded();
return bytes;
}
// 獲取公鑰
public static String getPrivateKeyStr(KeyPair keyPair) {
byte[] bytes = keyPair.getPrivate().getEncoded();
return encodeHex(bytes);
}
// 生成本地密鑰
public static byte[] getSecretKey(byte[] publicKey, byte[] privateKey) {
try {
// 實(shí)例化密鑰工廠
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
// 將公鑰從字節(jié)數(shù)組轉(zhuǎn)換為PublicKey
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
// 將私鑰從字節(jié)數(shù)組轉(zhuǎn)換為PrivateKey
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKey);
PrivateKey priKey = keyFactory.generatePrivate(privateSpec);
// 先實(shí)例化KeyAgreement
KeyAgreement keyAgreement = KeyAgreement.getInstance(ALGORITHM);
// 用自己的私鑰初始化keyAgreement
keyAgreement.init(priKey);
// 結(jié)合對(duì)方的公鑰進(jìn)行運(yùn)算
keyAgreement.doPhase(pubKey, true);
// 開始生成本地密鑰SecretKey 密鑰算法為對(duì)稱密碼算法
SecretKey key = keyAgreement.generateSecret("AES");
return key.getEncoded();
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
// 數(shù)據(jù)準(zhǔn)16進(jìn)制編碼
public static String encodeHex(final byte[] data) {
return encodeHex(data, true);
}
// 數(shù)據(jù)轉(zhuǎn)16進(jìn)制編碼
public static String encodeHex(final byte[] data, final boolean toLowerCase) {
final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
final char[] toDigits = toLowerCase ? DIGITS_LOWER : DIGITS_UPPER;
final int l = data.length;
final char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return new String(out);
}
public static void main(String[] args) {
KeyPair keyPair1 = initKey();
byte[] publicKey1 = getPublicKey(keyPair1);
String publicKeyStr1 = encodeHex(publicKey1);
byte[] privateKey1 = getPrivateKey(keyPair1);
String privateKeyStr1 = encodeHex(privateKey1);
System.out.println("甲方公鑰:" + publicKeyStr1);
System.out.println("甲方私鑰:" + privateKeyStr1);
KeyPair keyPair2 = initKey(publicKey1);
byte[] publicKey2 = getPublicKey(keyPair2);
String publicKeyStr2 = encodeHex(publicKey2);
byte[] privateKey2 = getPrivateKey(keyPair2);
String privateKeyStr2 = encodeHex(privateKey2);
System.out.println("乙方公鑰:" + publicKeyStr2);
System.out.println("乙方私鑰:" + privateKeyStr2);
byte[] secrectKey1 = getSecretKey(publicKey2, privateKey1);
byte[] secrectKey2 = getSecretKey(publicKey1, privateKey2);
String secrectKeyStr1 = encodeHex(secrectKey1);
String secrectKeyStr2 = encodeHex(secrectKey2);
System.out.println("甲方協(xié)議密鑰:" + secrectKeyStr1);
System.out.println("乙方協(xié)議密鑰:" + secrectKeyStr2);
System.out.println("甲=乙:" + secrectKeyStr1.equals(secrectKeyStr2));
}
}
如果讀完覺得有收獲的話,歡迎點(diǎn)贊、關(guān)注、加公眾號(hào)【小工匠技術(shù)圈】
個(gè)人公眾號(hào),歡迎關(guān)注,查閱更多精彩歷史!