按:下面的文字涉及早已在工程中廣泛采用的混合加密方式,對此熟知者就不用往下看了,以免浪費時間。
我們知道,現代加密方式有兩大類:一類是對稱加密方式,其優點是加密速度快,缺點是密鑰不便于傳遞,其中典型代表是
AES;一類是非對稱加密方式,優點是交換鑰匙方便,缺點是加密時間長,代表是
RSA。在實際應用,我們可以取其所長,棄其所短,這就是混合加密方式,有的場合也成為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();// 輸出結束
從上面這段代碼可以看出,想發送到服務器端的明文是:
<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加密的,只有用服務器的私鑰來解密;而密鑰文解不出來的話,截獲者對后端密文也是無能為力。這就可以讓人放心了,如果服務器端沒有潛伏一個余則成和截獲者里應外合的話。這里還可以把整段文字用Base64加密一下,到服務器再解密。
服務器端的處理代碼:
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);
這段代碼的輸入是:
<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鑰匙進行加密。(完整混合加密客戶機服務器通訊過程請參看:
http://www.tkk7.com/heyang/archive/2010/12/26/341556.html)
這種方式看似比純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;
}
}
上文中用到的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);
}
}
好了,感謝您看到這里,希望它沒有太多耽誤您的寶貴時間。