8.5 实例:非对称加密网络应用
目前,非对称加密算法(主要是RSA算法)主要应用于B2C、B2B等多种电子商务平台。但非对称加密算法并不直接对网络数据进行加密/解密,而是用于交换对称加密算法的秘密密钥。最终使用对称加密算法进行真正的加密/解密。
此处,我们将对第7章的DataServer应用稍作修改,使用非对称加密算法RSA交换对称加密算法AES的秘密密钥。并使用该秘密密钥对数据进行加密/解密。
对于第7章中用于实现DataServer应用的HttpUtils类和AESCoder类,本文不做详述,请读者阅读相关内容。此处,我们仅对DataServlet类和DataServlet类稍作修改,并增加用于RSA算法实现的RSACoder类。
首先,我们要对本章中的RSACoder类稍作修改:使用开源组件Commons Codec的十六进制转换工具类Hex对密钥进行封装/解包。相关实现如代码清单8-14所示。
代码清单8-14 密钥封装/解包
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编码长度小得多,读者朋友可以根据需要选择合适的算法。完整实现如代码清单8-15所示。
代码清单8-15 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安全编码组件
*@author梁栋
*@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类的相关方法获得公钥和私钥,相关实现如代码清单8-16所示。
代码清单8-16 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—
305c300d06092a864886f70d0101010500034b0030480241009fec6cff0209ef1a332a35ccafc2aae59c
4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a268d87
7fd8edc2690203010001
privateKey—
30820153020100300d06092a864886f70d01010105000482013d308201390201000241009fec6cff0209
ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f784
4c008389e5a6379a268d877fd8edc26902030100010240275ce73b214256b2e9331388ed1e0a3877ef64
43991305d084e44ed5b68ffeb124274a17a3e3dd6e3013011c0602bb6efebebb5d327ebdf8052766f6d8
be838d022100d372663308ab6e5c057bc5a56f9b9a16602e2872ded9344fa1dfbaadfb38d24b022100c1
9ed1dda201473d8fe6433fbfce1486d18782c837d30d4f122f81157a25ed9b0220047a6bc7b0eb508f0a
5eb0b4ec4433633dee3c55127b2f2c70953872eedb293902204c451bb68a92a65581d1dabbc9fa8beb6f
ae49be44ff4646d78b0ef63edfa1f1022010d9524a9febc784bf8ad8dc07a15dee9f47744f9081599b0c
c5e18fc37cee3f
接下来,我们来调整DataSerlvet类。这里,我们使用RSA算法对请求的数据进行解密,获得本次交互的对称加密算法密钥。相关实现如代码清单8-17所示。
代码清单8-17 DataServlet—解析密钥
byte[]input=HttpUtils.requestRead(request);
//对秘密密钥解密
String k=new String(RSACoder.decryptByPrivateKey(input, key));
本文演示程序获得密钥后,使用AES算法将数据加密回复给请求方,相关实现如代码清单8-18所示。
代码清单8-18 DataServlet—加密回复
//使用AES算法对数据加密并回复
HttpUtils.responseWrite(response, AESCoder.encrypt(output, k));
DataServlet类完整实现如代码清单8-19所示。
代码清单8-19 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文件,将私钥作为密钥变量配置其中。完整实现如代码清单8-20所示。
代码清单8-20 web.xml
<?xml version="1.0"encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-
app_2_5.xsd"
id="WebApp_ID"
version="2.5">
<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[30820153020100300d06092a864886f70d01010105000482013d3082013902010002410
09fec6cff0209ef1a332a35ccafc2aae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1
625a2e9e9f7844c008389e5a6379a268d877fd8edc26902030100010240275ce73b214256b2e9331388e
d1e0a3877ef6443991305d084e44ed5b68ffeb124274a17a3e3dd6e3013011c0602bb6efebebb5d327eb
df8052766f6d8be838d022100d372663308ab6e5c057bc5a56f9b9a16602e2872ded9344fa1dfbaadfb3
8d24b022100c19ed1dda201473d8fe6433fbfce1486d18782c837d30d4f122f81157a25ed9b0220047a6
bc7b0eb508f0a5eb0b4ec4433633dee3c55127b2f2c70953872eedb293902204c451bb68a92a65581d1d
abbc9fa8beb6fae49be44ff4646d78b0ef63edfa1f1022010d9524a9febc784bf8ad8dc07a15dee9f477
44f9081599b0cc5e18fc37cee3f]]>
</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对其加密并将其发送给服务器。相关实现如代码清单8-21所示。
代码清单8-21 DataServletTest—构建秘密密钥
/
*公钥
*/
private static final String publicKey=
"305c300d06092a864886f70d0101010500034b0030480241009fec6cff0209ef1a332a35ccafc2a ae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a2 68d877fd8edc2690203010001";
//省略
//构建秘密密钥
String secretKey=AESCoder.initKeyString();
//使用RSA算法加密并发送秘密密钥
byte[]input=HttpUtils.postRequest(url,
RSACoder.encryptByPublicKey(secretKey.getBytes(),publicKey));
*
*
最后,我们使用AES算法对收到的数据进行解密,如代码清单8-22所示。
代码清单8-22 DataServletTest—数据解密
//使用AES算法对数据解密
String data=new String(AESCoder.decrypt(input, secretKey));
完整实现如代码清单8-23所示。
代码清单8-23 DataServletTest
import static org.junit.Assert.*;
import org.junit.Test;
/**
*DataServlet测试用例
*@author梁栋
*@since 1.0
*/
public class DataServletTest{
//公钥
private static final String publicKey=
"305c300d06092a864886f70d0101010500034b0030480241009fec6cff0209ef1a332a35ccafc2a ae59c4d5275ef9186d73593186482ec637f6df042c2aa41115c8a1625a2e9e9f7844c008389e5a6379a2 68d877fd8edc2690203010001";
//请求地址
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>
在实际应用中,我们很少会直接使用非对称加密算法进行数据加密。真正对数据进行加密的算法其实都是对称加密算法。非对称加密算法的主要职责是用来初始化对称加密算法的秘密密钥。
这里,对比第7章代码实现我们缺少了对于数据校验的部分。对于这项关键操作我们有两种选择:
1)使用消息摘要算法对其数据进行摘要/验证。
2)使用数字签名对其数据进行签名/验证。
有关数字签名,以及对上述代码的改进我们将在第9章中详述。
非对称加密算法通常配合数字证书、SSL/TLS协议构建单向认证或双向认证使用,请读者朋友关注后续章节内容。