// 密钥协商算法密钥对生成入参,查看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还原出公钥对象,这里要关注两个问题:
- 公钥私钥规范;
- KeyFactory.getInstance的入参
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。
其他还需要知道的:
- MD系列已经退役了,MD5散列值128bit,安全强度才仅仅18bit,只需要26W次计算就能破解,按照当前的算力分分钟就无了;
- SHA1系列已经退役了;
- SHA2系列,SHA-256往上都是现役;
- SHA3系列,SHA3-256以上都是现役;
- 单向散列函数一定要注意避免长度延展攻击,SHA-512/224和SHA-512/256已经避免了该问题,如果选用其他现役算法,那就要注意公开消息和私密消息的顺序问题;
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)
// 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
* <code>sharedSecret</code>, beginning at <code>offset</code>.
* <p>If the <code>sharedSecret</code> buffer is too small to hold the
* result, a <code>ShortBufferException</code> is thrown.
* In this case, this call should be repeated with a larger output buffer.
* <p>This method resets this <code>KeyAgreementSpi</code> object,
* so that it
* can be reused for further key agreements. Unless this key agreement is
* reinitialized with one of the <code>engineInit</code> 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 <code>sharedSecret</code> where the
* shared secret will be stored
* @return the number of bytes placed into <code>sharedSecret</code>
* @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的入参不一样,注意这个细节就行了。
写文章确实很费时间,就到这里吧,感谢阅读。
本文的思路是这样的:先了解Diffie-Hellman的流程原理,然后将其流程和Java的实现对应起来;理解了原理和Java实现的流程,再写应用代码进一步辅助验证,最后走一走源码流程中的相关细节,做最终验证;最后,再了解一下性能更好、安全性更高的ECDH(基于椭圆曲线来实现的Diffie-Hellman)
diffie-Hellman(DH)算法原理
Diffie-Hellman算法是Whitefield Diffie和Martin Hellman在1976年公布的一种秘钥交换算法,它是一种建立秘钥的方法,而不是加密方法,所以秘钥必须和其他一种加密算法结合使用。这种秘钥交换技术的目的在于使两个用户安全的交换一个秘钥一遍后面的报文加密。
Diffie-Hellman密钥交换算法的有效性依赖于计算离散对数的难度。
1)由消息发送的一方构建密钥,这里由甲方构建密钥。
2)由构建密钥的一方向对方公布其公钥
公开密钥密码学(英语:Public-key cryptography)也称非对称式密码学(英语:Asymmetric cryptography)是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;公钥用作加密,私钥则用作解密。使用公钥把明文加密后所得的密文,只能用相对应的私钥才能解密并得到原本的明文,最初用来加密的公钥不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。公钥可以公开,可任意向外发布;私钥不可以公开,必须由用户自行严
Bob利用对称秘钥K对信息进行加密并将加密结果发送给Alice,Alice收到信息之后,用同样的秘钥进行解密。
问题1:Alice是如何知道对称秘钥K的?------即,Bob首先需要将对称秘钥K发送给Alice,Alice才能对信息进行解密。即:通信双方需要达成共识,也就是用什么样的秘钥进行加密。
由此推出:传递信息的前提是需要关于秘钥达成共识,也就是传递秘钥。
问题2:...
对称加密算法在加密和解密时使用的是同一个秘钥;而非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
是一种 高级的双保险加密方式,一般的实现加密方式有DH密钥交换算法,RSA基于因子分解算法,ElGamal离散对数算法及ECC椭圆曲线加密等。
------------------DH加密解密--
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import java.securi
<br /><br /> <br />Diffie-Hellman密钥交换代码如下:<br />import
java.util.ArrayList; <br />import
java.util.List; <br />import
java.util.Random;<br />import
java.math.*;<br /> <br />public class
DHtest {<br /> <br /> public static void main(String[]
IM消息自定义消息体设计时要考虑到可能产生的安全问题,如果消息体没有进行加密措施,很容易被抓包截取,消息加密要考虑到各个流程的可能出现的安全性问题!
下面是IM消息加密设计的密钥协商过程:
C S
(public key)P1,K1(AES) p1(k1)---> (private k...
Diffie-Hellman算法是一种密钥交换协议,用于在不安全的通信渠道上安全地交换密钥。Java中可以使用javax.crypto包中的KeyAgreement类来实现Diffie-Hellman算法。具体步骤如下:
1. 创建KeyPairGenerator对象,指定算法为DiffieHellman。
2. 生成密钥对,包括公钥和私钥。
3. 创建KeyAgreement对象,指定算法为DiffieHellman。
4. 初始化KeyAgreement对象,传入自己的私钥。
5. 使用对方的公钥,执行KeyAgreement对象的doPhase方法,生成共享密钥。
6. 将共享密钥用于加密通信。
需要注意的是,Diffie-Hellman算法只能用于密钥交换,不能用于加密和解密数据。在实际应用中,通常会使用共享密钥来加密数据,例如使用AES算法。
示例代码如下:
```java
import javax.crypto.KeyAgreement;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
public class DiffieHellmanExample {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 创建KeyPairGenerator对象,指定算法为DiffieHellman
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DiffieHellman");
// 生成密钥对,包括公钥和私钥
KeyPair keyPair = keyPairGenerator.generateKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
// 创建KeyAgreement对象,指定算法为DiffieHellman
KeyAgreement keyAgreement = KeyAgreement.getInstance("DiffieHellman");
// 初始化KeyAgreement对象,传入自己的私钥
keyAgreement.init(keyPair.getPrivate());
// 假设对方的公钥为publicKey2
byte[] publicKey2 = ...;
// 使用对方的公钥,执行KeyAgreement对象的doPhase方法,生成共享密钥
keyAgreement.doPhase(publicKey2, true);
SecretKey sharedSecretKey = keyAgreement.generateSecret("AES");
// 将共享密钥用于加密通信
byte[] plaintext = "Hello, world!".getBytes();
byte[] ciphertext = encrypt(plaintext, sharedSecretKey);
byte[] decryptedPlaintext = decrypt(ciphertext, sharedSecretKey);
System.out.println(new String(decryptedPlaintext));
private static byte[] encrypt(byte[] plaintext, SecretKey key) {
// 使用共享密钥key加密plaintext
return null;
private static byte[] decrypt(byte[] ciphertext, SecretKey key) {
// 使用共享密钥key解密ciphertext
return null;