本文主要談一下密碼學(xué)中的加密和數(shù)字簽名,以及其在java中如何進(jìn)行使用。對密碼學(xué)有興趣的伙伴,推薦看 Bruce Schneier的著作:Applied Crypotography。在jdk1.5的發(fā)行版本中安全性方面有了很大的改進(jìn),也提供了對RSA算法的直接支持,現(xiàn)在我們從實例入手解決問題(本文僅是作為簡單介紹):
一、密碼學(xué)上常用的概念
1)消息摘要:
這是一種與消息認(rèn)證碼結(jié)合使用以確保消息完整性的技術(shù)。主要使用單向散列函數(shù)算法,可用于檢驗消息的完整性,和通過散列密碼直接以文本形式保存等,目前廣泛使用的算法有MD4、MD5、SHA-1,jdk1.5對上面都提供了支持,在java中進(jìn)行消息摘要很簡單, java.security.MessageDigest提供了一個簡易的操作方法:
/**
*MessageDigestExample.java
*Copyright?2005-2-16
*/
import?java.security.MessageDigest;
/**
*單一的消息摘要算法,不使用密碼.可以用來對明文消息(如:密碼)隱藏保存
*/
public?class?MessageDigestExample{
public?static?void?main(String[]?args)?throws?Exception{
if(args.length!=1){
System.err.println("Usage:java?MessageDigestExample?text");
System.exit(1);
}
byte[]?plainText=args[0].getBytes("UTF8");
//使用getInstance("算法")來獲得消息摘要,這里使用SHA-1的160位算法
MessageDigest?messageDigest=MessageDigest.getInstance("SHA-1");
System.out.println("\n"+messageDigest.getProvider().getInfo());
//開始使用算法
messageDigest.update(plainText);
System.out.println("\nDigest:");
//輸出算法運算結(jié)果
System.out.println(new?String(messageDigest.digest(),"UTF8"));
}
}
還可以通過消息認(rèn)證碼來進(jìn)行加密實現(xiàn),javax.crypto.Mac提供了一個解決方案,有興趣者可以參考相關(guān)API文檔,本文只是簡單介紹什么是摘要算法。
這里補充另一個運用消息摘要的方式加密的例子:
public?class?TestEncrypt?{
????public?TestEncrypt()?{
????}
????/**
?????*?@param?strSrc??:strSrc?is?a?string?will?be?encrypted,
?????*?@param?encName?:?encName?is?the?algorithm?name?will?be?used.
?????*????????????????encName?dafault?to?"MD5"
?????*?@return?String
?????*/
????public?String?Encrypt(String?strSrc,?String?encName)?{
????????MessageDigest?md?=?null;
????????String?strDes?=?null;
????????byte[]?bt?=?strSrc.getBytes();
????????try?{
????????????if?(encName?==?null?||?encName.equals(""))?{
????????????????encName?=?"MD5";
????????????}
????????????md?=?MessageDigest.getInstance(encName);
????????????md.update(bt);
????????????strDes?=?bytes2Hex(md.digest());?//to?HexString
????????}
????????catch?(NoSuchAlgorithmException?e)?{
????????????System.out.println("Invalid?algorithm.");
????????????return?null;
????????}
????????return?strDes;
????}
????public?String?bytes2Hex(byte[]?bts)?{
????????String?des?=?"";
????????String?tmp?=?null;
????????for?(int?i?=?0;?i?<?bts.length;?i++)?{
????????????tmp?=?(Integer.toHexString(bts[i]?&?0xFF));
????????????if?(tmp.length()?==?1)?{
????????????????des?+=?"0";
????????????}
????????????des?+=?tmp;
????????}
????????return?des;
????}
????public?static?void?main(String[]args)?{
????????TestEncrypt?te?=?new?TestEncrypt();
????????String?strSrc?=?"可以加密漢字.Oh,and?english";
????????System.out.println("Source?String:"?+?strSrc);
????????System.out.println("Encrypted?String:");
????????System.out.println("Use?Def:"?+?te.Encrypt(strSrc,?null));
????????System.out.println("Use?MD5:"?+?te.Encrypt(strSrc,?"MD5"));
????????System.out.println("Use?SHA:"?+?te.Encrypt(strSrc,?"SHA-1"));
????????System.out.println("Use?SHA-256:"?+?te.Encrypt(strSrc,?"SHA-256"));
????}
}
另外,在javawebparts中的 RequestHelpers里的generateGUID方法也涉及到了MD5的方法,代碼如下:
public?static?String?generateGUID(HttpServletRequest?request)?{
????String?out?=?"";
????try?{
??????//?Construct?a?string?that?is?comprised?of:
??????//?Remote?IP?Address?+?Host?IP?Address?+?Date?(yyyyMMdd)?+
??????//?Time?(hhmmssSSa)?+?Requested?Path?+?Session?ID?+
??????//?HashCode?Of?ParameterMap
??????StringBuffer?sb?=?new?StringBuffer(1024);
??????sb.append(request.getRemoteAddr());
??????InetAddress?ia?=?InetAddress.getLocalHost();
??????sb.append(ia.getHostAddress());
??????sb.append(new?SimpleDateFormat("yyyyMMddhhmmssSSa").format(new?Date()));
??????String?path?=?request.getServletPath();
??????String?pathInfo?=?request.getPathInfo();
??????if?(pathInfo?!=?null)?{
????????path?+=?pathInfo;
??????}
??????sb.append(path);
??????sb.append(request.getSession(false));
??????sb.append(request.getParameterMap().hashCode());
??????String?str?=?sb.toString();
??????//?Now?encode?the?string?using?an?MD5?encryption?algorithm.
??????MessageDigest?md?=?MessageDigest.getInstance("md5");
??????md.update(str.getBytes());
??????byte[]?digest?=?md.digest();
??????StringBuffer?hexStr?=?new?StringBuffer(1024);
??????for?(int?i?=?0;?i?<?digest.length;?i++)?{
????????str?=?Integer.toHexString(0xFF?&?digest[i]);
????????if?(str.length()?<?2)?{
??????????str?=?"0"?+?str;
????????}
????????hexStr.append(str);
??????}
??????out?=?hexStr.toString();
????}?catch?(NoSuchAlgorithmException?nsae)?{
??????log.error(nsae);
????}?catch?(UnknownHostException?uhe)?{
??????log.error(uhe);
????}
????//?Return?the?encrypted?string.??It?should?be?unique?based?on?the
????//?components?that?comprise?the?plain?text?string,?and?should?always?be
????//?32?characters?thanks?to?the?MD5?algorithm.
????return?out;
??}?//?End?generateGUID().
2)私鑰加密:
消息摘要只能檢查消息的完整性,但是單向的,對明文消息并不能加密,要加密明文的消息的話,就要使用其他的算法,要確保機密性,我們需要使用私鑰密碼術(shù)來交換私有消息。
這種最好理解,使用對稱算法。比如:A用一個密鑰對一個文件加密,而B讀取這個文件的話,則需要和A一樣的密鑰,雙方共享一個私鑰(而在web環(huán)境下,私鑰在傳遞時容易被偵聽):
使用私鑰加密的話,首先需要一個密鑰,可用javax.crypto.KeyGenerator產(chǎn)生一個密鑰(java.security.Key), 然后傳遞給一個加密工具(javax.crypto.Cipher),該工具再使用相應(yīng)的算法來進(jìn)行加密,主要對稱算法有:DES(實際密鑰只用到56 位),AES(支持三種密鑰長度:128、192、256位),通常首先128位,其他的還有DESede等,jdk1.5種也提供了對對稱算法的支持,以下例子使用AES算法來加密:
/**
*PrivateExmaple.java
*Copyright?2005-2-16
*/
import?javax.crypto.Cipher;
import?javax.crypto.KeyGenerator;
import?java.security.Key;
/**
*私鈅加密,保證消息機密性
*/
public?class?PrivateExample{
public?static?void?main(String[]?args)?throws?Exception{
if(args.length!=1){
System.err.println("Usage:java?PrivateExample?<text>");
System.exit(1);
}
byte[]?plainText=args[0].getBytes("UTF8");
//通過KeyGenerator形成一個key
System.out.println("\nStart?generate?AES?key");
KeyGenerator?keyGen=KeyGenerator.getInstance("AES");
keyGen.init(128);
Key?key=keyGen.generateKey();
System.out.println("Finish?generating?DES?key");
//獲得一個私鈅加密類Cipher,ECB是加密方式,PKCS5Padding是填充方法
Cipher?cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
System.out.println("\n"+cipher.getProvider().getInfo());
//使用私鈅加密
System.out.println("\nStart?encryption:");
cipher.init(Cipher.ENCRYPT_MODE,key);
byte[]?cipherText=cipher.doFinal(plainText);
System.out.println("Finish?encryption:");
System.out.println(new?String(cipherText,"UTF8"));
System.out.println("\nStart?decryption:");
cipher.init(Cipher.DECRYPT_MODE,key);
byte[]?newPlainText=cipher.doFinal(cipherText);
System.out.println("Finish?decryption:");
System.out.println(new?String(newPlainText,"UTF8"));
}
}
3)公鑰加密:
上面提到,私鑰加密需要一個共享的密鑰,那么如何傳遞密鑰呢?web環(huán)境下,直接傳遞的話很容易被偵聽到,幸好有了公鑰加密的出現(xiàn)。公鑰加密也叫不對稱加密,不對稱算法使用一對密鑰對,一個公鑰,一個私鑰,使用公鑰加密的數(shù)據(jù),只有私鑰能解開(可用于加密);同時,使用私鑰加密的數(shù)據(jù),只有公鑰能解開(簽名)。但是速度很慢(比私鑰加密慢100到1000倍),公鑰的主要算法有RSA,還包括Blowfish,Diffie-Helman等, jdk1.5種提供了對RSA的支持,是一個改進(jìn)的地方:
/**
*PublicExample.java
*Copyright?2005-2-16
*/
import?java.security.Key;
import?javax.crypto.Cipher;
import?java.security.KeyPairGenerator;
import?java.security.KeyPair;
/**
*一個簡單的公鈅加密例子,Cipher類使用KeyPairGenerator生成的公鈅和私鈅
*/
public?class?PublicExample{
public?static?void?main(String[]?args)?throws?Exception{
if(args.length!=1){
System.err.println("Usage:java?PublicExample?<text>");
System.exit(1);
}
byte[]?plainText=args[0].getBytes("UTF8");
//構(gòu)成一個RSA密鑰
System.out.println("\nStart?generating?RSA?key");
KeyPairGenerator?keyGen=KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair?key=keyGen.generateKeyPair();
System.out.println("Finish?generating?RSA?key");
//獲得一個RSA的Cipher類,使用公鈅加密
Cipher?cipher=Cipher.getInstance("RSA/ECB/PKCS1Padding");
System.out.println("\n"+cipher.getProvider().getInfo());
System.out.println("\nStart?encryption");
cipher.init(Cipher.ENCRYPT_MODE,key.getPublic());
byte[]?cipherText=cipher.doFinal(plainText);
System.out.println("Finish?encryption:");
System.out.println(new?String(cipherText,"UTF8"));
//使用私鈅解密
System.out.println("\nStart?decryption");
cipher.init(Cipher.DECRYPT_MODE,key.getPrivate());
byte[]?newPlainText=cipher.doFinal(cipherText);
System.out.println("Finish?decryption:");
System.out.println(new?String(newPlainText,"UTF8"));
}
}