本文的思路:
DH的流程中涉及三个角色,主要是通信双方Alice、Bobby,和可能存在窃听的中间人Eaves。
场景:Alice要和Bobby通信,发送的信息可能会被中间人Eaves窃听到,Alice和Bobby如何通过DH算法来保证通信的机密性?
DH是密钥协商(key agreement)算法,通信双方并没有真正的交换密钥,而是通过交换部分可以公开的信息来计算生成出相同的对称密钥,然后通过对称密钥对消息进行加密,从而保证消息的机密性。
DH的流程原理如下:
再做进一步的抽象,四小步骤实际上可以抽象成两个阶段:
我们分析一下在Alice、Bobby协商密钥的过程中,中间人Eaves通过窃听到的信息能否计算出密钥:
JDK的API封装了很多细节(大质数P、生成元G、私钥A/B,公钥(G^A)modP/(G^B)modP都封装到了PublicKey或PrivateKey对象中),对外程序员能看到的类就是公钥、私钥,DH流程原理的细节对应放在第四部分再剖析,这一部分只做大概的、整体上的流程对应,目的是为了顺利写出第三部分的应用代码。
1.密钥协商阶段,这一部分主要是DH算法:
2.加密通信阶段,这一部分主要是对称加密算法,本文选择现役、流行的AES算法为例:
我们按照第二部分中的流程来。
1.Alice生成密钥对
// 密钥协商算法密钥对生成入参,查看KeyPairGenerator.getInstance文档
private static String DH_KEY = "DH";
/***
* Alice生成密钥对
* @return Alice的密钥对
* @throws Exception
*/
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
return keyPairGenerator.generateKeyPair();
}
代码很简洁,几乎都是固定格式,你需要关注的是KeyPairGenerator.getInstance方法的入参,这个字符串从哪里去找?
JDK中都在一个页面找,后面的getInstance都在相同的页面,只是要在页面找不同的类的文档,页面地址:Java Security Standard Algorithm Names
找到KeyPairGenerator类的文档,截图如下:
2.Bobby根据Alice的公钥生成自己的密钥对
/**
* Bobby收到Alice的公钥数组:
* 1.先将公钥数组解析成公钥对象
* KeyFactory.getInstance的入参查看KeyFactory的文档
* 文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
* 2.根据Alice的公钥对象生成自己的密钥对
* @param publicKeyArray 收到的、对方的公钥数组
* @return
* @throws Exception
*/
public static KeyPair generateKeyPair(byte[] publicKeyArray) throws Exception {
// 将Alice发过来的公钥数组解析成公钥对象
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyArray);
KeyFactory keyFactory = KeyFactory.getInstance(DH_KEY);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
// Bobby根据Alice的公钥生成自己的密钥对
DHParameterSpec dhParameterSpec = ((DHPublicKey)publicKey).getParams();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
keyPairGenerator.initialize(dhParameterSpec);
return keyPairGenerator.generateKeyPair();
}
Alice将自己的公钥通过网络传送给Bobby,一般传送的是公钥数组,所以当Bobby收到Alice的公钥数组以后,通过X509EncodedKeySpec封装公钥数组,再通过KeyFactory还原出公钥对象,这里要关注两个问题:
a.公钥私钥规范
Java中公钥数组通过X509EncodedKeySpec规范来解析,私钥数组通过PKCS8EncodedKeySpec规范来解析,代码都是一样的。
X509EncodedKeySpec:This class represents the ASN.1 encoding of a public key, encoded according to the ASN.1 type
SubjectPublicKeyInfo
.PKCS8EncodedKeySpec:This class represents the ASN.1 encoding of a private key, encoded according to the ASN.1 type
PrivateKeyInfo
.
ASN.1是一种数据格式标准;PKCS(The Public-Key Cryptography Standards)是公钥密码标准,目前有15个标准,其中PKCS8是私钥格式标准,这一部分你可以自行拓展。
b.KeyFactory.getInstance的入参
需要查询的JDK文档和KeyPairGenerator是一样的,只是要查页面KeyFactory部分:
3.Alice和Bobby根据自己的私钥和对方的公钥生成相同的对称密钥
这一部分代码不多,但是你需要重点关注,因为这是将密钥协商阶段和加密通信阶段连接起来的部分,两个阶段通过这一部分计算出来的对称密钥衔接在一起。
该部分需要关注JDK的版本,在JDK 8u161前后的写法是不一样的。我们先看JDK 8u161之前的写法,然后过一下JDK 8u161中的变化,最后再看看现在的写法。
在JDK 8u161之前:
/**
* Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
* KeyAgreement.getInstance入参查看相同的文档
* 文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
* @param publicKey 对方的公钥
* @param privateKey 自己的私钥
* @return 用于信息加密的对称密钥
* @throws Exception
*/
@Deprecated
public static SecretKey generateSecretKey(PublicKey publicKey, PrivateKey privateKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret(AES_KEY);
}
KeyAgreement.gtInstance的入参找法和上面都是一样的,关注点在KeyAgreement.generateSecret方法。
keyAgreement.generateSecret(AES_KEY)在JDK 8u161前是可以正常实用,之后不再推荐使用了,直接报异常:
这个异常信息很迷啊,实际上并不是不支持AES算法,根据异常链的信息,你直接点到DHKeyAgreement类中去看:
这个AllowKDF.VALUE就是一个系统变量:
这个系统变量默认是没有设置的,你通过System.out.println(System.getProperty("jdk.crypto.KeyAgreement.legacyKDF"))打印出来值是null,所以在JDK 8u161之后你想要用这个方法,你要先设置这个系统变量:
System.setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");
但是Oracle官方并不推荐这样做,我们转去看看JDK 8u161的更新文档。
JDK 8u161 Update Release Notes:Java™ SE Development Kit 8, Update 161 (oracle.com)
不推荐使用的原因截图如下:
那现在应该怎么做呢,官方给了三种方式,截图如下:
A是根据相关密钥规范实现密钥派生功能,这对密码学素养要求比较高,对绝大多数程序员来说不现实,直接pass;
C就是设置System.getProperty("jdk.crypto.KeyAgreement.legacyKDF")系统变量,启用keyAgreement.generateSecret(AES_KEY)方法,但是不安全,直接pass;
那就只有B了,实现简单,也足够安全,代码也几乎是固定格式:
/**
* Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
* 注意:当前推荐的用法
* @param publicKey 对方的公钥
* @param privateKey 自己的私钥
* @return 生成的对称密钥
* @throws Exception
*/
public static SecretKey generateSecretKeyBySHA256(PublicKey publicKey, PrivateKey privateKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
// 当前推荐的做法
byte[] keyArray = keyAgreement.generateSecret();
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] digest = messageDigest.digest(keyArray);
return new SecretKeySpec(digest, AES_KEY);
}
同样,MessageDigest.getInstance入参查询的也是同样的页面。
Java对单向散列函数的支持是最容易上手的了,就是一个类MessageDigest和一个方法digest,然后你只要知道不再使用退役算法,熟悉现役、常见的算法就行了。例如本例中SHA-256,现役、常用的单向散列函数,安全强度128bit,散列值长度256bit。
单向散列函数的命名是有规律的,安全强度是其输出的散列值的一半,输出的散列值长度就在算法名中,例如SHA256,安全强度128bit、散列值256bit;SHA384,安全强度192bit、散列值384bit;SHA512,安全强度256bit、散列值512bit。
其他还需要知道的:
4.Alice和Bobby通过对称密钥对消息进行加解密后通信
/**
* 生成分组密码分组模式的初始化向量
* 本例中,AES的分组128bit,16字节
* @param length
* @return
*/
public static byte[] initIV(int length) {
return random.generateSeed(length);
}
/**
* 加密
* @param secretKey 密钥协商阶段协商的对称密钥
* @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
* @param data 需要加密的明文字节数组
* @return 密文
* @throws Exception
*/
public static byte[] encryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
// 密钥和算法有关系,和加解密模式没有关系,所以上面分成了AES_KEY和CIPHER_MODE
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
// 分组密码分组模式的初始化向量参数,本例中是CBC模式
IvParameterSpec ivp = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivp);
return cipher.doFinal(data);
}
/**
* 解密
* @param secretKey 密钥协商阶段协商的对称密钥
* @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
* @param data 需要解密的密文的字节数组
* @return 解密后的明文
* @throws Exception
*/
public static byte[] decryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
// 分组密码分组模式的初始化向量参数,本例中是CBC模式
IvParameterSpec ivp = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivp);
return cipher.doFinal(data);
}
Cipher.getInstance的入参查询方法和上面一样。
分组密码的模式参看java DES_厚积薄发者,轻舟万重山-CSDN博客
5.最后看一下完整的代码和简单的测试用例
package wxy.secret;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DiffieHellman {
// JDK文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
// 密钥协商算法密钥对生成入参,查看KeyPairGenerator.getInstance文档
private static String DH_KEY = "DH";
// 对称加密算法密钥生成入参,参看KeyGenerator.getInstance文档
private static String AES_KEY = "AES";
// 分组密码的模式入参,参看Cipher.getInstance文档
private static String CIPHER_MODE = "AES/CBC/PKCS5Padding";
private static SecureRandom random = new SecureRandom();
/***
* Alice生成密钥对
* KeyPairGenerator.getInstance的入参查看KeyPairGenerator的文档
* 文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
* @return Alice的密钥对
* @throws Exception
*/
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
return keyPairGenerator.generateKeyPair();
}
/**
* Bobby收到Alice的公钥数组:
* 1.先将公钥数组解析成公钥对象
* KeyFactory.getInstance的入参查看KeyFactory的文档
* 文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
* 2.根据Alice的公钥对象生成自己的密钥对
* @param publicKeyArray
* @return
* @throws Exception
*/
public static KeyPair generateKeyPair(byte[] publicKeyArray) throws Exception {
// 将Alice发过来的公钥数组解析成公钥对象
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyArray);
KeyFactory keyFactory = KeyFactory.getInstance(DH_KEY);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
// Bobby根据Alice的公钥生成自己的密钥对
DHParameterSpec dhParameterSpec = ((DHPublicKey)publicKey).getParams();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DH_KEY);
keyPairGenerator.initialize(dhParameterSpec);
return keyPairGenerator.generateKeyPair();
}
/**
* Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
* KeyAgreement.getInstance入参查看相同的文档
* 文档地址:https://docs.oracle.com/en/java/javase/15/docs/specs/security/standard-names.html
* 注意:这种实现方式不安全,已不推荐使用,参看JDK 8u161的更新文档
* 文档地址:https://www.oracle.com/java/technologies/javase/8u161-relnotes.html
* @param publicKey 对方的公钥
* @param privateKey 自己的私钥
* @return 用于信息加密的对称密钥
* @throws Exception
*/
@Deprecated
public static SecretKey generateSecretKey(PublicKey publicKey, PrivateKey privateKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
// 关注keyAgreement.generateSecret(AES_KEY),在JDK 8u161前后有很大差异
return keyAgreement.generateSecret(AES_KEY);
}
/**
* Alice和Bobby通过自己的私钥和对方的公钥计算生成相同的对称密钥
* 注意:当前推荐的用法
* @param publicKey 对方的公钥
* @param privateKey 自己的私钥
* @return 生成的对称密钥
* @throws Exception
*/
public static SecretKey generateSecretKeyBySHA256(PublicKey publicKey, PrivateKey privateKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance(DH_KEY);
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
// 当前推荐的做法
byte[] keyArray = keyAgreement.generateSecret();
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] digest = messageDigest.digest(keyArray);
return new SecretKeySpec(digest, AES_KEY);
}
/**
* 生成分组密码分组模式的初始化向量
* 本例中,AES的分组128bit,16字节
* @param length
* @return
*/
public static byte[] initIV(int length) {
return random.generateSeed(length);
}
/**
* 加密
* @param secretKey 密钥协商阶段协商的对称密钥
* @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
* @param data 需要加密的明文字节数组
* @return 密文
* @throws Exception
*/
public static byte[] encryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
// 密钥和算法有关系,和加解密模式没有关系,所以上面分成了AES_KEY和CIPHER_MODE
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
// 分组密码分组模式的初始化向量参数,本例中是CBC模式
IvParameterSpec ivp = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivp);
return cipher.doFinal(data);
}
/**
* 解密
* @param secretKey 密钥协商阶段协商的对称密钥
* @param iv 分组密码分组模式的初始化向量,加密和解密需要使用相同的iv
* @param data 需要解密的密文的字节数组
* @return 解密后的明文
* @throws Exception
*/
public static byte[] decryption(SecretKey secretKey, byte[] iv, byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
// 分组密码分组模式的初始化向量参数,本例中是CBC模式
IvParameterSpec ivp = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivp);
return cipher.doFinal(data);
}
}
// 不推荐通过该方式启用keyAgreement.generateSecret(AES_KEY)方法
//System.setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");
// 1.Alice生成密钥对
KeyPair keyPairA = DiffieHellman.generateKeyPair();
// 2.Alice将自己的公钥发送给Bobby,实际上公钥对象中封装了大质数P、生成元G和Alice的公钥Y
PublicKey publicKeyA = keyPairA.getPublic();
byte[] publicKeyArray = publicKeyA.getEncoded();
// 3.Bobby根据Alice的公钥对象生成自己的密钥对
KeyPair keyPairB = DiffieHellman.generateKeyPair(publicKeyArray);
// 4.Bobby将自己的公钥回送给Alice
PublicKey publicKeyB = keyPairB.getPublic();
// 5.Alice根据自己的私钥和Bobby的公钥生成对称密钥
SecretKey secretKeyA = DiffieHellman.generateSecretKeyBySHA256(publicKeyB, keyPairA.getPrivate());
// 6.Bobby根据自己的私钥和Alice的公钥生成对称密钥
SecretKey secretKeyB = DiffieHellman.generateSecretKeyBySHA256(publicKeyA, keyPairB.getPrivate());
System.out.print("Alice生成的对称密钥:");
HexStringTool.print(secretKeyA.getEncoded());
System.out.print("Bobby生成的对称密钥:");
HexStringTool.print(secretKeyB.getEncoded());
String message = "Diffie-Hellman密钥协商算法简介";
byte[] data = message.getBytes(Charset.forName("UTF-8"));
// 初始化分组密码分组模式的初始化向量,本例AES CBC模式,分组长度128bit,所以IV也是128bit
byte[] iv = DiffieHellman.initIV(16);
// 加密
byte[] ciphertext = DiffieHellman.encryption(secretKeyA, iv, data);
System.out.print("加密后的密文:");
HexStringTool.print(ciphertext);
// 解密
byte[] plaintext = DiffieHellman.decryption(secretKeyB, iv, ciphertext);
System.out.print("解密后的明文:");
HexStringTool.print(plaintext);
// 将解密后的字节数组还原成UTF8编码的字符
System.out.println("解密得到的明文数组还原成UTF8编码:"+new String(plaintext, Charset.forName("UTF-8")));
Alice生成的对称密钥:3735c170f563519c92f80f89548bac53f68607f5a89a2a6d348b0f77a9fb6228
Bobby生成的对称密钥:3735c170f563519c92f80f89548bac53f68607f5a89a2a6d348b0f77a9fb6228
加密后的密文:4270b6ed0b6cea65bb927a6738ff1db96cb40960ac326eb7a0ee2e32a71accacd3df2612e205450ead6ef222d7e302b2
解密后的明文:4469666669652d48656c6c6d616ee5af86e992a5e58d8fe59586e7ae97e6b395e7ae80e4bb8b
解密得到的明文数组还原成UTF8编码:Diffie-Hellman密钥协商算法简介
这一部分是将JDK API中封装的原理细节剖析出来,Diffie-Hellman原理参看第一部分,对应的Java API流程参看第二部分。
1.生成密钥对
入口:keyPairGenerator.generateKeyPair()
实现:com.sun.crypto.provider.DHKeyPairGenerator.generateKeyPair()
/**
* Generates a key pair.
* @return the new key pair
*/
public KeyPair generateKeyPair() {
if (random == null) {
random = SunJCE.getRandom();
}
if (params == null) {
try {
params = ParameterCache.getDHParameterSpec(pSize, random);
} catch (GeneralSecurityException e) {
// should never happen
throw new ProviderException(e);
}
}
BigInteger p = params.getP();
BigInteger g = params.getG();
if (lSize <= 0) {
lSize = pSize >> 1;
// use an exponent size of (pSize / 2) but at least 384 bits
if (lSize < 384) {
lSize = 384;
}
}
BigInteger x;
BigInteger pMinus2 = p.subtract(BigInteger.TWO);
//
// PKCS#3 section 7.1 "Private-value generation"
// Repeat if either of the followings does not hold:
// 0 < x < p-1
// 2^(lSize-1) <= x < 2^(lSize)
//
do {
// generate random x up to 2^lSize bits long
x = new BigInteger(lSize, random);
} while ((x.compareTo(BigInteger.ONE) < 0) ||
((x.compareTo(pMinus2) > 0)) || (x.bitLength() != lSize));
// calculate public value y
BigInteger y = g.modPow(x, p);
DHPublicKey pubKey = new DHPublicKey(y, p, g, lSize);
DHPrivateKey privKey = new DHPrivateKey(x, p, g, lSize);
return new KeyPair(pubKey, privKey);
}
其中,大质数P、生成元G,x = new BigInteger(lSize, random)生成的随机数x作为私钥,BigInteger y = g.modPow(x, p)计算出的y作为公钥(g的x次方mod p)。
然后私钥对象封装了大质数P、生成元G和私钥X;公钥对象封装了大质数P、生成元G和公钥Y。
2.通过自己的私钥和对方的公钥生成相同的对称密钥
入口:keyAgreement.generateSecret();
实现:com.sun.crypto.provider.DHKeyAgreement.engineGenerateSecret(byte[], int)
/**
* Generates the shared secret, and places it into the buffer
* sharedSecret
, beginning at offset
.
*
* If the sharedSecret
buffer is too small to hold the
* result, a ShortBufferException
is thrown.
* In this case, this call should be repeated with a larger output buffer.
*
*
This method resets this KeyAgreementSpi
object,
* so that it
* can be reused for further key agreements. Unless this key agreement is
* reinitialized with one of the engineInit
methods, the same
* private information and algorithm parameters will be used for
* subsequent key agreements.
*
* @param sharedSecret the buffer for the shared secret
* @param offset the offset in sharedSecret
where the
* shared secret will be stored
*
* @return the number of bytes placed into sharedSecret
*
* @exception IllegalStateException if this key agreement has not been
* completed yet
* @exception ShortBufferException if the given output buffer is too small
* to hold the secret
*/
protected int engineGenerateSecret(byte[] sharedSecret, int offset)
throws IllegalStateException, ShortBufferException
{
if (generateSecret == false) {
throw new IllegalStateException
("Key agreement has not been completed yet");
}
if (sharedSecret == null) {
throw new ShortBufferException
("No buffer provided for shared secret");
}
BigInteger modulus = init_p;
int expectedLen = (modulus.bitLength() + 7) >>> 3;
if ((sharedSecret.length - offset) < expectedLen) {
throw new ShortBufferException
("Buffer too short for shared secret");
}
// Reset the key agreement after checking for ShortBufferException
// above, so user can recover w/o losing internal state
generateSecret = false;
/*
* NOTE: BigInteger.toByteArray() returns a byte array containing
* the two's-complement representation of this BigInteger with
* the most significant byte is in the zeroth element. This
* contains the minimum number of bytes required to represent
* this BigInteger, including at least one sign bit whose value
* is always 0.
*
* Keys are always positive, and the above sign bit isn't
* actually used when representing keys. (i.e. key = new
* BigInteger(1, byteArray)) To obtain an array containing
* exactly expectedLen bytes of magnitude, we strip any extra
* leading 0's, or pad with 0's in case of a "short" secret.
*/
byte[] secret = this.y.modPow(this.x, modulus).toByteArray();
if (secret.length == expectedLen) {
System.arraycopy(secret, 0, sharedSecret, offset,
secret.length);
} else {
// Array too short, pad it w/ leading 0s
if (secret.length < expectedLen) {
System.arraycopy(secret, 0, sharedSecret,
offset + (expectedLen - secret.length),
secret.length);
} else {
// Array too long, check and trim off the excess
if ((secret.length == (expectedLen+1)) && secret[0] == 0) {
// ignore the leading sign byte
System.arraycopy(secret, 1, sharedSecret, offset, expectedLen);
} else {
throw new ProviderException("Generated secret is out-of-range");
}
}
}
return expectedLen;
}
重点就关注两句代码:
byte[] secret = this.y.modPow(this.x, modulus).toByteArray();
其中modulus就是大质数P:
BigInteger modulus = init_p;
所以this.y.modPow(this.x, modulus).toByteArray()这句代码中,y是对方的公钥,x是自己的私钥,modulus是大质数P,即y的x次方mod p。
这些源码封装了的实现细节和第一部分的原理一模一样。
鉴于篇幅问题,ECDH就不再写出来了,你可以自己试一试,本质都是DH密钥协商算法,只是通过ECC椭圆曲线来实现。实现代码几乎都是一致的,只是由于历史原因,ECDH中KeyPairGenerator.getInstance和KeyAgreement.getInstance的入参不一样,注意这个细节就行了。
写文章确实很费时间,就到这里吧,感谢阅读。