按:下面的文字涉及早已在工程中广泛采用的混合加密方式,对此熟知者就不用往下看了,以免浪费时间。
我们知道,现代加密方式有两大类:一类是对称加密方式,其优点是加密速度快,缺点是密钥不便于传递,其中典型例子是AES;一类是非对称加密方式,优点是交换钥匙方便,缺点是加密时间长。在实际应用,我们可以取其所长,弃其所短,这就是混合加密方式,有的场合也成为Hybrid方式。
具体来说混合加密方式的工作过程大体是这样:首先,客户端将明文用本地的AES钥匙加密,然后从服务器端得到服务器端的RSA公钥,用它来对本地的AES钥匙加密,然后把两端密文拼合在一起送给服务器端;服务器端得到密文后,将其拆分成密钥文和密文两段,然后,用本地的RSA私钥对密钥文进行解密,得到加密密文的AES钥匙,然后用AES钥匙对密文解密,得到明文。在此过程中,对明文加密和对密文解密都采用了对称加密解密方式,速度快,且都在服务器客户机的一侧进行,没有通过网络传输,安全性高;而网络传输的是服务器的RSA公钥和经其加密的AES钥匙,即使被截获也没有什么好担心的。如果要双向传递则把这个过程反过来就可以了。
以上过程的示意UML SEQUENCE图如下:
下面用代码来辅助说明一下。
客户端进行加密并传输密文到服务器端的代码,其中,服务器端的RSA公钥已经用别的方法得到了,下面serverPublicKey变量就存储了它:
Socket s
=
new
Socket(
"
127.0.0.1
"
,
8888
);
InputStream inStram = s.getInputStream();
OutputStream outStream = s.getOutputStream();
// 输出
PrintWriter out = new PrintWriter(outStream, true );
// 待加密的明文
StringBuilder sb1 = new StringBuilder();
sb1.append( " <request> " );
sb1.append( " <command>register</command> " );
sb1.append( " <username>何杨</username> " );
sb1.append( " <password>123456</password> " );
sb1.append( " </request> " );
String plainText = sb1.toString();
// 对明文进行AES加密
byte [] aesArr = aesCoder.getEncryptByteArray(plainText); // 对明文进行AES加密
String cipherText = Base64.encodeBase64String(aesArr); // 得到AES加密后的密文
// 使用RSA对AES密钥进行加密
String key = aesCoder.getAesKey(); // 取得AES的密钥
byte [] rsaArr = rsaCoder.getEncryptArray(key, serverPublicKey);
String encryptedKey = Base64.encodeBase64String(rsaArr);
// 在发出的密文前附带经服务器RSA公钥加密的AES密钥
String request = " <key> " + encryptedKey + " </key> " + cipherText;
out.print(request);
out.flush();
s.shutdownOutput(); // 输出结束
InputStream inStram = s.getInputStream();
OutputStream outStream = s.getOutputStream();
// 输出
PrintWriter out = new PrintWriter(outStream, true );
// 待加密的明文
StringBuilder sb1 = new StringBuilder();
sb1.append( " <request> " );
sb1.append( " <command>register</command> " );
sb1.append( " <username>何杨</username> " );
sb1.append( " <password>123456</password> " );
sb1.append( " </request> " );
String plainText = sb1.toString();
// 对明文进行AES加密
byte [] aesArr = aesCoder.getEncryptByteArray(plainText); // 对明文进行AES加密
String cipherText = Base64.encodeBase64String(aesArr); // 得到AES加密后的密文
// 使用RSA对AES密钥进行加密
String key = aesCoder.getAesKey(); // 取得AES的密钥
byte [] rsaArr = rsaCoder.getEncryptArray(key, serverPublicKey);
String encryptedKey = Base64.encodeBase64String(rsaArr);
// 在发出的密文前附带经服务器RSA公钥加密的AES密钥
String request = " <key> " + encryptedKey + " </key> " + cipherText;
out.print(request);
out.flush();
s.shutdownOutput(); // 输出结束
从上面这段代码可以看出,想发送到服务器端的明文是:
<
request
><
command
>
register
</
command
><
username
>
何杨
</
username
><
password
>
123456
</
password
></
request
>
通过这段代码的处理后,最终发送到服务器端的密文是,
<
key
>
1B2FM07HS4iB+vjeehb/RqHTnEXAr1cj/CR6z+SDPI58ZG5TK54iEoi8cvdIL0oj60X7axrAL3YO
b6PMzQxKHzipSYw3ishH/3KxoYF8bkQGn2PkMNsn+xL1Gz6XgJcQ+B700hYvVT2FFPfelVz3VNlB
KhwVIE6h8LyD4w/SxhE=
</ key > J4TsMoB3l8Cy91a9v6O0TADXZvKEkDPZ3E5noeu2dImfdsM55urhEY7lFAAsXm0AB4/jUL1h1lNP
cafz9srORh7h8NCb4760XnrBA5Q2JQrqwr1TGsB3oGq2Ha+FOLoFcI2Ab/wjEiAhe/kB6ZTgTA==
其中key节点的内容是加密的AES密钥,后面是AES加密后的密文。如果这段文字在网络上被截获,截获者可能会猜测出key节点是密钥,后半段是密文,但密钥部分是被服务器的公钥进行RSA加密的,只有用服务器的私钥来解密;而密钥文解不出来的话,截获者对后端密文也是无能为力。这就可以让人放心了,如果服务器端没有潜伏一个余则成和截获者里应外合的话。
b6PMzQxKHzipSYw3ishH/3KxoYF8bkQGn2PkMNsn+xL1Gz6XgJcQ+B700hYvVT2FFPfelVz3VNlB
KhwVIE6h8LyD4w/SxhE=
</ key > J4TsMoB3l8Cy91a9v6O0TADXZvKEkDPZ3E5noeu2dImfdsM55urhEY7lFAAsXm0AB4/jUL1h1lNP
cafz9srORh7h8NCb4760XnrBA5Q2JQrqwr1TGsB3oGq2Ha+FOLoFcI2Ab/wjEiAhe/kB6ZTgTA==
服务器端的处理代码:
String cipheredAesKey
=
""
;
//
经服务器RSA公钥加密的客户端AES钥匙密文
String cipherText = "" ; // 经客户端AES加密的密文
// 用正则表达式得到密钥文和密文
String regex = " <key>(.+)</key>(.+) " ;
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(request);
while (matcher.find()){
cipheredAesKey = matcher.group( 1 );
cipherText = matcher.group( 2 );
break ;
}
// 得到经过服务器RSA私钥解密后的AES密钥
String plainAesKey = "" ;
try {
byte [] cipheredAesKeyArr = Base64.decodeBase64(cipheredAesKey);
plainAesKey = model.getRsaCoder().getDecryptString(cipheredAesKeyArr);
} catch (Exception e) {
e.printStackTrace();
return null ;
}
// 使用AES密钥解密出明文
byte [] cipherTextArr = Base64.decodeBase64(cipherText);
String plainText = model.getAesCoder().getDecryptString(cipherTextArr, plainAesKey);
String cipherText = "" ; // 经客户端AES加密的密文
// 用正则表达式得到密钥文和密文
String regex = " <key>(.+)</key>(.+) " ;
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(request);
while (matcher.find()){
cipheredAesKey = matcher.group( 1 );
cipherText = matcher.group( 2 );
break ;
}
// 得到经过服务器RSA私钥解密后的AES密钥
String plainAesKey = "" ;
try {
byte [] cipheredAesKeyArr = Base64.decodeBase64(cipheredAesKey);
plainAesKey = model.getRsaCoder().getDecryptString(cipheredAesKeyArr);
} catch (Exception e) {
e.printStackTrace();
return null ;
}
// 使用AES密钥解密出明文
byte [] cipherTextArr = Base64.decodeBase64(cipherText);
String plainText = model.getAesCoder().getDecryptString(cipherTextArr, plainAesKey);
这段代码的输入是:
<
key
>
P9SQ2DtWqrdH3hJbQNWRb51OEs9c7KpsgjRg0yPT5LZJoqJBeYmq3r/1T050n136OelvTh+XtaZaXbCJAvfnF4fvtAKdXqPp+lzUNgPYk8R0OaVDUIi8pNi1rb/+GvtY2ZucFYL1BOwO8ARwvXf8f52Cl+Vdu5TdinXVjmwSPZY=
</
key
>
u0ube9sy7bsIy8aaUSJofoswY+R3WXD8yJbOzEZWiDniyXNNyrHNiygfRHj3TKwVQXRck/OVPXptMvUjCVqmg118TN0tc4sKoOKHaSmUtvGC2WW3K5anxlFzdUIZMIhvpDF1nWoaTXvEJ1nOuwhIig==
它和客户端传过来的内容是一样的。
而经过拆分和解密后,AES密钥是:
83aeacfa1b59eb2dc557a9f3d5df6af83ee9a1646652f1d2b55ea6ec76a95bde
用得到的AES密钥解密后,最终得到的明文部分是:
<
request
><
command
>
register
</
command
><
username
>
何杨
</
username
><
password
>
123456
</
password
></
request
>
到这里,密文的还原工作就完成了。如果服务器端要向客户端发回处理后的结果,把上述过程再做一遍就可以了,注意一点,客户端要把自己的RSA公钥发过来,也就是说传递的文本中还要增加一个节点,这样服务器端就有了客户端的RSA公钥对服务器端的AES钥匙进行加密。
这种方式看似比纯RSA方式和AES方式都复杂了一点,但考虑到网络传输的安全性和速度,多写一些代码是完全值得的。
上文中用到的AESSecurityCoder类代码如下:
package
com.heyang.common.code;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
/**
* AES加密解密类
* 说明:
* 作者:何杨(heyang78@gmail.com)
* 创建时间:2010-12-25 下午12:19:12
* 修改时间:2010-12-25 下午12:19:12
*/
public class AESSecurityCoder{
// 加密方法
private static final String Algorithm = " AES " ;
// 进行加密解密的密钥
private String aesKey = "" ;
/**
* 构造函数
* @throws NoSuchAlgorithmException
*/
public AESSecurityCoder() throws NoSuchAlgorithmException{
KeyGenerator kg = KeyGenerator.getInstance(Algorithm);
kg.init( 256 );
SecretKey sk = kg.generateKey();
byte [] arr = sk.getEncoded();
aesKey = new String(Hex.encodeHex(arr));
}
/**
* 取得解密后的字符串
*
* 说明:
* @param encryptArr
* @return
* 创建时间:2010-12-1 下午03:33:31
*/
public String getDecryptString( byte [] encryptArr){
try {
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.DECRYPT_MODE, getKey());
byte [] arr = cp.doFinal(encryptArr);
return new String(arr);
}
catch (Exception ex){
System.out.println( " 无法进行解密,原因是 " + ex.getMessage());
return null ;
}
}
/**
* 传入密钥,得到解密后的字符串
*
* 说明:
* @param encryptArr
* @param aesKey
* @return
* 创建时间:2010-12-25 下午01:55:42
*/
public String getDecryptString( byte [] encryptArr,String aesKeyIn){
try {
Cipher cp = Cipher.getInstance(Algorithm);
byte [] arr1 = Hex.decodeHex(aesKeyIn.toCharArray());
cp.init(Cipher.DECRYPT_MODE, new SecretKeySpec(arr1,Algorithm));
byte [] arr = cp.doFinal(encryptArr);
return new String(arr);
}
catch (Exception ex){
System.out.println( " 无法进行解密,原因是 " + ex.getMessage());
return null ;
}
}
/**
* 取得加密后的字节数组
*
* 说明:
* @param originalString
* @return
* 创建时间:2010-12-1 下午03:33:49
*/
public byte [] getEncryptByteArray(String originalString){
try {
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.ENCRYPT_MODE, getKey());
return cp.doFinal(originalString.getBytes());
}
catch (Exception ex){
System.out.println( " 无法进行加密,原因是 " + ex.getMessage());
return null ;
}
}
/**
* 取得密钥
*
* 说明:
* @return
* @throws Exception
* 创建时间:2010-12-1 下午03:33:17
*/
private Key getKey() throws Exception{
byte [] arr = Hex.decodeHex(aesKey.toCharArray());
return new SecretKeySpec(arr,Algorithm);
}
/**
* 取得AES加密钥匙
*
* 说明:
* @return
* 创建时间:2010-12-25 下午12:27:16
*/
public String getAesKey() {
return aesKey;
}
}
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
/**
* AES加密解密类
* 说明:
* 作者:何杨(heyang78@gmail.com)
* 创建时间:2010-12-25 下午12:19:12
* 修改时间:2010-12-25 下午12:19:12
*/
public class AESSecurityCoder{
// 加密方法
private static final String Algorithm = " AES " ;
// 进行加密解密的密钥
private String aesKey = "" ;
/**
* 构造函数
* @throws NoSuchAlgorithmException
*/
public AESSecurityCoder() throws NoSuchAlgorithmException{
KeyGenerator kg = KeyGenerator.getInstance(Algorithm);
kg.init( 256 );
SecretKey sk = kg.generateKey();
byte [] arr = sk.getEncoded();
aesKey = new String(Hex.encodeHex(arr));
}
/**
* 取得解密后的字符串
*
* 说明:
* @param encryptArr
* @return
* 创建时间:2010-12-1 下午03:33:31
*/
public String getDecryptString( byte [] encryptArr){
try {
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.DECRYPT_MODE, getKey());
byte [] arr = cp.doFinal(encryptArr);
return new String(arr);
}
catch (Exception ex){
System.out.println( " 无法进行解密,原因是 " + ex.getMessage());
return null ;
}
}
/**
* 传入密钥,得到解密后的字符串
*
* 说明:
* @param encryptArr
* @param aesKey
* @return
* 创建时间:2010-12-25 下午01:55:42
*/
public String getDecryptString( byte [] encryptArr,String aesKeyIn){
try {
Cipher cp = Cipher.getInstance(Algorithm);
byte [] arr1 = Hex.decodeHex(aesKeyIn.toCharArray());
cp.init(Cipher.DECRYPT_MODE, new SecretKeySpec(arr1,Algorithm));
byte [] arr = cp.doFinal(encryptArr);
return new String(arr);
}
catch (Exception ex){
System.out.println( " 无法进行解密,原因是 " + ex.getMessage());
return null ;
}
}
/**
* 取得加密后的字节数组
*
* 说明:
* @param originalString
* @return
* 创建时间:2010-12-1 下午03:33:49
*/
public byte [] getEncryptByteArray(String originalString){
try {
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.ENCRYPT_MODE, getKey());
return cp.doFinal(originalString.getBytes());
}
catch (Exception ex){
System.out.println( " 无法进行加密,原因是 " + ex.getMessage());
return null ;
}
}
/**
* 取得密钥
*
* 说明:
* @return
* @throws Exception
* 创建时间:2010-12-1 下午03:33:17
*/
private Key getKey() throws Exception{
byte [] arr = Hex.decodeHex(aesKey.toCharArray());
return new SecretKeySpec(arr,Algorithm);
}
/**
* 取得AES加密钥匙
*
* 说明:
* @return
* 创建时间:2010-12-25 下午12:27:16
*/
public String getAesKey() {
return aesKey;
}
}
上文中用到的RSASecurityCoder类代码如下:
package
com.heyang.common.code;
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 javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
/**
* RSA加密解密类
* 说明:
* 作者:何杨(heyang78@gmail.com)
* 创建时间:2010-12-1 下午06:14:38
* 修改时间:2010-12-1 下午06:14:38
*/
public class RSASecurityCoder{
// 非对称加密密钥算法
private static final String Algorithm = " RSA " ;
// 密钥长度,用来初始化
private static final int Key_Size = 1024 ;
// 公钥
private byte [] publicKey;
// 私钥
private byte [] privateKey;
/**
* 构造函数,在其中生成公钥和私钥
* @throws Exception
*/
public RSASecurityCoder() throws Exception{
// 得到密钥对生成器
KeyPairGenerator kpg = KeyPairGenerator.getInstance(Algorithm);
kpg.initialize(Key_Size);
// 得到密钥对
KeyPair kp = kpg.generateKeyPair();
// 得到公钥
RSAPublicKey keyPublic = (RSAPublicKey)kp.getPublic();
publicKey = keyPublic.getEncoded();
// 得到私钥
RSAPrivateKey keyPrivate = (RSAPrivateKey)kp.getPrivate();
privateKey = keyPrivate.getEncoded();
}
/**
* 用公钥对字符串进行加密
*
* 说明:
* @param originalString
* @param publicKeyArray
* @return
* @throws Exception
* 创建时间:2010-12-1 下午06:29:51
*/
public byte [] getEncryptArray(String originalString, byte [] publicKeyArray) throws Exception{
// 得到公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyArray);
KeyFactory kf = KeyFactory.getInstance(Algorithm);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 加密数据
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.ENCRYPT_MODE, keyPublic);
return cp.doFinal(originalString.getBytes());
}
/**
* 使用私钥进行解密
*
* 说明:
* @param encryptedDataArray
* @return
* @throws Exception
* 创建时间:2010-12-1 下午06:35:28
*/
public String getDecryptString( byte [] encryptedDataArray) throws Exception{
// 得到私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(Algorithm);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 解密数据
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.DECRYPT_MODE, keyPrivate);
byte [] arr = cp.doFinal(encryptedDataArray);
// 得到解密后的字符串
return new String(arr);
}
/**
* 取得数组形式的公钥
*
* 说明:
* @return
* 创建时间:2010-12-25 上午07:50:04
*/
public byte [] getPublicKey() {
return publicKey;
}
/**
* 取得字符串形式的公钥
*
* 说明:
* @return
* 创建时间:2010-12-25 上午07:51:11
*/
public String getPublicKeyString() {
return Base64.encodeBase64String(getPublicKey());
}
public static void main(String[] arr) throws Exception{
String str = " 你好,世界! Hello,world! " ;
System.out.println( " 准备用公钥加密的字符串为: " + str);
// 用公钥加密
RSASecurityCoder rsaCoder = new RSASecurityCoder();
byte [] publicKey = rsaCoder.getPublicKey();
byte [] encryptArray = rsaCoder.getEncryptArray(str, publicKey);
System.out.print( " 用公钥加密后的结果为: " );
for ( byte b:encryptArray){
System.out.print(b);
}
System.out.println();
// 用私钥解密
String str1 = rsaCoder.getDecryptString(encryptArray);
System.out.println( " 用私钥解密后的字符串为: " + str1);
}
}
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 javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
/**
* RSA加密解密类
* 说明:
* 作者:何杨(heyang78@gmail.com)
* 创建时间:2010-12-1 下午06:14:38
* 修改时间:2010-12-1 下午06:14:38
*/
public class RSASecurityCoder{
// 非对称加密密钥算法
private static final String Algorithm = " RSA " ;
// 密钥长度,用来初始化
private static final int Key_Size = 1024 ;
// 公钥
private byte [] publicKey;
// 私钥
private byte [] privateKey;
/**
* 构造函数,在其中生成公钥和私钥
* @throws Exception
*/
public RSASecurityCoder() throws Exception{
// 得到密钥对生成器
KeyPairGenerator kpg = KeyPairGenerator.getInstance(Algorithm);
kpg.initialize(Key_Size);
// 得到密钥对
KeyPair kp = kpg.generateKeyPair();
// 得到公钥
RSAPublicKey keyPublic = (RSAPublicKey)kp.getPublic();
publicKey = keyPublic.getEncoded();
// 得到私钥
RSAPrivateKey keyPrivate = (RSAPrivateKey)kp.getPrivate();
privateKey = keyPrivate.getEncoded();
}
/**
* 用公钥对字符串进行加密
*
* 说明:
* @param originalString
* @param publicKeyArray
* @return
* @throws Exception
* 创建时间:2010-12-1 下午06:29:51
*/
public byte [] getEncryptArray(String originalString, byte [] publicKeyArray) throws Exception{
// 得到公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyArray);
KeyFactory kf = KeyFactory.getInstance(Algorithm);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 加密数据
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.ENCRYPT_MODE, keyPublic);
return cp.doFinal(originalString.getBytes());
}
/**
* 使用私钥进行解密
*
* 说明:
* @param encryptedDataArray
* @return
* @throws Exception
* 创建时间:2010-12-1 下午06:35:28
*/
public String getDecryptString( byte [] encryptedDataArray) throws Exception{
// 得到私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(Algorithm);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 解密数据
Cipher cp = Cipher.getInstance(Algorithm);
cp.init(Cipher.DECRYPT_MODE, keyPrivate);
byte [] arr = cp.doFinal(encryptedDataArray);
// 得到解密后的字符串
return new String(arr);
}
/**
* 取得数组形式的公钥
*
* 说明:
* @return
* 创建时间:2010-12-25 上午07:50:04
*/
public byte [] getPublicKey() {
return publicKey;
}
/**
* 取得字符串形式的公钥
*
* 说明:
* @return
* 创建时间:2010-12-25 上午07:51:11
*/
public String getPublicKeyString() {
return Base64.encodeBase64String(getPublicKey());
}
public static void main(String[] arr) throws Exception{
String str = " 你好,世界! Hello,world! " ;
System.out.println( " 准备用公钥加密的字符串为: " + str);
// 用公钥加密
RSASecurityCoder rsaCoder = new RSASecurityCoder();
byte [] publicKey = rsaCoder.getPublicKey();
byte [] encryptArray = rsaCoder.getEncryptArray(str, publicKey);
System.out.print( " 用公钥加密后的结果为: " );
for ( byte b:encryptArray){
System.out.print(b);
}
System.out.println();
// 用私钥解密
String str1 = rsaCoder.getDecryptString(encryptArray);
System.out.println( " 用私钥解密后的字符串为: " + str1);
}
}
好了,感谢您看到这里,希望它没有太多耽误您的宝贵时间。