七、 高等数据加密——非对称加密算法
我们可能没有在瑞士苏黎世银行存入巨额资产的机会,但相信大多数人都在电影中见到这样一组镜头:户主带着自己的钥匙来到银行,要求取出自己寄放的物品。银行工作人员验明户主身份后,拿出另一把钥匙同户主一起打开保险柜,将用户寄放的物品取出。我们可以把这个保险柜称为“双钥保险柜”。
与第6部分提到的密码日记本不同,要开启双钥保险柜同时需要两把钥匙:一把钥匙留给户主,由户主保管,我们可以把它叫做“公钥”;另一把钥匙留给银行,由银行保管,我们可以把它叫做“私钥”。
如果将上述内容引申为加密/解密应用,我们就能体会到非对称加密算法与对称加密算法之间的区别。对称加密算法仅有一个密钥,既可用于加密,亦可用于解密。若完成加密/解密操作,仅需要一个密钥即可;而非对称加密算法拥有两个密钥,一个用于加密,另一个则用于解密。若要完成加密/解密操作,需要两个密钥同时参与。
相比与对称加密算法的单钥体系,非对称加密算法的双钥体系就更为安全。但非对称加密的缺点是加解密速度要远远慢于对称加密,在某些极端情况下,非对称加密算法甚至比非对称加密要慢1000倍。
7.1 非对称加密算法概述
非对称加密算法与对称加密算法的主要差异在于非对称加密用于加密和解密的密钥不相同,一个公开,称为公钥;一个保密,称为私钥。因此,非对称加密也称为双钥或公钥加密算法。
非对称加密算法解决了 对称加密算啊密钥分配问题,病极大地提高了算法安全性。多种B2C或B2B应用均使用非对称加密算法作为数据加密的核心算法。
7.1.1 非对称加密算法的由来
密钥管理是对称加密算法系统不容忽视的问题,它成为安全系统中最为薄弱的环节。为了弥补这一弱势,非对称加密算法应运而生。
1976年,W.Diffie和M.Hellman在IEEE(Institute of Electrical and Electronics Engineers,美国电气和电子工程师协会)的刊物上发表《密码学的新方向》一文,首次提出非对称加密算法。
非对称加密算法有别于对称加密算法,将密钥一分为二,公钥公开,私钥保密。公钥通过非安全通道发放,私钥则由发放者保留。公钥与私钥相对应,成对出现。公钥加密的数据,只可使用私钥对其解密。反之,私钥加密的数据,只可使用公钥对其解密。
非对称加密算法与对称加密算法相比,密钥管理问题不复存在,在安全性上有着无法逾越的高度,但却无法回避加密/解密效率低这一问题。因此,非对称加密算法往往应用在一些安全性要求相当高的领域,如B2C、B2B等电子商务平台。注意 针对非对称加密算法的低效问题,各密码学机构主张将对称加密算法与非对称加密算法相结合,使用对称加密算法为数据加密/解密,使用公钥和私钥为对称加密算法密钥加密/解密。利用对称加密算法的高效性,加之非对称加密算法的密钥管理,提升整体加密系统的安全性。在算法设计上,非对称加密算法对待加密数据长度有着极为苛刻的要求。例如,RSA算法要求待加密数据不得大于53个字节。非对称加密算法主要用于交换对称加密算法的秘密密钥,而非数据交换。
7.1.2 非对称加密算法的家谱
非对称加密算法源于DH算法(Diffie-Hellman,密钥交换算法),由W.Diffie和M.Hellman共同提出。该算法为非对称加密算法奠定了基础,堪称非对称加密算法之鼻祖。
DH算法提出后,国际上相继出现了各种实用性更强的非对称加密算法,其构成主要是基于数学问题的求解,主要分为两类:
基于因子分解难题。RSA算法是最为典型的非对称加密算法,该算法由美国麻省理工学院(MIT)的Ron Rivest、AdiShamir和Leonard Adleman三位学者提出,并以这三位学者的姓氏开头字母命名,称为RSA算法。RSA算法是当今应用范围最为广泛的非对称加密算法,也是第一个既能用于数据加密也能用于数字签名的算法。
基于离散对数难题。ElGamal算法由Taher ElGamal提出,以自己的名字命名。该算法既可用于加密/解密,也可用于数字签名,并为数字签名算法形成标准提供参考。美国的DSS(Digital Signature Standard,数据签名标准)的DSA(Digital Signature Algorithm,数字签名算法)经ElGamal算法演变而来。
ECC(Elliptical Curve Cryptography,椭圆曲线加密)算法以椭圆曲线理论为基础,在创建密钥时可做到更快、更小,并且更有效。ECC 算法通过椭圆曲线方程式的性质产生密钥,而不是采用传统的方法利用大质数的积来产生。
在Java 6中仅提供了DH和RSA两种算法实现,而在Java 7中新增了ECDH算法实现,用于增强密钥交换算法,提升HTTPS服务的安全性。通过Boucy Castle可以获得ElGamal算法支持。
7.2 密钥交换算法——DH&ECDH
对称加密算法提高数据安全性的同时带来了密钥管理的复杂性。消息收发双方若想发送加密消息,必须事先约定好加密算法并发放密钥。而如何安全的传递密钥,成为对称加密算法的一个棘手问题。密钥交换算法(Diffie-Hellman算法,简称DH算法)成为解决这一问题的金钥匙!
7.2.1 简述
1976年,W.Diffie和M.Hellman在发表的论文中提出了公钥加密算法思想,但当时并没有给出具体的实施方案,原因在于没有找到单向函数(也就是消息摘要算法),但在该文中给出了通信双方通过信息交换协商密钥的算法,即Diffie-Hellman密钥交换算法(通常简称为DH算法)。该算法的目的在于让消息的收发双方可以在安全的条件下交换密钥,以备后续加密/解密使用。因此,DH算法是第一个密钥协商算法,但仅能用于密钥分配,不能用于加密或解密消息。
DH密钥交换算法的安全性基于有限域上的离散对数难题。基于这种安全性,通过DH算法进行密钥分配,使得消息的收发双方可以安全地交换一个秘密密钥,再通过这个密钥对数据进行加密和解密处理。
ECDH是基于椭圆曲线加密算法的密钥交换算法(这里将ECC简化为EC,同知椭圆曲线加密算法),密钥较短但密钥安全性更强,其原理与DH算法完全一致。
7.2.2 DH实现
Java 7提供了DH算法的相关实现,相关Java API的知识可阅读第3章内容。作为对称加密算法向非对称加密算法的一种过渡,DH算法的实现是我们了解非对称加密算法的最佳途径。
有关DH算法的Java 7实现细节如表所示。
上图为DH算法
所有非对称算法实现的公有方法均不使用java.security和javax.crypto包及其子包中的接口或类作为参数或返回值。
实现DH算法密钥需要用到密钥对及密钥对生成器,如代码清单7-1所示。
代码清单7-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)强行转换上述密钥。
上述代码完成了甲方密钥的构建,要构建乙方密钥需要使用甲方公钥,具体实现如代码清单7-2所示。
代码清单7-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对象
封装密钥,如代码清单7-3所示。
代码清单7-3 封装DH算法密钥
// 将密钥对存储在Map中
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put("DHPublicKey", publicKey);
keyMap.put("DHPrivateKey", privateKey);
我们将在后续的initKey()方法中见到上述代码。
如果要将密钥材料转换为密钥对象,可参考代码清单7-4。
代码清单7-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);
完成甲乙方密钥的构建操作后,我们便可以完成本地密钥的构建。这里,要求使用不对称的公钥和私钥来构建本地密钥,即使用甲方私钥和乙方公钥构建甲方本地密钥;使用乙方私钥和甲方公钥构建乙方本地密钥。相关实现如代码清单7-5所示。
代码清单7-5 构建本地密钥
// 实例化
KeyAgreement keyAgree = KeyAgreement.getInstance(keyFactory.getAlgorithm());
// 初始化
keyAgree.init(priKey);
keyAgree.doPhase(pubKey, true);
// 生成本地密钥
SecretKey secretKey = keyAgree.generateSecret("AES");
完成了上述准备后,我们接下来仅仅需要使用本地密钥进行加密/解密操作了。本地密钥就是对称加密算法中的秘密密钥,对于对称加密算法的加密/解密操作,我们在第7章中已经详尽描述,就不在此复述了。完整的代码实现如代码清单7-6所示。
代码清单7-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安全编码组件
* @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);
}
/**
*构建密钥
*@param publicKey 公钥
*@param privateKey私钥
*@return byte[]本地密钥
*@throw 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);
teturn secretKey.getEncoded();
}
/**
*@param keyMap 密钥Map
*@return byte[]私钥
*@throw 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算法测试用例如代码清单7-7所示。
代码清单7-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校验
* @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算法对密钥编码,在控制台得到相关信息,如以下代码:
甲方公钥:
MIHgMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG
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算法密码系统的关键。
7.2.3 ECDH实现
从Java 7开始,增加了椭圆曲线密钥交换算法ECDH,其原理与DH算法相同,在实现细节上略有不同。有关ECDH算法的Java 7实现细节如下表所示。
在代码实现方面,ECDH算法与DH算法的差别主要是密钥长度和对称加密算法。
ECDH算法密钥长度似乎没有什么规律,官方也没有明确说明。从作者对密钥长度的测试结果来看,ECDH算法密钥长度共有3种:112、256和571位。
在Java 6中,ECDH算法密钥生成是个很困难的工作,需要一段冗长的代码,且生成一对固定密钥,非常不方便。而在Java 7中,密钥生成和其他非对称加密算法完全一致,如代码清单7-8所示。
代码清单7-8 ECDH密钥生成
// 实例化密钥对生成器
KeyPairGenerator keyPairGenerator =KeyPairGenerator.getInstance(“ECDH”);
// 初始化密钥对生成器
keyPairGenerator.initialize(571);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 甲方公钥
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
// 甲方私钥
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
这里主要注意初始化密钥长度——112、256或571位。
ECDH算法的密钥实际上也是EC算法的密钥,初始化密钥对生成器时,使用“EC”代替“ECDH”也可以。ECDH算法对交换的密钥算法支持还不能像DH算法那样全面,作者尝试使用AES、DES和DESede算法构建本地密钥均告失败,但Blowfish、RC2和RC4都可以正常使用。详见代码清单7-9所示。
代码清单7-9 密钥对初始化和本地密钥初始化
// 初始化密钥对,EC或ECDH
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
// …
// 生成本地密钥,Blowfish、RC2或RC4
SecretKey secretKey = keyAgree.generateSecret("Blowfish");
完整的DH算法测试用例如代码清单7-10所示。
代码清单7-10 ECDH算法实现
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
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.spec.SecretKeySpec;
/**
* ECDH安全编码组件
* @version 1.0
*/
public abstract class ECDHCoder {
/**
* 非对称加密密钥算法,EC或ECDH
*/
public static final String KEY_ALGORITHM = "ECDH";
/**
* 本地密钥算法,即对称加密密钥算法,可选Blowfish、RC2、RC4 算法
*/
public static final String SECRET_ALGORITHM = "Blowfish";
/**
* 密钥长度
*
* ECDH算法默认密钥长度为256,其范围在112到571位之间
*/
private static final int KEY_SIZE = 256;
/**
* 公钥
*/
private static final String PUBLIC_KEY = “ECPublicKey”;
/*
* 私钥
*/
private static final String PRIVATE_KEY = “ECPrivateKey”;
/**
* 初始化甲方密钥
*
* @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();
// 甲方公钥
ECPublic KeypublicKey = (ECPublicKey) keyPair.getPublic();
// 甲方私钥
ECPrivateKey privateKey = (ECPrivateKey) 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);
// 由甲方公钥构建乙方密钥
ECParameterSpec ecParamSpec = ((ECPublicKey) pubKey).getParams();
// 实例化密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.
getInstance(keyFactory.getAlgorithm());
// 初始化密钥对生成器
keyPairGenerator.initialize(ecParamSpec);
// 产生密钥对
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 乙方公钥
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
// 乙方私钥
ECPrivateKey privateKey = (ECPrivateKey) 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 {
// 实例化密钥工厂
KeyFactorykeyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 初始化公钥
// 密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);
// 产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// 初始化私钥
// 密钥材料转换
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8
EncodedKey Spec(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算法实现差别不大,请读者自行对比。测试用例如代码清单7-11所示。
代码清单7-11 ECDH算法实现测试用例
import static org.junit.Assert.*;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
/**
* ECDH校验
*
* @author 梁栋
* @version 1.0
*/
public class ECDHCoderTest {
/**
* 甲方公钥
*/
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 = ECDHCoder.initKey();
publicKey1 = ECDHCoder.getPublicKey(keyMap1);
privateKey1 = ECDHCoder.getPrivateKey(keyMap1);
System.err.println("甲方公钥:\n" + Base64.encodeBase64String(publicKey1));
System.err.println("甲方私钥:\n" + Base64.encodeBase64String(privateKey1));
// 由甲方公钥产生本地密钥对
Map<String, Object> keyMap2 = ECDHCoder.initKey(publicKey1);
publicKey2 = ECDHCoder.getPublicKey(keyMap2);
privateKey2 = ECDHCoder.getPrivateKey(keyMap2);
System.err.println("乙方公钥:\n" + Base64.encodeBase64String(publicKey2));
System.err.println("乙方私钥:\n" + Base64.encodeBase64String(privateKey2));
key1 = ECDHCoder.getSecretKey(publicKey2,privateKey1);
System.err.println("甲方本地密钥:\n" + Base64.encodeBase64String(key1));
key2 = ECDHCoder.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 = ECDHCoder.encrypt(input1.getBytes(), key1);
System.err.println("加密: " + Base64.encodeBase64String(code1));
System.err.println("---使用乙方本地密钥对数据解密---");
// 使用乙方本地密钥对数据解密
byte[] decode1 = ECDHCoder.decrypt(code1, key2);
String output1 = (new String(decode1));
System.err.println("解密: " + output1);
assertEquals(input1, output1);
System.err.println("\n=====乙方向甲方发送加密数据=====”);
String input2 = "ECDH”;
System.err.println("原文: " + input2);
System.err.println("---使用乙方本地密钥对数据加密---");
// 使用乙方本地密钥对数据加密
byte[] code2 = ECDHCoder.encrypt(input2.getBytes(), key2);
System.err.println("加密: " + Base64.encodeBase64String(code2));
System.err.println("---使用甲方本地密钥对数据解密---");
// 使用甲方本地密钥对数据解密
byte[] decode2 = ECDHCoder.decrypt(code2, key1);
String output2 = (new String(decode2));
System.err.println("解密: " + output2);
// 校验
assertEquals(input2,output2);
}
}
ECDH和DH算法使用流程一致,上述测试用例操作流程也一样。
控制台对应输出如下双方公私钥以及本地密钥:
甲方公钥:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0E4OcYXDT1qTWiAsLovp0qFqmNFbYoA7FQc
MRYLp7llwn/nThHs/EycWfAAsVk31tpgUWbSfriL9Y0pUE7eFsg==
甲方私钥:
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg3fezhSzKiW+snx4rcYeDllF79tl
JgJssjvXHtmS/dDugCgYIKoZIzj0DAQehRANCAATQTg5xhcNPWpNaICwui+nSoWqY0VtigDsVBwx
FgunuWXCf+dOEez8TJxZ8ACxWTfW2mBRZtJ+uIv1jSlQTt4Wy
乙方公钥:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+SHDmslF2cZShJ9qmZk4uODPdxZ8ZQQyoOb5GMLtN
/9GXBJN0RkZoN2gZO94zbINvmMLxcmu7kCpbbOD4AirNg==
乙方私钥:
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgo+t/0myl16mlpoaf2E7iH2FAF
gJ+z+sM7nYsIUWuR4ygCgYIKoZIzj0DAQehRANCAAT5IcOayUXZxlKEn2qZmTi44M93FnxlBDKg
5vkYwu03/0ZcEk3RGRmg3aBk73jNsg2+YwvFya7uQKlts4PgCKs2
甲方本地密钥:
HAXl7KzDUOX1B19Incsw/299Flg/RohMbGZMOGZEzaY=
乙方本地密钥:
HAXl7KzDUOX1B19Incsw/299Flg/RohMbGZMOGZEzaY=
接下来甲乙双方互相传递加密数据:
=====甲方向乙方发送加密数据=====
原文: 密码交换算法
---使用甲方本地密钥对数据加密---
加密: JG7kZbegknXj86EYYUz9cJXRcWH+Zc/G
---使用乙方本地密钥对数据解密---
解密: 密码交换算法
=====乙方向甲方发送加密数据=====
原文: ECDH
---使用乙方本地密钥对数据加密---
加密: 00Na9Qey/FU=
---使用甲方本地密钥对数据解密---
解密: ECDH
甲乙双方并没有交换本地密钥,同样可以完成数据的对称加密实现,这就是DH算法的精髓。
7.3 典型非对称加密算法——RSA
DH算法的诞生为后续非对称加密算法奠定了基础,较为典型的对称加密算法(如ElGamal、RSA和ECC算法等)都是在DH算法提出后相继出现的,并且其算法核心都源于数学问题。
RSA算法基于大数因子分解难题,而ElGamal算法和ECC算法则是基于离散对数难题。
目前,各种主流计算机语言都提供了对RSA算法的支持。在Java 7中,我们可以很方便地构建该算法。
7.3.1 简述
1978年,美国麻省理工学院(MIT)的Ron Rivest、Adi Shamir和Leonard Adleman三位学者提出了一种新的非对称加密算法,这种算法以这三位学者的姓氏开头字母命名,被称为RSA算法。
RSA算法是唯一被广泛接受并实现的通用公开加密算法,目前已经成为非对称加密算法国际标准。不仅如此,RSA算法既可用于数据加密也可用于数字签名。我们熟知的电子邮件加密软件PGP就采用了RSA算法对数据进行加密/解密和数字签名处理。
RSA算法将非对称加密算法推向了一个高潮,但它并不是没有缺点。相比DES以及其他对称加密算法而言,RSA算法要慢得多。作为加密算法,尤其是作为非对称加密算法的典型,针对RSA算法的破解自诞生之日起就从未停止过。
1999年,RSA-155(512位)被成功因数分解,历时5个月。
2002年,RSA-158也被成功因数分解。
当然,基于大数因子分解数学难题的非对称加密算法仅有RSA算法这一种,但这不代表非对称加密算法除了RSA算法后继无人。基于离散对数问题的非对称加密算法包括ElGamal和ECC这两种算法,它们同样是优秀的非对称加密算法。
7.3.2 实现
Java 7提供了RSA算法实现,相关内容请读者朋友阅读第3章。RSA算法在理解上较之DH算法更为简单,在实现层面上也同样如此。
RSA算法与DH算法在密钥管理和加密/解密两方面有所不同:一方面,甲方保留了私钥,而将公钥公布于乙方,甲乙双方密钥一一对应;另一方面,私钥用于解密,公钥则用于加密,反之亦然。
这里要注意一点,在RSA算法中,公钥既可用于解密也可用于加密,私钥也是如此。但公钥加密的数据只能用私钥对其解密,而私钥加密的数据只能用公钥对其解密,这种对应关系是不能违背的。
RSA算法实现较之DH算法实现较为简单,大部分内容与DH算法较为接近。与DH算法不同的是,RSA算法仅需要一套密钥即可完成加密/解密操作。Bouncy Castle对RSA算法做了相应扩充,增加了ISO9796-1Padding填充方式。
有关RSA算法的Java 7实现与Bouncy Castle实现细节如下表所示。
表 RSA算法
完整实现如代码清单7-12所示。
代码清单7-12 RSA算法实现
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
/**
* RSA安全编码组件
* @version 1.0
*/
public abstract class RSACoder {
// 非对称加密密钥算法
public static final String KEY_ALGORITHM = "RSA";
// 公钥
private static final String PUBLIC_KEY = "RSAPublicKey";
// 私钥
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* RSA密钥长度
* 默认1024位,
* 密钥长度必须是64的倍数,
* 范围在512~65536位之间。
*/
private static final int KEY_SIZE = 512;
/**
* 私钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
* @param data 待解密数据
* @param key 公钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成公钥
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
* @param data 待加密数据
* @param key 公钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 私钥加密
* @param data 待加密数据
* @param key 私钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 取得私钥
* @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();
}
/**
* 初始化密钥
* @return Map 密钥Map
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
// 实例化密钥对生成器
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance (KEY_ALGORITHM);
// 初始化密钥对生成器
keyPairGen.initialize(KEY_SIZE);
// 生成密钥对
KeyPair keyPair = keyPairGen.generateKeyPair();
// 公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 封装密钥
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
}
RSA算法实现易于理解,对于RSA算法的测试只需要注意经公钥加密的数据是否可以通过私钥将其解密,反之,经私钥加密的数据是否可以通过公钥将其解密。完整的测试用例如代码清单7-13所示。
代码清单7-13 RSA算法实现测试用例
import static org.junit.Assert.*;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
import java.util.Map;
/**
* RSA校验
* @version 1.0
*/
public class RSACoderTest {
// 公钥
private byte[] publicKey;
// 私钥
private byte[] privateKey;
/**
* 初始化密钥
* @throws Exception
*/
@Before
public void initKey() throws Exception {
// 初始化密钥
Map<String, Object> keyMap = RSACoder.initKey();
publicKey = RSACoder.getPublicKey(keyMap);
privateKey = RSACoder.getPrivateKey(keyMap);
System.err.println("公钥:\n" + Base64.encodeBase64String(publicKey));
System.err.println("私钥:\n" + Base64.encodeBase64String(privateKey));
}
/**
* 校验
* @throws Exception
*/
@Test
public void test() throws Exception {
System.err.println("\n---私钥加密--公钥解密---");
String inputStr1 = "RSA加密算法";
byte[] data1 = inputStr1.getBytes();
System.err.println("原文:\n" + inputStr1);
// 加密
byte[] encodedData1 = RSACoder.encryptByPrivateKey(data1, privateKey);
System.err.println("加密后:\n" + Base64.encodeBase64String (encodedData1));
// 解密
byte[] decodedData1 = RSACoder.decryptByPublicKey(encodedData1,
publicKey);
String outputStr1 = new String(decodedData1);
System.err.println("解密后:\n" + outputStr1);
// 校验
assertEquals(inputStr1, outputStr1);
System.err.println("\n---公钥加密--私钥解密---");
String inputStr2 = "RSA Encypt Algorithm";
byte[] data2 = inputStr2.getBytes();
System.err.println("原文:\n" + inputStr2);
// 加密
byte[] encodedData2 = RSACoder.encryptByPublicKey(data2, publicKey);
System.err.println("加密后:\n" + Base64.encodeBase64String (encodedData2));
// 解密
byte[] decodedData2 = RSACoder.decryptByPrivateKey(encodedData2,
privateKey);
String outputStr2 = new String(decodedData2);
System.err.println("解密后: " + outputStr2);
// 校验
assertEquals(inputStr2, outputStr2);
}
}
在控制台中输出了密钥的Base64编码信息,如下文所示:
公钥:
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ0PYlHiU439wWfWvQKMEgjjE/swks9KCG0UJy2qmSBs
2e76f0eTswnNl7nFxbBr5TRRyl4EhkudWDIr0u6kBgcCAwEAAQ==
私钥:
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAnQ9iUeJTjf3BZ9a9AowSCOMT+zCS
z0oIbRQnLaqZIGzZ7vp/R5OzCc2XucXFsGvlNFHKXgSGS51YMivS7qQGBwIDAQABAkAjMB4sEFP9
/PtG43KHTpB/0zhXz8MklAadQaWhcpZKEB3LRINLH4jJ2UJxGWXMDS5nZtbG3/1jYd2Ee0bwGb7B
AiEA8M/t/6MB4Yx4C4OBkd51TDByKJmKIEnFu2HSyzA6c2ECIQCm9zi9OuX2N/5rVytXfA+Oj7L1
wetSOrGbEHXX3D/aZwIhAMRHJlyr13eoj5wK1ww2/vJXtmSjKPNCThl6FV8p8yphAiBVJyC44aEG
wefvtrVUGOGWQ5Nx40Sw215ZRzvSq3GlYQIhALIHY3fxmLlEg3NC269ouFsYeTF/EO+M02+pazz+
3UPv
与DH算法不同,RSA算法仅需要一套密钥即可完成加密/解密操作,并且我们发现公钥的密钥长度明显小于私钥的密钥长度,更便于发送和携带。
私钥加密公钥解密,如下所示:
---私钥加密--公钥解密---
原文:
RSA加密算法
加密后:
EePGm+yWtFvgSvc1pmh1hNoy3KyH0gssjc2FlvPSNkFAOOFOvvVIPQAmeRtTD+L3oUKUC61zQeqf
N2B/t0ylxg==
解密后:
RSA加密算法
反之,公钥加密私钥解密,如下所示:
---公钥加密--私钥解密---
原文:
RSA Encypt Algorithm
加密后:
hehNcGA8EGilNk3FJ7snGOU9XKGHN7t6DJlHQG9Ddi+h/xdk/IzWs3+SJfFsEnrTQe+96UvpEmF2
atA7+Fndgw==
解密后: RSA Encypt Algorithm
RSA算法公钥长度远小于私钥长度,并遵循“公钥加密,私钥解密”和“私钥加密,公钥解密”这两项加密/解密原则。
7.4 常用非对称加密算法——ElGamal
在非对称加密算法中,几乎所有的算法都是基于数学问题而建立的:RSA算法基于大数因子分解数学难题,而ElGamal算法和ECC算法则基于离散对数问题。与典型非对称加密算法RSA算法相比,ElGamal算法则被称为常用非对称加密算法。
7.4.1 简述
1985年,Taher ElGamal提出了一种基于离散对数问题的非对称加密算法,该算法既可用于加密,又可用于数字签名,是除了RSA算法外最具有代表性的公钥加密算法之一。Taher ElGamal用自己的名字定义了这种算法——ElGamal算法。
由于ElGamal算法具有较好的安全性,因此得到了广泛的应用。著名的美国数字签名标准(Digital Signature Standard,DSS)就是采用了ElGamal签名方案的一种变形——DSA(Digital Signature Algorithm)。ElGamal的一个不足之处是它的密文会成倍扩张。
7.4.2 实现
很遗憾,作为常用非对称加密算法的ElGamal算法并没有出现在Java 7的API中,但却包含在了Bouncy Castle的API中,弥补了Java语言缺少对ElGamal算法支持的缺憾。
有关ElGamal算法的Bouncy Castle实现细节如表所示。
表 ElGamal算法
JCE框架为其他非对称加密算法实现提供了一个构建密钥对方式,均基于DH算法参数材料——DHParameterSpec类。代码清单7-14展示了如何构建ElGamal算法密钥对。
代码清单7-14 构建ElGamal算法密钥对
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// 省略
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
// 实例化算法参数生成器
AlgorithmParameterGenerator apg = AlgorithmParameterGenerator.getInstance(KEY_ALGORITHM);
// 初始化算法参数生成器
apg.init(KEY_SIZE);
// 生成算法参数
AlgorithmParameters params = apg.generateParameters();
// 构建参数材料
DHParameterSpec elParams = (DHParameterSpec) params.getParameterSpec(DHParameterSpec.class);
// 实例化密钥对生成器
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 初始化密钥对生成器
kpg.initialize(elParams, new SecureRandom());
// 生成密钥对
KeyPair keys = kpg.genKeyPair();
有了密钥对实例化对象keys自然就可以获得相应的密钥了。
需要注意的是,这里我们使用Bouncy Castle提供的ElGamal算法实现,在使用各种引擎类做实例化操作前,需导入Bouncy Castle提供者,如代码清单7-15所示。
代码清单7-15 导入Bouncy Castle提供者
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// 省略
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
抛开密钥对的生成实现,ElGamal算法实现与RSA算法实现几乎一致,如代码清单7-16所示。
代码清单7-16 ElGamal算法实现
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
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.spec.DHParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* ElGamal安全编码组件
* @version 1.0
* @since 1.0
*/
public abstract class ElGamalCoder {
// 非对称加密密钥算法
public static final String KEY_ALGORITHM = "ElGamal";
/**
* 密钥长度
* ElGamal算法
* 默认密钥长度为1024
* 密钥长度范围在160~16384位不等,
* 且密钥长度必须是8的倍数。
*/
private static final int KEY_SIZE = 256;
// 公钥
private static final String PUBLIC_KEY = "ElGamalPublicKey";
// 私钥
private static final String PRIVATE_KEY = "ElGamalPrivateKey";
/**
* 用私钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
// 私钥材料转换
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
// 实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成私钥
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 用公钥加密
* @param data 待加密数据
* @param key 公钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
// 公钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
// 实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成公钥
Key publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 生成密钥
* @return Map 密钥Map
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
// 加入BouncyCastleProvider支持
Security.addProvider(new BouncyCastleProvider());
// 实例化算法参数生成器
AlgorithmParameterGenerator apg = AlgorithmParameterGenerator.getInstance (KEY_ALGORITHM);
// 初始化算法参数生成器
apg.init(KEY_SIZE);
// 生成算法参数
AlgorithmParameters params = apg.generateParameters();
// 构建参数材料
DHParameterSpec elParams = (DHParameterSpec) params.getParameterSpec (DHParameterSpec.class);
// 实例化密钥对生成器
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 初始化密钥对生成器
kpg.initialize(elParams, new SecureRandom());
// 生成密钥对
KeyPair keys = kpg.genKeyPair();
// 取得密钥
PublicKey publicKey = keys.getPublic();
PrivateKey privateKey = keys.getPrivate();
// 封装密钥
Map<String, Object> map = new HashMap<String, Object>(2);
map.put(PUBLIC_KEY, publicKey);
map.put(PRIVATE_KEY, privateKey);
return map;
}
/**
* 取得私钥
* @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
* @return
* @throws Exception
*/
public static byte[] getPublicKey(Map<String, Object> keyMap)throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return key.getEncoded();
}
}
ElGamal算法的测试用例与RSA算法测试用例较为相近。不同的是,ElGamal算法实现仅有“公钥加密,私钥解密”的部分。测试用例如代码清单7-17所示。
代码清单7-17 ElGamal算法实现测试用例
import static org.junit.Assert.*;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Test;
/**
* ElGamal校验
* @author 梁栋
* @version 1.0
*/
public class ElGamalCoderTest {
// 公钥
private byte[] publicKey;
// 私钥
private byte[] privateKey;
/**
* 初始化密钥
* @throws Exception
*/
@Before
public void initKey() throws Exception {
Map<String, Object> keyMap = ElGamalCoder.initKey();
publicKey = ElGamalCoder.getPublicKey(keyMap);
privateKey = ElGamalCoder.getPrivateKey(keyMap);
System.err.println("公钥: \n" + Base64.encodeBase64String(publicKey));
System.err.println("私钥: \n" + Base64.encodeBase64String(privateKey));
}
/**
* 校验
* @throws Exception
*/
@Test
public void test() throws Exception {
String inputStr = "ElGamal加密";
byte[] data = inputStr.getBytes();
System.err.println("原文: \n" + inputStr);
byte[] encodedData = ElGamalCoder.encryptByPublicKey(data, publicKey);
System.err.println("加密后: \n" + Base64.encodeBase64String(encodedData));
byte[] decodedData = ElGamalCoder.decryptByPrivateKey(encodedData, privateKey);
String outputStr = new String(decodedData);
System.err.println("解密后: \n" + outputStr);
assertEquals(inputStr, outputStr);
}
}
在控制台中的输出信息中,我们可以得到相应的公钥和私钥,代码如下所示:
公钥:
MHcwUAYGKw4HAgEBMEYCIQCutlvZWBGgITJngn6hyMJ/VC/vt7K47W2p7QZdk+xpDwIhAKr3JJo1
jqbZp0YJSeBceSDLL7fJOUATmOzEyXhv0kRcAyMAAiA6HMzcFJSyF78uBXzemyHNFbXOFF0plX15
17p31YQqjQ==
私钥:
MHkCAQAwUAYGKw4HAgEBMEYCIQCutlvZWBGgITJngn6hyMJ/VC/vt7K47W2p7QZdk+xpDwIhAKr3
JJo1jqbZp0YJSeBceSDLL7fJOUATmOzEyXhv0kRcBCICIBecbEByJs28q7NH69zA2xDsjYbx9ihc
IZSZzKO8z/Dn
仔细观察,我们发现公钥和私钥的长度几乎是一致的。
观察控制台的输出信息,加密/解密信息如下代码所示:
原文:
ElGamal加密
加密后:
T92lluoBzFrAkly8I6b9PX9MuuGTiUAcGmn4Zw+iNYozA1BtX/RkhLTtPzDobJQKLAUV3fLN7Jeq
GZsgXC8gOA==
解密后:
ElGamal加密
ElGamal算法公钥和私钥长度几乎一致,基于Bouncy Castle加密组件的ElGamal算法实现仅遵循“公钥加密,私钥解密”的简单原则。
7.5 实例:非对称加密网络应用
目前,非对称加密算法(主要是RSA算法)主要应用于B2C、B2B等多种电子商务平台。但非对称加密算法并不直接对网络数据进行加密/解密,而是用于交换对称加密算法的秘密密钥。最终使用对称加密算法进行真正的加密/解密。
此处,我们将对第7章的DataServer应用稍作修改,使用非对称加密算法RSA交换对称加密算法AES的秘密密钥,并使用该秘密密钥对数据进行加密/解密。
对于前边用于实现DataServer应用的HttpUtils类和AESCoder类,本节不做详述,请读者阅读相关内容。此处,我们仅对DataServlet类和DataServlet类稍作修改,并增加用于RSA算法实现的RSACoder类。
首先,我们要对本章中的RSACoder类稍作修改:使用开源组件Commons Codec的十六进制转换工具类Hex对密钥进行封装/解包。相关实现如代码清单7-18所示。
代码清单7-18 密钥封装/解包
import org.apache.commons.codec.binary.Hex;
// 省略
/**
* 私钥加密
* @param data 待加密数据
* @param key 私钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception {
return encryptByPrivateKey(data, getKey(key));
}
/**
* 公钥加密
* @param data 待加密数据
* @param key 公钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String key)
throws Exception {
return encryptByPublicKey(data, getKey(key));
}
/**
* 私钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String key)
throws Exception {
return decryptByPrivateKey(data, getKey(key));
}
/**
* 公钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception {
return decryptByPublicKey(data, getKey(key));
}
/**
* 初始化密钥
* @param keyMap 密钥Map
* @return String 十六进制编码密钥
* @throws Exception
*/
public static String getPrivateKeyString(Map<String, Object> keyMap) throws Exception {
return Hex.encodeHexString(getPrivateKey(keyMap));
}
/**
* 初始化密钥
* @param keyMap 密钥Map
* @return String 十六进制编码密钥
* @throws Exception
*/
public static String getPublicKeyString(Map<String, Object> keyMap) throws Exception {
return Hex.encodeHexString(getPublicKey(keyMap));
}
/**
* 获取密钥
* @param key 密钥
* @return byte[] 密钥
* @throws Exception
*/
public static byte[] getKey(String key) throws Exception {
return Hex.decodeHex(key.toCharArray());
}
在实际应用中,常常使用Base64或十六进制编码将密钥转为可见字符存储。十六进制编码要比Base64编码长度小得多,读者可以根据需要选择合适的算法。完整实现如代码清单7-19所示。
代码清单7-19 RSACoder
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Hex;
/**
* RSA安全编码组件
* @version 1.0
*/
public abstract class RSACoder {
// 非对称加密密钥算法
public static final String KEY_ALGORITHM = "RSA";
// 公钥
private static final String PUBLIC_KEY = "RSAPublicKey";
// 私钥
private static final String PRIVATE_KEY = "RSAPrivateKey";
// RSA密钥长度默认1024位,密钥长度必须是64的倍数,范围在512~65536位之间。
private static final int KEY_SIZE = 512;
/**
* 私钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
* @param data 待解密数据
* @param key 公钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成公钥
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
* @param data 待加密数据
* @param key 公钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 私钥加密
* @param data 待加密数据
* @param key 私钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 取得私钥
* @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();
}
/**
* 初始化密钥
* @return Map 密钥Map
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
// 实例化密钥对生成器
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 初始化密钥对生成器
keyPairGen.initialize(KEY_SIZE);
// 生成密钥对
KeyPair keyPair = keyPairGen.generateKeyPair();
// 公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 封装密钥
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[] encryptByPrivateKey(byte[] data, String key) throws Exception {
return encryptByPrivateKey(data, getKey(key));
}
/**
* 公钥加密
* @param data 待加密数据
* @param key 公钥
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception {
return encryptByPublicKey(data, getKey(key));
}
/**
* 私钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception {
return decryptByPrivateKey(data, getKey(key));
}
/**
* 公钥解密
* @param data 待解密数据
* @param key 私钥
* @return byte[] 解密数据
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception {
return decryptByPublicKey(data, getKey(key));
}
/**
* 初始化密钥
* @param keyMap 密钥Map
* @return String 十六进制编码密钥
* @throws Exception
*/
public static String getPrivateKeyString(Map<String, Object> keyMap) throws Exception {
return Hex.encodeHexString(getPrivateKey(keyMap));
}
/**
* 初始化密钥
* @param keyMap 密钥Map
* @return String 十六进制编码密钥
* @throws Exception
*/
public static String getPublicKeyString(Map<String, Object> keyMap) throws Exception {
return Hex.encodeHexString(getPublicKey(keyMap));
}
/**
* 获取密钥
* @param key 密钥
* @return byte[] 密钥
* @throws Exception
*/
public static byte[] getKey(String key) throws Exception {
return Hex.decodeHex(key.toCharArray());
}
}
通过调用RSACoder类的相关方法获得公钥和私钥,相关实现如代码清单7-20所示。
代码清单7-20 RSACoder——构建密钥
// 初始化密钥
Map<String, Object> keyMap = RSACoder.initKey();
// 获得并打印公钥
String publicKey = RSACoder.getPublicKeyString(keyMap);
System.err.println("publicKey - " + publicKey);
// 获得并打印私钥
String privateKey = RSACoder.getPrivateKeyString(keyMap);
System.err.println("privateKey - " + privateKey);
我们在控制台得到相应的公钥与私钥信息,我们将在后续实现中直接使用这些密钥。控制台密钥输出代码如下所示:
publicKey- - 305c300d06092a864886f70d0101010500034b0030480241009fec6cff0209ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a268d877fd8edc2690203010001
privateKey- - 30820153020100300d06092a864886f70d01010105000482013d308201390201000241009fec6cff0209ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a268d877fd8edc26902030100010240275ce73b214256b2e9331388ed1e0a3877ef6443991305d084e44ed5b68ffeb124274a17a3e3dd6e3013011c0602bb6efebebb5d327ebdf8052766f6d8be838d022100d372663308ab6e5c057bc5a56f9b9a16602e2872ded9344fa1dfbaadfb38d24b022100c19ed1dda201473d8fe6433fbfce1486d18782c837d30d4f122f81157a25ed9b0220047a6bc7b0eb508f0a5eb0b4ec4433633dee3c55127b2f2c70953872eedb293902204c451bb68a92a65581d1dabbc9fa8beb6fae49be44ff4646d78b0ef63edfa1f1022010d9524a9febc784bf8ad8dc07a15dee9f47744f9081599b0cc5e18fc37cee3f
接下来,我们来调整DataSerlvet类。这里,我们使用RSA算法对请求的数据进行解密,获得本次交互的对称加密算法密钥。相关实现如代码清单7-21所示。
代码清单7-21 DataServlet——解析密钥
byte[] input = HttpUtils.requestRead(request);
// 对秘密密钥解密
String k = new String(RSACoder.decryptByPrivateKey(input, key));
本文演示程序获得密钥后,使用AES算法将数据加密回复给请求方,相关实现如代码清单7-22所示。
代码清单7-22 DataServlet——加密回复
// 使用AES算法对数据加密并回复
HttpUtils.responseWrite(response, AESCoder.encrypt(output, k));
DataServlet类完整实现如代码清单7-23所示。
代码清单7-23 DataServlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 数据服务DataServlet
* @author 梁栋
* @since 1.0
*/
public class DataServlet extends HttpServlet {
private static final long serialVersionUID = -6219906900195793155L;
// 密钥
private static String key;
// Servlet初始化参数--私钥
private static final String KEY_PARAM = "key";
// 初始化
@Override
public void init() throws ServletException {
super.init();
// 初始化密钥
key = getInitParameter(KEY_PARAM);
}
// 处理POST请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
byte[] input = HttpUtils.requestRead(request);
// 对秘密密钥解密
String k = new String(RSACoder.decryptByPrivateKey(input, key));
// 输出秘密密钥
System.err.println(k);
// 构造数据包
StringBuilder sb = new StringBuilder();
sb.append("<?xmi version=\"1.0\"encoding=]"UTF-8\"?>\r\n");
sb.append("<dataGroup>\r\n");
sb.append("\t<dataItem>\r\n");
sb.append("\t\t<id>");
sb.append("10201");
sb.append("</id>\r\n");
sb.append("\t\t<price>");
sb.append("35.0");
sb.append("</price>\r\n");
sb.append("\t\t<time>");
sb.append("2009-10-30");
sb.append("</time>\r\n");
sb.append("\t</dataItem>\r\n");
sb.append("\t<dataItem>\r\n");
sb.append("\t\t<id>");
sb.append("10301");
sb.append("</id>\r\n");
sb.append("\t\t<price>");
sb.append("55.0");
sb.append("</price>\r\n");
sb.append("\t\t<time>");
sb.append("2009-10-31");
sb.append("</time>\r\n");
sb.append("\t</dataItem>\r\n");
sb.append("</dataGroup>\r\n");
byte[] output = sb.toString().getBytes();
// 使用AES算法对数据加密并回复
HttpUtils.responseWrite(response, AESCoder.encrypt(output, k));
} catch (Exception e) {
throw new ServletException(e);
}
}
}
同时,我们需要修改web.xml文件,将私钥作为密钥变量配置在其中。完整实现如代码清单7-24所示。
代码清单7-24 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<display-name>DataServer</display-name>
<servlet>
<servlet-name>DataServlet</servlet-name>
<servlet-class>DataServlet</servlet-class>
<init-param>
<param-name>key</param-name>
<param-value>
[CDATA[30820153020100300d06092a864886f70d01010105000482013d308201390201000241009fec6cff0209ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a268d877fd8edc26902030100010240275ce73b214256b2e9331388ed1e0a3877ef6443991305d084e44ed5b68ffeb124274a17a3e3dd6e3013011c0602bb6efebebb5d327ebdf8052766f6d8be838d022100d372663308ab6e5c057bc5a56f9b9a16602e2872ded9344fa1dfbaadfb38d24b022100c19ed1dda201473d8fe6433fbfce1486d18782c837d30d4f122f81157a25ed9b0220047a6bc7b0eb508f0a5eb0b4ec4433633dee3c55127b2f2c70953872eedb293902204c451bb68a92a65581d1dabbc9fa8beb6fae49be44ff4646d78b0ef63edfa1f1022010d9524a9febc784bf8ad8dc07a15dee9f47744f9081599b0cc5e18fc37cee3f]]>
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DataServlet</servlet-name>
<url-pattern>/DataServlet</url-pattern>
</servlet-mapping>
</web-app>
最后,我们来调整测试用例DataServletTest类。
此处,我们先通过AESCoder类initKeyString()方法构建秘密密钥,使用RSACoder对其加密并将其发送给服务器。相关实现如代码清单7-25所示。
代码清单7-25 DataServletTest——构建秘密密钥
/**
* 公钥
*/
private static final String publicKey =
"305c300d06092a864886f70d0101010500034b0030480241009fec6cff0209ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a268d877fd8edc2690203010001";
// 省略
// 构建秘密密钥
String secretKey = AESCoder.initKeyString();
// 使用RSA算法加密并发送秘密密钥
byte[] input = HttpUtils.postRequest(url,
RSACoder.encryptByPublicKey(secretKey.getBytes(), publicKey));
最后,我们使用AES算法对收到的数据进行解密,如代码清单7-26所示。
代码清单7-26 DataServletTest——数据解密
// 使用AES算法对数据解密
String data = new String(AESCoder.decrypt(input, secretKey));
完整实现如代码清单7-27所示。
代码清单7-27 DataServletTest
import static org.junit.Assert.*;
import org.junit.Test;
/**
* DataServlet测试用例
* @since 1.0
*/
public class DataServletTest {
// 公钥
private static final String publicKey =
"305c300d06092a864886f70d0101010500034b0030480241009fec6cff0209ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a268d877fd8edc2690203010001";
// 请求地址
private static final String url = "http://localhost:8080/dataserver/DataServlet";
@Test
public final void test() throws Exception {
// 构建秘密密钥
String secretKey = AESCoder.initKeyString();
// 使用RSA算法加密并发送秘密密钥
byte[] input = HttpUtils.postRequest(url, RSACoder.encryptByPublicKey (secretKey.getBytes(), publicKey));
// 使用AES算法对数据解密
String data = new String(AESCoder.decrypt(input, secretKey));
System.err.println(data);
// 校验
assertNotNull(data);
}
}
启动DataServer服务,执行DataServletTest测试方法。
最终,我们可以在控制台得到由服务器下发的数据信息,代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<dataGroup>
<dataItem>
<id>10201</id>
<price>35.0</price>
<time>2009-10-30</time>
</dataItem>
<dataItem>
<id>10301</id>
<price>55.0</price>
<time>2009-10-31</time>
</dataItem>
</dataGroup>
在实际应用中,我们很少会直接使用非对称加密算法进行数据加密。真正对数据进行加密的算法其实都是对称加密算法。非对称加密算法的主要职责是用来初始化对称加密算法的秘密密钥。
对比第六部分代码实现,这里我们缺少了对于数据校验的部分。对于这项关键操作我们有两种选择:
使用消息摘要算法对其数据进行摘要/验证。
使用数字签名对其数据进行签名/验证。
有关数字签名,以及对上述代码的改进我们将在下一部分涉及
非对称加密算法通常配合数字证书、SSL/TLS协议构建单向认证或双向认证使用。