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协议构建单向认证或双向认证使用,请读者朋友关注后续章节内容。