8.2.3 实现
Java 6提供了DH算法的相关实现,相关Java API的知识可阅读第3章内容。作为对称加密算法向非对称加密算法的一种过渡,DH算法的实现是我们了解非对称加密算法的最佳途径。
有关DH算法的Java 6实现细节如表8-1所示。
在这里,作者与读者朋友做一个约定:所有非对称算法实现的公有方法均不使用java.security和javax.crypto包及其子包中的接口或类作为参数或返回值。
实现DH算法密钥需要用到密钥对及密钥对生成器,如代码清单8-1所示。
代码清单8-1 构建DH算法甲方密钥对
//实例化密钥对生成器
KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance("DH");
//初始化密钥对生成器
keyPairGenerator.initialize(1024);
//生成密钥对
KeyPair keyPair=keyPairGenerator.generateKeyPair();
//公钥
PublicKey publicKey=keyPair.getPublic();
//私钥
PrivateKey privateKey=keyPair.getPrivate();
如果有必要,我们需要使用DH算法专用公钥/私钥接口(DHPublicKey/DHPrivateKey)强行转换上述密钥。
上述代码完成了甲方密钥的构建,要构建乙方密钥需要使用甲方公钥,具体实现如代码清单8-2所示。
代码清单8-2 构建DH算法乙方密钥对
//由甲方公钥构建乙方密钥
DHParameterSpec dhParamSpec=((DHPublicKey)pubKey).getParams();
//实例化密钥对生成器
KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(keyFactory.getAlgorithm());
//初始化密钥对生成器
keyPairGenerator.initialize(dhParamSpec);
//生成密钥对
KeyPair keyPair=keyPairGenerator.generateKeyPair();
//公钥
PublicKey publicKey=keyPair.getPublic();
//私钥
PrivateKey privateKey=keyPair.getPrivate();
按照作者与读者朋友的约定,密钥仅能以二进制编码形式出现。因此,我们通过Map对象封装密钥,如代码清单8-3所示。
代码清单8-3 封装DH算法密钥
//将密钥对存储在Map中
Map<String, Object>keyMap=new HashMap<String, Object>(2);
keyMap.put("DHPublicKey",publicKey);
keyMap.put("DHPrivateKey",privateKey);
我们将在后续的initKey()方法中见到上述代码。
如果要将密钥材料转换为密钥对象,可参考代码清单8-4。
代码清单8-4 转换密钥材料
//实例化密钥工厂
KeyFactory keyFactory=KeyFactory.getInstance("DH");
//初始化公钥
//公钥密钥材料转换
X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(publicKey);
//产生公钥
PublicKey publicKey=keyFactory.generatePublic(x509KeySpec);
//初始化私钥
//私钥密钥材料转换
PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(privateKey);
//产生私钥
PrivateKey privateKey=keyFactory.generatePrivate(pkcs8KeySpec);
完成甲乙方密钥的构建操作后,我们便可以完成本地密钥的构建。这里,要求使用不对称的公钥和私钥来构建本地密钥,即使用甲方私钥和乙方公钥构建甲方本地密钥;使用乙方私钥和甲方公钥构建乙方本地密钥。相关实现如代码清单8-5所示。
代码清单8-5 构建本地密钥
//实例化
KeyAgreement keyAgree=KeyAgreement.getInstance(keyFactory.getAlgorithm());
//初始化
keyAgree.init(priKey);
keyAgree.doPhase(pubKey, true);
//生成本地密钥
SecretKey secretKey=keyAgree.generateSecret("AES");
完成了上述准备后,我们接下来仅仅需要使用本地密钥进行加密/解密操作了。本地密钥就是对称加密算法中的秘密密钥,对于对称加密算法的加密/解密操作,我们在第7章中已经详尽描述,就不在此复述了。完整的代码实现如代码清单8-6所示。
代码清单8-6 DH算法实现
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
*DH安全编码组件
*@author梁栋
*@version 1.0
*/
public abstract class DHCoder{
//非对称加密密钥算法
public static final String KEY_ALGORITHM="DH";
/**
*本地密钥算法,即对称加密密钥算法,
*可选DES、DESede和AES算法
*/
public static final String SECRET_ALGORITHM="AES";
/**
*密钥长度
*DH算法默认密钥长度为1024
*密钥长度必须是64的倍数,其范围在512位到1024位之间。
*/
private static final int KEY_SIZE=512;
//公钥
private static final String PUBLIC_KEY="DHPublicKey";
//私钥
private static final String PRIVATE_KEY="DHPrivateKey";
/**
*初始化甲方密钥
*@return Map甲方密钥Map
*@throws Exception
*/
public static Map<String, Object>initKey()throws Exception{
//实例化密钥对生成器
KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(KEY_ALGORITHM);
//初始化密钥对生成器
keyPairGenerator.initialize(KEY_SIZE);
//生成密钥对
KeyPair keyPair=keyPairGenerator.generateKeyPair();
//甲方公钥
DHPublicKey publicKey=(DHPublicKey)keyPair.getPublic();
//甲方私钥
DHPrivateKey privateKey=(DHPrivateKey)keyPair.getPrivate();
//将密钥对存储在Map中
Map<String, Object>keyMap=new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
*初始化乙方密钥
*@param key甲方公钥
*@return Map乙方密钥Map
*@throws Exception
*/
public static Map<String, Object>initKey(byte[]key)throws Exception{
//解析甲方公钥
//转换公钥材料
X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(key);
//实例化密钥工厂
KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
//产生公钥
PublicKey pubKey=keyFactory.generatePublic(x509KeySpec);
//由甲方公钥构建乙方密钥
DHParameterSpec dhParamSpec=((DHPublicKey)pubKey).getParams();
//实例化密钥对生成器
KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(keyFactory.getAlgorithm());
//初始化密钥对生成器
keyPairGenerator.initialize(dhParamSpec);
//产生密钥对
KeyPair keyPair=keyPairGenerator.genKeyPair();
//乙方公钥
DHPublicKey publicKey=(DHPublicKey)keyPair.getPublic();
//乙方私钥
DHPrivateKey privateKey=(DHPrivateKey)keyPair.getPrivate();
//将密钥对存储在Map中
Map<String, Object>keyMap=new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
*加密
*@param data待加密数据
*@param key密钥
*@return byte[]加密数据
*@throws Exception
*/
public static byte[]encrypt(byte[]data, byte[]key)throws Exception{
//生成本地密钥
SecretKey secretKey=new SecretKeySpec(key, SECRET_ALGORITHM);
//数据加密
Cipher cipher=Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(data);
}
/**
*解密<br>
*@param data待解密数据
*@param key密钥
*@return byte[]解密数据
*@throws Exception
*/
public static byte[]decrypt(byte[]data, byte[]key)throws Exception{
//生成本地密钥
SecretKey secretKey=new SecretKeySpec(key, SECRET_ALGORITHM);
//数据解密
Cipher cipher=Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(data);
}
/**
*构建密钥
*@param publicKey公钥
*@param privateKey私钥
*@return byte[]本地密钥
*@throws Exception
*/
public static byte[]getSecretKey(byte[]publicKey, byte[]privateKey)
throws Exception{
//实例化密钥工厂
KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(publicKey);
//产生公钥
PublicKey pubKey=keyFactory.generatePublic(x509KeySpec);
//初始化私钥
//密钥材料转换
PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(privateKey);
//产生私钥
PrivateKey priKey=keyFactory.generatePrivate(pkcs8KeySpec);
//实例化
KeyAgreement keyAgree=KeyAgreement.getInstance(keyFactory.getAlgorithm());
//初始化
keyAgree.init(priKey);
keyAgree.doPhase(pubKey, true);
//生成本地密钥
SecretKey secretKey=keyAgree.generateSecret(SECRET_ALGORITHM);
return secretKey.getEncoded();
}
/**
*取得私钥
*@param keyMap密钥Map
*@return byte[]私钥
*@throws Exception
*/
public static byte[]getPrivateKey(Map<String, Object>keyMap)throws Exception{
Key key=(Key)keyMap.get(PRIVATE_KEY);
return key.getEncoded();
}
/**
*取得公钥
*@param keyMap密钥Map
*@return byte[]公钥
*@throws Exception
*/
public static byte[]getPublicKey(Map<String, Object>keyMap)
throws Exception{
Key key=(Key)keyMap.get(PUBLIC_KEY);
return key.getEncoded();
}
}
对于DH算法的测试,我们需要关注两点:一点是甲乙方的本地密钥是否相同;另一点是甲方加密后的数据,乙方是否能够解析,反之亦然。
完整的DH算法测试用例如代码清单8-7所示。
代码清单8-7 DH算法实现测试用例
import static org.junit.Assert.*;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
/**
*DH校验
*@author梁栋
*@version 1.0
*/
public class DHCoderTest{
//甲方公钥
private byte[]publicKey1;
//甲方私钥
private byte[]privateKey1;
//甲方本地密钥
private byte[]key1;
//乙方公钥
private byte[]publicKey2;
//乙方私钥
private byte[]privateKey2;
//乙方本地密钥
private byte[]key2;
/**
*初始化密钥
*@throws Exception
*/
@Before
public final void initKey()throws Exception{
//生成甲方密钥对
Map<String, Object>keyMap1=DHCoder.initKey();
publicKey1=DHCoder.getPublicKey(keyMap1);
privateKey1=DHCoder.getPrivateKey(keyMap1);
System.err.println("甲方公钥:\n"+Base64.encodeBase64String
(publicKey1));
System.err.println("甲方私钥:\n"+Base64.encodeBase64String
(privateKey1));
//由甲方公钥产生本地密钥对
Map<String, Object>keyMap2=DHCoder.initKey(publicKey1);
publicKey2=DHCoder.getPublicKey(keyMap2);
privateKey2=DHCoder.getPrivateKey(keyMap2);
System.err.println("乙方公钥:\n"+Base64.encodeBase64String
(publicKey2));
System.err.println("乙方私钥:\n"+Base64.encodeBase64String
(privateKey2));
key1=DHCoder.getSecretKey(publicKey2,privateKey1);
System.err.println("甲方本地密钥:\n"+Base64.encodeBase64String(key1));
key2=DHCoder.getSecretKey(publicKey1,privateKey2);
System.err.println("乙方本地密钥:\n"+Base64.encodeBase64String(key2));
//校验
assertArrayEquals(key1,key2);
}
/**
*校验
*@throws Exception
*/
@Test
public final void test()throws Exception{
System.err.println("\n=====甲方向乙方发送加密数据=====");
String input1="密码交换算法";
System.err.println("原文:"+input1);
System.err.println("—-使用甲方本地密钥对数据加密—-");
//使用甲方本地密钥对数据加密
byte[]code1=DHCoder.encrypt(input1.getBytes(),key1);
System.err.println("加密:"+Base64.encodeBase64String(code1));
System.err.println("—-使用乙方本地密钥对数据解密—-");
//使用乙方本地密钥对数据解密
byte[]decode1=DHCoder.decrypt(code1,key2);
String output1=(new String(decode1));
System.err.println("解密:"+output1);
assertEquals(input1,output1);
System.err.println("\n=====乙方向甲方发送加密数据=====");
String input2="DH";
System.err.println("原文:"+input2);
System.err.println("—-使用乙方本地密钥对数据加密—-");
//使用乙方本地密钥对数据加密
byte[]code2=DHCoder.encrypt(input2.getBytes(),key2);
System.err.println("加密:"+Base64.encodeBase64String(code2));
System.err.println("—-使用甲方本地密钥对数据解密—-");
//使用甲方本地密钥对数据解密
byte[]decode2=DHCoder.decrypt(code2,key1);
String output2=(new String(decode2));
System.err.println("解密:"+output2);
//校验
assertEquals(input2,output2);
}
}
按照惯例,我们使用Base64算法对密钥编码,在控制台得到相关信息,如以下代码所示:
甲方公钥:
MIHgMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHz
W5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG
kx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgANEAAJBAJei+kbWCpbA
EiXXcMrj991qHahR6nSQ4EfKqBah4+apd+zV92iy1C+rwsv8ea2V43zhesySZcylPnosfBl7haA=
甲方私钥:
MIHRAgEAMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYX
rgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpD
TWSGkx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgAQyAjA1b4WOjC0x
efoTnRX9Y7cZ5EhVh2lqaOZxdaHMA1U4LMKQZQmNIewW796UkVWeW9o=
乙方公钥:
MIHfMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHz
W5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG
kx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgANDAAJAEdxKB72W1Sgq
/QRhh7WOXS31arbo55hEdLDn9vL5PT26rlaEQ5i2wECCJ0zXHIXInQHaxbftwP734Ar/vvMMBQ==
乙方私钥:
MIHSAgEAMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYX
rgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpD
TWSGkx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgAQzAjEAog1vG/Nm
PHuVfVUHe53MkO1jTNDXYFPpY92+G6BCEzeweNDEoRj8EVQdClf7xz+t
不论公钥还是私钥,不论甲方还是乙方对密钥的长度都难以接受。若作者选择1024位的密钥长度,控制台输出的密钥信息打印出来恐怕要够两页A4纸了。
甲乙方的本地密钥是否相同呢?我们将其通过Base64编码,在控制台中得到如下信息:
甲方本地密钥:
FNDVElg+KyfttpT81YWHsiaTYNs2e0fzhf8OwCDWUGs=
乙方本地密钥:
FNDVElg+KyfttpT81YWHsiaTYNs2e0fzhf8OwCDWUGs=
显然,甲乙方的本地密钥是一致的。
接下来验证甲方加密的数据乙方是否可以解密,在控制台中得到如下信息:
=====甲方向乙方发送加密数据=====
原文:密码交换算法
—-使用甲方本地密钥对数据加密—-
加密:IyzI6a6HyEq4927UoHQ8jIgMrA9RFtKf2Hcd/503eTM=
—-使用乙方本地密钥对数据解密—-
解密:密码交换算法
反之,验证乙方加密的数据甲方是否可以解密,在控制台中得到如下信息:
=====乙方向甲方发送加密数据=====
原文:DH
—-使用乙方本地密钥对数据加密—-
加密:OVvejG+sj98I7BIqLGWpmA==
—-使用甲方本地密钥对数据解密—-
解密:DH
通过上述测试,我们可以验证:经由甲乙双方构建的秘密密钥相同,基于DH算法实现的加密通信系统实际上是使用同一个秘密密钥完成相应的加密/解密对称加密系统。
对于DH算法而言,算法的安全强度在于密钥长度和对称加密算法的选择。
❑DH算法支持的密钥长度为64的倍数位,取值范围在512~1024位(含1024位)之间。密钥长度与加密安全强度成正比,与运算时间成反比。在使用时,需选择合适的密钥长度。
❑对称加密算法可以选择DES、DESede和AES算法等。
合理选择密钥长度和对称加密算法是构建基于DH算法密码系统的关键。