各種Java加密演算法
-
BASE64 嚴格地說,屬於編碼格式,而非加密演算法
-
MD5(Message Digest algorithm 5,資訊摘要演算法)
-
SHA(Secure Hash Algorithm,安全雜湊演算法)
-
HMAC(Hash Message Authentication Code,雜湊訊息鑑別碼)
複雜的對稱加密(DES、PBE)、非對稱加密演算法:
-
DES(Data Encryption Standard,資料加密演算法)
-
PBE(Password-based encryption,基於密碼驗證)
-
RSA(演算法的名字以發明者的名字命名:Ron Rivest, AdiShamir 和Leonard Adleman)
-
DH(Diffie-Hellman演算法,金鑰一致協議)
-
DSA(Digital Signature Algorithm,數字簽名)
-
ECC(Elliptic Curves Cryptography,橢圓曲線密碼編碼學)
本篇內容簡要介紹BASE64、MD5、SHA、HMAC幾種方法。
MD5、SHA、HMAC這三種加密演算法,可謂是非可逆加密,就是不可解密的加密方法。我們通常只把他們作為加密的基礎。單純的以上三種的加密並不可靠。
BASE64
按 照RFC2045的定義,Base64被定義為:Base64內容傳送編碼被設計用來把任意序列的8位位元組描述為一種不易被人直接識別的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.)
常見於郵件、http加密,擷取http資訊,你就會發現登入操作的使用者名稱、密碼欄位通過BASE64加密的。
通過java程式碼實現如下:
/**
* BASE64解密
*
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/**
* BASE64加密
*
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
主要就是BASE64Encoder、BASE64Decoder兩個類,我們只需要知道使用對應的方法即可。另,BASE加密後產生的位元組位數是8的倍數,如果不夠位數以=符號填充。
MD5
MD5 -- message-digest algorithm 5 (資訊-摘要演算法)縮寫,廣泛用於加密和解密技術,常用於檔案校驗。校驗?不管檔案多大,經過MD5後都能生成唯一的MD5值。好比現在的ISO校驗,都 是MD5校驗。怎麼用?當然是把ISO經過MD5後產生MD5的值。一般下載linux-ISO的朋友都見過下載連結旁邊放著MD5的串。就是用來驗證文 件是否一致的。
通過java程式碼實現如下:
/**
* MD5加密
*
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptMD5(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
md5.update(data);
return md5.digest();
}
通常我們不直接使用上述MD5加密。通常將MD5產生的位元組陣列交給BASE64再加密一把,得到相應的字串。
SHA
SHA(Secure Hash Algorithm,安全雜湊演算法),數字簽名等密碼學應用中重要的工具,被廣泛地應用於電子商務等資訊保安領域。雖然,SHA與MD5通過碰撞法都被破解了, 但是SHA仍然是公認的安全加密演算法,較之MD5更為安全。
通過java程式碼實現如下:
/**
* SHA加密
*
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptSHA(byte[] data) throws Exception {
MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
sha.update(data);
return sha.digest();
}
}
HMAC
HMAC(Hash Message Authentication Code,雜湊訊息鑑別碼,基於金鑰的Hash演算法的認證協議。訊息鑑別碼實現鑑別的原理是,用公開函式和金鑰產生一個固定長度的值作為認證標識,用這個 標識鑑別訊息的完整性。使用一個金鑰生成一個固定大小的小資料塊,即MAC,並將其加入到訊息中,然後傳輸。接收方利用與傳送方共享的金鑰進行鑑別認證 等。
通過java程式碼實現如下:
/**
* 初始化HMAC金鑰
*
* @return
* @throws Exception
*/
public static String initMacKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
SecretKey secretKey = keyGenerator.generateKey();
return encryptBASE64(secretKey.getEncoded());
}
/**
* HMAC加密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data);
}
給出一個完整類,如下:
import java.security.MessageDigest;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
/**
* 基礎加密元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class Coder {
public static final String KEY_SHA = "SHA";
public static final String KEY_MD5 = "MD5";
/**
* MAC演算法可選以下多種演算法
*
* <pre>
* HmacMD5
* HmacSHA1
* HmacSHA256
* HmacSHA384
* HmacSHA512
* </pre>
*/
public static final String KEY_MAC = "HmacMD5";
/**
* BASE64解密
*
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/**
* BASE64加密
*
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
/**
* MD5加密
*
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptMD5(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
md5.update(data);
return md5.digest();
}
/**
* SHA加密
*
* @param data
* @return
* @throws Exception
*/
public static byte[] encryptSHA(byte[] data) throws Exception {
MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
sha.update(data);
return sha.digest();
}
/**
* 初始化HMAC金鑰
*
* @return
* @throws Exception
*/
public static String initMacKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
SecretKey secretKey = keyGenerator.generateKey();
return encryptBASE64(secretKey.getEncoded());
}
/**
* HMAC加密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data);
}
}
再給出一個測試類:
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class CoderTest {
@Test
public void test() throws Exception {
String inputStr = "簡單加密";
System.err.println("原文:\n" + inputStr);
byte[] inputData = inputStr.getBytes();
String code = Coder.encryptBASE64(inputData);
System.err.println("BASE64加密後:\n" + code);
byte[] output = Coder.decryptBASE64(code);
String outputStr = new String(output);
System.err.println("BASE64解密後:\n" + outputStr);
// 驗證BASE64加密解密一致性
assertEquals(inputStr, outputStr);
// 驗證MD5對於同一內容加密是否一致
assertArrayEquals(Coder.encryptMD5(inputData), Coder
.encryptMD5(inputData));
// 驗證SHA對於同一內容加密是否一致
assertArrayEquals(Coder.encryptSHA(inputData), Coder
.encryptSHA(inputData));
String key = Coder.initMacKey();
System.err.println("Mac金鑰:\n" + key);
// 驗證HMAC對於同一內容,同一金鑰加密是否一致
assertArrayEquals(Coder.encryptHMAC(inputData, key), Coder.encryptHMAC(
inputData, key));
BigInteger md5 = new BigInteger(Coder.encryptMD5(inputData));
System.err.println("MD5:\n" + md5.toString(16));
BigInteger sha = new BigInteger(Coder.encryptSHA(inputData));
System.err.println("SHA:\n" + sha.toString(32));
BigInteger mac = new BigInteger(Coder.encryptHMAC(inputData, inputStr));
System.err.println("HMAC:\n" + mac.toString(16));
}
}
控制檯輸出:
原文: 簡單加密 BASE64加密後: 566A5Y2V5Yqg5a+G BASE64解密後: 簡單加密 Mac金鑰: uGxdHC+6ylRDaik++leFtGwiMbuYUJ6mqHWyhSgF4trVkVBBSQvY/a22xU8XT1RUemdCWW155Bke pBIpkd7QHg== MD5: -550b4d90349ad4629462113e7934de56 SHA: 91k9vo7p400cjkgfhjh0ia9qthsjagfn HMAC: 2287d192387e95694bdbba2fa941009a
注意
編譯時,可能會看到如下提示:
引用
警告:sun.misc.BASE64Decoder 是 Sun 的專用 API,可能會在未來版本中刪除
import sun.misc.BASE64Decoder;
^
警告:sun.misc.BASE64Encoder 是 Sun 的專用 API,可能會在未來版本中刪除
import sun.misc.BASE64Encoder;
^
BASE64Encoder 和BASE64Decoder是非官方JDK實現類。雖然可以在JDK裡能找到並使用,但是在API裡查不到。JRE 中 sun 和 com.sun 開頭包的類都是未被文件化的,他們屬於 java, javax 類庫的基礎,其中的實現大多數與底層平臺有關,一般來說是不推薦使用的。
BASE64的加密解密是雙向的,可以求反解。
MD5、SHA以及HMAC是單向加密,任何資料加密後只會產生唯一的一個加密串,通常用來校驗資料在傳輸過程中是否被修改。其中HMAC演算法有一個金鑰,增強了資料傳輸過程中的安全性,強化了演算法外的不可控因素。
單向加密的用途主要是為了校驗資料在傳輸過程中是否被修改。
接下來我們介紹對稱加密演算法,最常用的莫過於DES資料加密演算法。
DES
DES-Data Encryption Standard,即資料加密演算法。是IBM公司於1975年研究成功並公開發表的。DES演算法的入口引數有三個:Key、Data、Mode。其中 Key為8個位元組共64位,是DES演算法的工作金鑰;Data也為8個位元組64位,是要被加密或被解密的資料;Mode為DES的工作方式,有兩種:加密 或解密。
DES演算法把64位的明文輸入塊變為64位的密文輸出塊,它所使用的金鑰也是64位。
通過java程式碼實現如下:Coder類見
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
/**
* DES安全編碼元件
*
* <pre>
* 支援 DES、DESede(TripleDES,就是3DES)、AES、Blowfish、RC2、RC4(ARCFOUR)
* DES key size must be equal to 56
* DESede(TripleDES) key size must be equal to 112 or 168
* AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
* Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
* RC2 key size must be between 40 and 1024 bits
* RC4(ARCFOUR) key size must be between 40 and 1024 bits
* 具體內容 需要關注 JDK Document http://.../docs/technotes/guides/security/SunProviders.html
* </pre>
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class DESCoder extends Coder {
/**
* ALGORITHM 演算法 <br>
* 可替換為以下任意一種演算法,同時key值的size相應改變。
*
* <pre>
* DES key size must be equal to 56
* DESede(TripleDES) key size must be equal to 112 or 168
* AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
* Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
* RC2 key size must be between 40 and 1024 bits
* RC4(ARCFOUR) key size must be between 40 and 1024 bits
* </pre>
*
* 在Key toKey(byte[] key)方法中使用下述程式碼
* <code>SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);</code> 替換
* <code>
* DESKeySpec dks = new DESKeySpec(key);
* SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
* SecretKey secretKey = keyFactory.generateSecret(dks);
* </code>
*/
public static final String ALGORITHM = "DES";
/**
* 轉換金鑰<br>
*
* @param key
* @return
* @throws Exception
*/
private static Key toKey(byte[] key) throws Exception {
DESKeySpec dks = new DESKeySpec(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey secretKey = keyFactory.generateSecret(dks);
// 當使用其他對稱加密演算法時,如AES、Blowfish等演算法時,用下述程式碼替換上述三行程式碼
// SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
return secretKey;
}
/**
* 解密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String key) throws Exception {
Key k = toKey(decryptBASE64(key));
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, k);
return cipher.doFinal(data);
}
/**
* 加密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String key) throws Exception {
Key k = toKey(decryptBASE64(key));
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, k);
return cipher.doFinal(data);
}
/**
* 生成金鑰
*
* @return
* @throws Exception
*/
public static String initKey() throws Exception {
return initKey(null);
}
/**
* 生成金鑰
*
* @param seed
* @return
* @throws Exception
*/
public static String initKey(String seed) throws Exception {
SecureRandom secureRandom = null;
if (seed != null) {
secureRandom = new SecureRandom(decryptBASE64(seed));
} else {
secureRandom = new SecureRandom();
}
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);
kg.init(secureRandom);
SecretKey secretKey = kg.generateKey();
return encryptBASE64(secretKey.getEncoded());
}
}
延續上一個類的實現,我們通過MD5以及SHA對字串加密生成金鑰,這是比較常見的金鑰生成方式。
再給出一個測試類:
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class DESCoderTest {
@Test
public void test() throws Exception {
String inputStr = "DES";
String key = DESCoder.initKey();
System.err.println("原文:\t" + inputStr);
System.err.println("金鑰:\t" + key);
byte[] inputData = inputStr.getBytes();
inputData = DESCoder.encrypt(inputData, key);
System.err.println("加密後:\t" + DESCoder.encryptBASE64(inputData));
byte[] outputData = DESCoder.decrypt(inputData, key);
String outputStr = new String(outputData);
System.err.println("解密後:\t" + outputStr);
assertEquals(inputStr, outputStr);
}
}
得到的輸出內容如下:
原文: DES 金鑰: f3wEtRrV6q0= 加密後: C6qe9oNIzRY= 解密後: DES
由控制檯得到的輸出,我們能夠比對加密、解密後結果一致。這是一種簡單的加密解密方式,只有一個金鑰。
其實DES有很多同胞兄弟,如DESede(TripleDES)、AES、Blowfish、RC2、RC4(ARCFOUR)。這裡就不過多闡述了,大同小異,只要換掉ALGORITHM換成對應的值,同時做一個程式碼替換SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);就可以了,此外就是金鑰長度不同了。
/**
* DES key size must be equal to 56
* DESede(TripleDES) key size must be equal to 112 or 168
* AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
* Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
* RC2 key size must be between 40 and 1024 bits
* RC4(ARCFOUR) key size must be between 40 and 1024 bits
**/
除了DES,我們還知道有DESede(TripleDES,就是3DES)、AES、Blowfish、RC2、RC4(ARCFOUR)等多種對稱加密方式,其實現方式大同小異,這裡介紹對稱加密的另一個演算法——PBE
PBE
PBE——Password-based encryption(基於密碼加密)。其特點在於口令由使用者自己掌管,不借助任何物理媒體;採用隨機數(這裡我們叫做鹽)雜湊多重加密等方法保證資料的安全性。是一種簡便的加密方式。
通過java程式碼實現如下:Coder類見
import java.security.Key;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
/**
* PBE安全編碼元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class PBECoder extends Coder {
/**
* 支援以下任意一種演算法
*
* <pre>
* PBEWithMD5AndDES
* PBEWithMD5AndTripleDES
* PBEWithSHA1AndDESede
* PBEWithSHA1AndRC2_40
* </pre>
*/
public static final String ALGORITHM = "PBEWITHMD5andDES";
/**
* 鹽初始化
*
* @return
* @throws Exception
*/
public static byte[] initSalt() throws Exception {
byte[] salt = new byte[8];
Random random = new Random();
random.nextBytes(salt);
return salt;
}
/**
* 轉換金鑰<br>
*
* @param password
* @return
* @throws Exception
*/
private static Key toKey(String password) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey secretKey = keyFactory.generateSecret(keySpec);
return secretKey;
}
/**
* 加密
*
* @param data 資料
* @param password 密碼
* @param salt 鹽
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String password, byte[] salt)
throws Exception {
Key key = toKey(password);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
return cipher.doFinal(data);
}
/**
* 解密
*
* @param data 資料
* @param password 密碼
* @param salt 鹽
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String password, byte[] salt)
throws Exception {
Key key = toKey(password);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
return cipher.doFinal(data);
}
}
再給出一個測試類:
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class PBECoderTest {
@Test
public void test() throws Exception {
String inputStr = "abc";
System.err.println("原文: " + inputStr);
byte[] input = inputStr.getBytes();
String pwd = "efg";
System.err.println("密碼: " + pwd);
byte[] salt = PBECoder.initSalt();
byte[] data = PBECoder.encrypt(input, pwd, salt);
System.err.println("加密後: " + PBECoder.encryptBASE64(data));
byte[] output = PBECoder.decrypt(data, pwd, salt);
String outputStr = new String(output);
System.err.println("解密後: " + outputStr);
assertEquals(inputStr, outputStr);
}
}
控制檯輸出:
原文: abc 密碼: efg 加密後: iCZ0uRtaAhE= 解密後: abc
後續我們會介紹非對稱加密演算法,如RSA、DSA、DH、ECC等。
接下來我們介紹典型的非對稱加密演算法——RSA
RSA
這種演算法1978年就出現了,它是第一個既能用於資料加密也能用於數字簽名的演算法。它易於理解和操作,也很流行。演算法的名字以發明者的名字命名:Ron Rivest, AdiShamir 和Leonard Adleman。
這種加密演算法的特點主要是金鑰的變化,上文我們看到DES只有一個金鑰。相當於只有一把鑰匙,如果這把鑰匙丟了,資料也就不安全了。RSA同時有兩把鑰 匙,公鑰與私鑰。同時支援數字簽名。數字簽名的意義在於,對傳輸過來的資料進行校驗。確保資料在傳輸工程中不被修改。
流程分析:
-
甲方構建金鑰對兒,將公鑰公佈給乙方,將私鑰保留。
-
甲方使用私鑰加密資料,然後用私鑰對加密後的資料簽名,傳送給乙方簽名以及加密後的資料;乙方使用公鑰、簽名來驗證待解密資料是否有效,如果有效使用公鑰對資料解密。
-
乙方使用公鑰加密資料,向甲方傳送經過加密後的資料;甲方獲得加密資料,通過私鑰解密。
按如上步驟給出序列圖,如下:
通過java程式碼實現如下:Coder類見
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.Signature;
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;
/**
* RSA安全編碼元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class RSACoder extends Coder {
public static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 用私鑰對資訊生成數字簽名
*
* @param data
* 加密資料
* @param privateKey
* 私鑰
*
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
// 解密由base64編碼的私鑰
byte[] keyBytes = decryptBASE64(privateKey);
// 構造PKCS8EncodedKeySpec物件
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密演算法
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取私鑰匙物件
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 用私鑰對資訊生成數字簽名
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(priKey);
signature.update(data);
return encryptBASE64(signature.sign());
}
/**
* 校驗數字簽名
*
* @param data
* 加密資料
* @param publicKey
* 公鑰
* @param sign
* 數字簽名
*
* @return 校驗成功返回true 失敗返回false
* @throws Exception
*
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
// 解密由base64編碼的公鑰
byte[] keyBytes = decryptBASE64(publicKey);
// 構造X509EncodedKeySpec物件
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密演算法
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取公鑰匙物件
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
// 驗證簽名是否正常
return signature.verify(decryptBASE64(sign));
}
/**
* 解密<br>
* 用私鑰解密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String key)
throws Exception {
// 對金鑰解密
byte[] keyBytes = decryptBASE64(key);
// 取得私鑰
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 對資料解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 解密<br>
* 用私鑰解密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String key)
throws Exception {
// 對金鑰解密
byte[] keyBytes = decryptBASE64(key);
// 取得公鑰
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicKey = keyFactory.generatePublic(x509KeySpec);
// 對資料解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 加密<br>
* 用公鑰加密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String key)
throws Exception {
// 對公鑰解密
byte[] keyBytes = decryptBASE64(key);
// 取得公鑰
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicKey = keyFactory.generatePublic(x509KeySpec);
// 對資料加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 加密<br>
* 用私鑰加密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String key)
throws Exception {
// 對金鑰解密
byte[] keyBytes = decryptBASE64(key);
// 取得私鑰
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 對資料加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 取得私鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 取得公鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 初始化金鑰
*
* @return
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator
.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
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;
}
}
再給出一個測試類:
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import java.util.Map;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class RSACoderTest {
private String publicKey;
private String privateKey;
@Before
public void setUp() throws Exception {
Map<String, Object> keyMap = RSACoder.initKey();
publicKey = RSACoder.getPublicKey(keyMap);
privateKey = RSACoder.getPrivateKey(keyMap);
System.err.println("公鑰: \n\r" + publicKey);
System.err.println("私鑰: \n\r" + privateKey);
}
@Test
public void test() throws Exception {
System.err.println("公鑰加密——私鑰解密");
String inputStr = "abc";
byte[] data = inputStr.getBytes();
byte[] encodedData = RSACoder.encryptByPublicKey(data, publicKey);
byte[] decodedData = RSACoder.decryptByPrivateKey(encodedData,
privateKey);
String outputStr = new String(decodedData);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
assertEquals(inputStr, outputStr);
}
@Test
public void testSign() throws Exception {
System.err.println("私鑰加密——公鑰解密");
String inputStr = "sign";
byte[] data = inputStr.getBytes();
byte[] encodedData = RSACoder.encryptByPrivateKey(data, privateKey);
byte[] decodedData = RSACoder
.decryptByPublicKey(encodedData, publicKey);
String outputStr = new String(decodedData);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
assertEquals(inputStr, outputStr);
System.err.println("私鑰簽名——公鑰驗證簽名");
// 產生簽名
String sign = RSACoder.sign(encodedData, privateKey);
System.err.println("簽名:\r" + sign);
// 驗證簽名
boolean status = RSACoder.verify(encodedData, publicKey, sign);
System.err.println("狀態:\r" + status);
assertTrue(status);
}
}
控制檯輸出:
公鑰: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYU/+I0+z1aBl5X6DUUOHQ7FZpmBSDbKTtx89J EcB64jFCkunELT8qiKly7fzEqD03g8ALlu5XvX+bBqHFy7YPJJP0ekE2X3wjUnh2NxlqpH3/B/xm 1ZdSlCwDIkbijhBVDjA/bu5BObhZqQmDwIxlQInL9oVz+o6FbAZCyHBd7wIDAQAB 私鑰: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJhT/4jT7PVoGXlfoNRQ4dDsVmmY FINspO3Hz0kRwHriMUKS6cQtPyqIqXLt/MSoPTeDwAuW7le9f5sGocXLtg8kk/R6QTZffCNSeHY3 GWqkff8H/GbVl1KULAMiRuKOEFUOMD9u7kE5uFmpCYPAjGVAicv2hXP6joVsBkLIcF3vAgMBAAEC gYBvZHWoZHmS2EZQqKqeuGr58eobG9hcZzWQoJ4nq/CarBAjw/VovUHE490uK3S9ht4FW7Yzg3LV /MB06Huifh6qf/X9NQA7SeZRRC8gnCQk6JuDIEVJOud5jU+9tyumJakDKodQ3Jf2zQtNr+5ZdEPl uwWgv9c4kmpjhAdyMuQmYQJBANn6pcgvyYaia52dnu+yBUsGkaFfwXkzFSExIbi0MXTkhEb/ER/D rLytukkUu5S5ecz/KBa8U4xIslZDYQbLz5ECQQCy5dutt7RsxN4+dxCWn0/1FrkWl2G329Ucewm3 QU9CKu4D+7Kqdj+Ha3lXP8F0Etaaapi7+EfkRUpukn2ItZV/AkEAlk+I0iphxT1rCB0Q5CjWDY5S Df2B5JmdEG5Y2o0nLXwG2w44OLct/k2uD4cEcuITY5Dvi/4BftMCZwm/dnhEgQJACIktJSnJwxLV o9dchENPtlsCM9C/Sd2EWpqISSUlmfugZbJBwR5pQ5XeMUqKeXZYpP+HEBj1nS+tMH9u2/IGEwJA fL8mZiZXan/oBKrblAbplNcKWGRVD/3y65042PAEeghahlJMiYquV5DzZajuuT0wbJ5xQuZB01+X nfpFpBJ2dw== 公鑰加密——私鑰解密 加密前: abc 解密後: abc 公鑰: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdOj40yEB48XqWxmPILmJAc7UecIN7F32etSHF 9rwbuEh3+iTPOGSxhoSQpOED0vOb0ZIMkBXZSgsxLaBSin2RZ09YKWRjtpCA0kDkiD11gj4tzTiM l9qq1kwSK7ZkGAgodEn3yIILVmQDuEImHOXFtulvJ71ka07u3LuwUNdB/wIDAQAB 私鑰: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAN06PjTIQHjxepbGY8guYkBztR5w g3sXfZ61IcX2vBu4SHf6JM84ZLGGhJCk4QPS85vRkgyQFdlKCzEtoFKKfZFnT1gpZGO2kIDSQOSI PXWCPi3NOIyX2qrWTBIrtmQYCCh0SffIggtWZAO4QiYc5cW26W8nvWRrTu7cu7BQ10H/AgMBAAEC gYEAz2JWBizjI31bqhP4XiP9PuY5F3vqBW4T+L9cFbQiyumKJc58yzTWUAUGKIIn3enXLG7dNqGr mbJro4JeFIJ3CiVDpXR9+FluIgI4SXm7ioGKF2NOMA9LR5Fu82W+pLfpTN2y2SaLYWEDZyp53BxY j9gUxaxi1MQs+C1ZgDF2xmECQQDy70bQntbRfysP+ppCtd56YRnES1Tyekw0wryS2tr+ivQJl7JF gp5rPAOXpgrq36xHDwUspQ0sJ0vj0O7ywxr1AkEA6SAaLhrJJrYucC0jxwAhUYyaPN+aOsWymaRh 9jA/Wc0wp29SbGTh5CcMuGpXm1g0M+FKW3dGiHgS3rVUKim4owJAbnxgapUzAgiiHxxMeDaavnHW 9C2GrtjsO7qtZOTgYI/1uT8itvZW8lJTF+9OW8/qXE76fXl7ai9dFnl5kzMk2QJBALfHz/vCsArt mkRiwY6zApE4Z6tPl1V33ymSVovvUzHnOdD1SKQdD5t+UV/crb3QVi8ED0t2B0u0ZSPfDT/D7kMC QDpwdj9k2F5aokLHBHUNJPFDAp7a5QMaT64gv/d48ITJ68Co+v5WzLMpzJBYXK6PAtqIhxbuPEc2 I2k1Afmrwyw= 私鑰加密——公鑰解密 加密前: sign 解密後: sign 私鑰簽名——公鑰驗證簽名 簽名: ud1RsIwmSC1pN22I4IXteg1VD2FbiehKUfNxgVSHzvQNIK+d20FCkHCqh9djP3h94iWnIUY0ifU+ mbJkhAl/i5krExOE0hknOnPMcEP+lZV1RbJI2zG2YooSp2XDleqrQk5e/QF2Mx0Zxt8Xsg7ucVpn i3wwbYWs9wSzIf0UjlM= 狀態: true
簡要總結一下,使用公鑰加密、私鑰解密,完成了乙方到甲方的一次資料傳遞,通過私鑰加密、公鑰解密,同時通過私鑰簽名、公鑰驗證簽名,完成了一次甲方到乙方的資料傳遞與驗證,兩次資料傳遞完成一整套的資料互動!
類似數字簽名,數字信封是這樣描述的:
數字信封
數字信封用加密技術來保證只有特定的收信人才能閱讀信的內容。
流程:
資訊傳送方採用對稱金鑰來加密資訊,然後再用接收方的公鑰來加密此對稱金鑰(這部分稱為數字信封),再將它和資訊一起傳送給接收方;接收方先用相應的私鑰開啟數字信封,得到對稱金鑰,然後使用對稱金鑰再解開資訊。
接下來我們分析DH加密演算法,一種適基於金鑰一致協議的加密演算法。
DH
Diffie- Hellman演算法(D-H演算法),金鑰一致協議。是由公開金鑰密碼體制的奠基人Diffie和Hellman所提出的一種思想。簡單的說就是允許兩名用 戶在公開媒體上交換資訊以生成"一致"的、可以共享的金鑰。換句話說,就是由甲方產出一對金鑰(公鑰、私鑰),乙方依照甲方公鑰產生乙方金鑰對(公鑰、私 鑰)。以此為基線,作為資料傳輸保密基礎,同時雙方使用同一種對稱加密演算法構建本地金鑰(SecretKey)對資料加密。這樣,在互通了本地金鑰 (SecretKey)演算法後,甲乙雙方公開自己的公鑰,使用對方的公鑰和剛才產生的私鑰加密資料,同時可以使用對方的公鑰和自己的私鑰對資料解密。不單
單是甲乙雙方兩方,可以擴充套件為多方共享資料通訊,這樣就完成了網路互動資料的安全通訊!該演算法源於中國的同餘定理——中國餘數定理。
流程分析:
1.甲方構建金鑰對兒,將公鑰公佈給乙方,將私鑰保留;雙方約定資料加密演算法;乙方通過甲方公鑰構建金鑰對兒,將公鑰公佈給甲方,將私鑰保留。
2.甲方使用私鑰、乙方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰加密資料,傳送給乙方加密後的資料;乙方使用私鑰、甲方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰對資料解密。
3.乙方使用私鑰、甲方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰加密資料,傳送給甲方加密後的資料;甲方使用私鑰、乙方公鑰、約定資料加密演算法構建本地金鑰,然後通過本地金鑰對資料解密。
通過java程式碼實現如下:Coder類見
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
/**
* DH安全編碼元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class DHCoder extends Coder {
public static final String ALGORITHM = "DH";
/**
* 預設金鑰位元組數
*
* <pre>
* DH
* Default Keysize 1024
* Keysize must be a multiple of 64, ranging from 512 to 1024 (inclusive).
* </pre>
*/
private static final int KEY_SIZE = 1024;
/**
* DH加密下需要一種對稱加密演算法對資料加密,這裡我們使用DES,也可以使用其他對稱加密演算法。
*/
public static final String SECRET_ALGORITHM = "DES";
private static final String PUBLIC_KEY = "DHPublicKey";
private static final String PRIVATE_KEY = "DHPrivateKey";
/**
* 初始化甲方金鑰
*
* @return
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator
.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 甲方公鑰
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
// 甲方私鑰
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 初始化乙方金鑰
*
* @param key
* 甲方公鑰
* @return
* @throws Exception
*/
public static Map<String, Object> initKey(String key) throws Exception {
// 解析甲方公鑰
byte[] keyBytes = decryptBASE64(key);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// 由甲方公鑰構建乙方金鑰
DHParameterSpec dhParamSpec = ((DHPublicKey) pubKey).getParams();
KeyPairGenerator keyPairGenerator = KeyPairGenerator
.getInstance(keyFactory.getAlgorithm());
keyPairGenerator.initialize(dhParamSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 乙方公鑰
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
// 乙方私鑰
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 加密<br>
*
* @param data
* 待加密資料
* @param publicKey
* 甲方公鑰
* @param privateKey
* 乙方私鑰
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String publicKey,
String privateKey) throws Exception {
// 生成本地金鑰
SecretKey secretKey = getSecretKey(publicKey, privateKey);
// 資料加密
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(data);
}
/**
* 解密<br>
*
* @param data
* 待解密資料
* @param publicKey
* 乙方公鑰
* @param privateKey
* 乙方私鑰
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String publicKey,
String privateKey) throws Exception {
// 生成本地金鑰
SecretKey secretKey = getSecretKey(publicKey, privateKey);
// 資料解密
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(data);
}
/**
* 構建金鑰
*
* @param publicKey
* 公鑰
* @param privateKey
* 私鑰
* @return
* @throws Exception
*/
private static SecretKey getSecretKey(String publicKey, String privateKey)
throws Exception {
// 初始化公鑰
byte[] pubKeyBytes = decryptBASE64(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKeyBytes);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// 初始化私鑰
byte[] priKeyBytes = decryptBASE64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKeyBytes);
Key priKey = keyFactory.generatePrivate(pkcs8KeySpec);
KeyAgreement keyAgree = KeyAgreement.getInstance(keyFactory
.getAlgorithm());
keyAgree.init(priKey);
keyAgree.doPhase(pubKey, true);
// 生成本地金鑰
SecretKey secretKey = keyAgree.generateSecret(SECRET_ALGORITHM);
return secretKey;
}
/**
* 取得私鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 取得公鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encryptBASE64(key.getEncoded());
}
}
再給出一個測試類:
import static org.junit.Assert.*;
import java.util.Map;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class DHCoderTest {
@Test
public void test() throws Exception {
// 生成甲方金鑰對兒
Map<String, Object> aKeyMap = DHCoder.initKey();
String aPublicKey = DHCoder.getPublicKey(aKeyMap);
String aPrivateKey = DHCoder.getPrivateKey(aKeyMap);
System.err.println("甲方公鑰:\r" + aPublicKey);
System.err.println("甲方私鑰:\r" + aPrivateKey);
// 由甲方公鑰產生本地金鑰對兒
Map<String, Object> bKeyMap = DHCoder.initKey(aPublicKey);
String bPublicKey = DHCoder.getPublicKey(bKeyMap);
String bPrivateKey = DHCoder.getPrivateKey(bKeyMap);
System.err.println("乙方公鑰:\r" + bPublicKey);
System.err.println("乙方私鑰:\r" + bPrivateKey);
String aInput = "abc ";
System.err.println("原文: " + aInput);
// 由甲方公鑰,乙方私鑰構建密文
byte[] aCode = DHCoder.encrypt(aInput.getBytes(), aPublicKey,
bPrivateKey);
// 由乙方公鑰,甲方私鑰解密
byte[] aDecode = DHCoder.decrypt(aCode, bPublicKey, aPrivateKey);
String aOutput = (new String(aDecode));
System.err.println("解密: " + aOutput);
assertEquals(aInput, aOutput);
System.err.println(" ===============反過來加密解密================== ");
String bInput = "def ";
System.err.println("原文: " + bInput);
// 由乙方公鑰,甲方私鑰構建密文
byte[] bCode = DHCoder.encrypt(bInput.getBytes(), bPublicKey,
aPrivateKey);
// 由甲方公鑰,乙方私鑰解密
byte[] bDecode = DHCoder.decrypt(bCode, aPublicKey, bPrivateKey);
String bOutput = (new String(bDecode));
System.err.println("解密: " + bOutput);
assertEquals(bInput, bOutput);
}
}
控制檯輸出:
甲方公鑰: MIHfMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHz W5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG kx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgANDAAJAdAWBVmIzqcko Ej6qFjLDL2+Y3FPq1iRbnOyOpDj71yKaK1K+FhTv04B0zy4DKcvAASV7/Gv0W+bgqdmffRkqrQ== 甲方私鑰: MIHRAgEAMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYX rgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpD TWSGkx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgAQyAjACJRfy1LyR eHyD+4Hfb+xR0uoIGR1oL9i9Nk6g2AAuaDPgEVWHn+QXID13yL/uDos= 乙方公鑰: MIHfMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHz W5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG kx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgANDAAJAVEYSfBA+I9nr dWw3OBv475C+eBrWBBYqt0m6/eu4ptuDQHwV4MmUtKAC2wc2nNrdb1wmBhY1X8RnWkJ1XmdDbQ== 乙方私鑰: MIHSAgEAMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYX rgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpD TWSGkx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgAQzAjEAqaZiCdXp 2iNpdBlHRaO9ir70wo2n32xNlIzIX19VLSPCDdeUWkgRv4CEj/8k+/yd 原文: abc 解密: abc ===============反過來加密解密================== 原文: def 解密: def
如我所言,甲乙雙方在獲得對方公鑰後可以對傳送給對方的資料加密,同時也能對接收到的資料解密,達到了資料安全通訊的目的!
接下來我們介紹DSA數字簽名,非對稱加密的另一種實現。
DSA
DSA-Digital Signature Algorithm 是Schnorr和ElGamal簽名演算法的變種,被美國NIST作為DSS(DigitalSignature Standard)。簡單的說,這是一種更高階的驗證方式,用作數字簽名。不單單隻有公鑰、私鑰,還有數字簽名。私鑰加密生成數字簽名,公鑰驗證資料及籤 名。如果資料和簽名不匹配則認為驗證失敗!數字簽名的作用就是校驗資料在傳輸過程中不被修改。數字簽名,是單向加密的升級!
通過java程式碼實現如下:Coder類見
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.SecureRandom;
import java.security.Signature;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* DSA安全編碼元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class DSACoder extends Coder {
public static final String ALGORITHM = "DSA";
/**
* 預設金鑰位元組數
*
* <pre>
* DSA
* Default Keysize 1024
* Keysize must be a multiple of 64, ranging from 512 to 1024 (inclusive).
* </pre>
*/
private static final int KEY_SIZE = 1024;
/**
* 預設種子
*/
private static final String DEFAULT_SEED = "0f22507a10bbddd07d8a3082122966e3";
private static final String PUBLIC_KEY = "DSAPublicKey";
private static final String PRIVATE_KEY = "DSAPrivateKey";
/**
* 用私鑰對資訊生成數字簽名
*
* @param data
* 加密資料
* @param privateKey
* 私鑰
*
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
// 解密由base64編碼的私鑰
byte[] keyBytes = decryptBASE64(privateKey);
// 構造PKCS8EncodedKeySpec物件
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密演算法
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
// 取私鑰匙物件
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 用私鑰對資訊生成數字簽名
Signature signature = Signature.getInstance(keyFactory.getAlgorithm());
signature.initSign(priKey);
signature.update(data);
return encryptBASE64(signature.sign());
}
/**
* 校驗數字簽名
*
* @param data
* 加密資料
* @param publicKey
* 公鑰
* @param sign
* 數字簽名
*
* @return 校驗成功返回true 失敗返回false
* @throws Exception
*
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
// 解密由base64編碼的公鑰
byte[] keyBytes = decryptBASE64(publicKey);
// 構造X509EncodedKeySpec物件
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// ALGORITHM 指定的加密演算法
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
// 取公鑰匙物件
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(keyFactory.getAlgorithm());
signature.initVerify(pubKey);
signature.update(data);
// 驗證簽名是否正常
return signature.verify(decryptBASE64(sign));
}
/**
* 生成金鑰
*
* @param seed
* 種子
* @return 金鑰物件
* @throws Exception
*/
public static Map<String, Object> initKey(String seed) throws Exception {
KeyPairGenerator keygen = KeyPairGenerator.getInstance(ALGORITHM);
// 初始化隨機產生器
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(seed.getBytes());
keygen.initialize(KEY_SIZE, secureRandom);
KeyPair keys = keygen.genKeyPair();
DSAPublicKey publicKey = (DSAPublicKey) keys.getPublic();
DSAPrivateKey privateKey = (DSAPrivateKey) keys.getPrivate();
Map<String, Object> map = new HashMap<String, Object>(2);
map.put(PUBLIC_KEY, publicKey);
map.put(PRIVATE_KEY, privateKey);
return map;
}
/**
* 預設生成金鑰
*
* @return 金鑰物件
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
return initKey(DEFAULT_SEED);
}
/**
* 取得私鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 取得公鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encryptBASE64(key.getEncoded());
}
}
再給出一個測試類:
import static org.junit.Assert.*;
import java.util.Map;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class DSACoderTest {
@Test
public void test() throws Exception {
String inputStr = "abc";
byte[] data = inputStr.getBytes();
// 構建金鑰
Map<String, Object> keyMap = DSACoder.initKey();
// 獲得金鑰
String publicKey = DSACoder.getPublicKey(keyMap);
String privateKey = DSACoder.getPrivateKey(keyMap);
System.err.println("公鑰:\r" + publicKey);
System.err.println("私鑰:\r" + privateKey);
// 產生簽名
String sign = DSACoder.sign(data, privateKey);
System.err.println("簽名:\r" + sign);
// 驗證簽名
boolean status = DSACoder.verify(data, publicKey, sign);
System.err.println("狀態:\r" + status);
assertTrue(status);
}
}
控制檯輸出:
公鑰: MIIBtzCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZp RV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fn xqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuE C/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJ FnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoDgYQAAoGAIu4RUlcQLp49PI0MrbssOY+3uySVnp0TULSv 5T4VaHoKzsLHgGTrwOvsGA+V3yCNl2WDu3D84bSLF7liTWgOj+SMOEaPk4VyRTlLXZWGPsf1Mfd9 21XAbMeVyKDSHHVGbMjBScajf3bXooYQMlyoHiOt/WrCo+mv7efstMM0PGo= 私鑰: MIIBTAIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2 USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4 O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmC ouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCB gLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhR kImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFwIVAIegLUtmm2oQKQJTOiLugHTSjl/q 簽名: MC0CFQCMg0J/uZmF8GuRpr3TNq48w60nDwIUJCyYNah+HtbU6NcQfy8Ac6LeLQs= 狀態: true
注意狀態為true,就驗證成功!
ECC
ECC-Elliptic Curves Cryptography,橢圓曲線密碼編碼學,是目前已知的公鑰體制中,對每位元所提供加密強度最高的一種體制。在軟體註冊保護方面起到很大的作用,一般的序列號通常由該演算法產生。
當我開始整理《Java加密技術(二)》的時候,我就已經在開始研究ECC了,但是關於Java實現ECC演算法的資料實在是太少了,無論是國內還是國外的 資料,無論是官方還是非官方的解釋,最終只有一種答案——ECC演算法在jdk1.5後加入支援,目前僅僅只能完成金鑰的生成與解析。 如果想要獲得ECC演算法實現,需要呼叫硬體完成加密/解密(ECC演算法相當耗費資源,如果單純使用CPU進行加密/解密,效率低下),涉及到Java Card領域,PKCS#11。 其實,PKCS#11配置很簡單,但缺乏硬體裝置,無法嘗試!
儘管如此,我照舊提供相應的Java實現程式碼,以供大家參考。
通過java程式碼實現如下:Coder類見
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldF2m;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.NullCipher;
import sun.security.ec.ECKeyFactory;
import sun.security.ec.ECPrivateKeyImpl;
import sun.security.ec.ECPublicKeyImpl;
/**
* ECC安全編碼元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class ECCCoder extends Coder {
public static final String ALGORITHM = "EC";
private static final String PUBLIC_KEY = "ECCPublicKey";
private static final String PRIVATE_KEY = "ECCPrivateKey";
/**
* 解密<br>
* 用私鑰解密
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String key) throws Exception {
// 對金鑰解密
byte[] keyBytes = decryptBASE64(key);
// 取得私鑰
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = ECKeyFactory.INSTANCE;
ECPrivateKey priKey = (ECPrivateKey) keyFactory
.generatePrivate(pkcs8KeySpec);
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(priKey.getS(),
priKey.getParams());
// 對資料解密
// TODO Chipher不支援EC演算法 未能實現
Cipher cipher = new NullCipher();
// Cipher.getInstance(ALGORITHM, keyFactory.getProvider());
cipher.init(Cipher.DECRYPT_MODE, priKey, ecPrivateKeySpec.getParams());
return cipher.doFinal(data);
}
/**
* 加密<br>
* 用公鑰加密
*
* @param data
* @param privateKey
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String privateKey)
throws Exception {
// 對公鑰解密
byte[] keyBytes = decryptBASE64(privateKey);
// 取得公鑰
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = ECKeyFactory.INSTANCE;
ECPublicKey pubKey = (ECPublicKey) keyFactory
.generatePublic(x509KeySpec);
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(pubKey.getW(),
pubKey.getParams());
// 對資料加密
// TODO Chipher不支援EC演算法 未能實現
Cipher cipher = new NullCipher();
// Cipher.getInstance(ALGORITHM, keyFactory.getProvider());
cipher.init(Cipher.ENCRYPT_MODE, pubKey, ecPublicKeySpec.getParams());
return cipher.doFinal(data);
}
/**
* 取得私鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 取得公鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encryptBASE64(key.getEncoded());
}
/**
* 初始化金鑰
*
* @return
* @throws Exception
*/
public static Map<String, Object> initKey() throws Exception {
BigInteger x1 = new BigInteger(
"2fe13c0537bbc11acaa07d793de4e6d5e5c94eee8", 16);
BigInteger x2 = new BigInteger(
"289070fb05d38ff58321f2e800536d538ccdaa3d9", 16);
ECPoint g = new ECPoint(x1, x2);
// the order of generator
BigInteger n = new BigInteger(
"5846006549323611672814741753598448348329118574063", 10);
// the cofactor
int h = 2;
int m = 163;
int[] ks = { 7, 6, 3 };
ECFieldF2m ecField = new ECFieldF2m(m, ks);
// y^2+xy=x^3+x^2+1
BigInteger a = new BigInteger("1", 2);
BigInteger b = new BigInteger("1", 2);
EllipticCurve ellipticCurve = new EllipticCurve(ecField, a, b);
ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, g,
n, h);
// 公鑰
ECPublicKey publicKey = new ECPublicKeyImpl(g, ecParameterSpec);
BigInteger s = new BigInteger(
"1234006549323611672814741753598448348329118574063", 10);
// 私鑰
ECPrivateKey privateKey = new ECPrivateKeyImpl(s, ecParameterSpec);
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
}
請注意上述程式碼中的TODO內容,再次提醒注意,Chipher不支援EC演算法 ,以上程式碼僅供參考。Chipher、Signature、KeyPairGenerator、KeyAgreement、SecretKey均不支援EC演算法。為了確保程式能夠正常執行,我們使用了NullCipher類,驗證程式。
照舊提供一個測試類:
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.security.spec.ECFieldF2m;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.util.Map;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class ECCCoderTest {
@Test
public void test() throws Exception {
String inputStr = "abc";
byte[] data = inputStr.getBytes();
Map<String, Object> keyMap = ECCCoder.initKey();
String publicKey = ECCCoder.getPublicKey(keyMap);
String privateKey = ECCCoder.getPrivateKey(keyMap);
System.err.println("公鑰: \n" + publicKey);
System.err.println("私鑰: \n" + privateKey);
byte[] encodedData = ECCCoder.encrypt(data, publicKey);
byte[] decodedData = ECCCoder.decrypt(encodedData, privateKey);
String outputStr = new String(decodedData);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
assertEquals(inputStr, outputStr);
}
}
控制檯輸出:
公鑰: MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAv4TwFN7vBGsqgfXk95ObV5clO7oAokHD7BdOP9YMh8u gAU21TjM2qPZ 私鑰: MDICAQAwEAYHKoZIzj0CAQYFK4EEAAEEGzAZAgEBBBTYJsR3BN7TFw7JHcAHFkwNmfil7w== 加密前: abc 解密後: abc
本篇的主要內容為Java證照體系的實現。
在構建Java程式碼實現前,我們需要完成證照的製作。
1.生成keyStroe檔案
在命令列下執行以下命令:
keytool -genkey -validity 36000 -alias www.zlex.org -keyalg RSA -keystore d:\zlex.keystore
其中
-genkey表示生成金鑰
-validity指定證照有效期,這裡是36000天
-alias指定別名,這裡是www.zlex.org
-keyalg指定演算法,這裡是RSA
-keystore指定儲存位置,這裡是d:\zlex.keystore
在這裡我使用的密碼為 123456
控制檯輸出:
輸入keystore密碼: 再次輸入新密碼: 您的名字與姓氏是什麼? [Unknown]: www.zlex.org 您的組織單位名稱是什麼? [Unknown]: zlex 您的組織名稱是什麼? [Unknown]: zlex 您所在的城市或區域名稱是什麼? [Unknown]: BJ 您所在的州或省份名稱是什麼? [Unknown]: BJ 該單位的兩字母國家程式碼是什麼 [Unknown]: CN CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 正確嗎? [否]: Y 輸入<tomcat>的主密碼 (如果和 keystore 密碼相同,按回車): 再次輸入新密碼:
這時,在D盤下會生成一個zlex.keystore的檔案。
2.生成自簽名證照
光有keyStore檔案是不夠的,還需要證照檔案,證照才是直接提供給外界使用的公鑰憑證。
匯出證照:
keytool -export -keystore d:\zlex.keystore -alias www.zlex.org -file d:\zlex.cer -rfc
其中
-export指定為匯出操作
-keystore指定keystore檔案
-alias指定匯出keystore檔案中的別名
-file指向匯出路徑
-rfc以文字格式輸出,也就是以BASE64編碼輸出
這裡的密碼是 123456
控制檯輸出:
輸入keystore密碼: 儲存在檔案中的認證 <d:\zlex.cer>
當然,使用方是需要匯入證照的!
可以通過自簽名證照完成CAS單點登入系統的構建!
Ok,準備工作完成,開始Java實現!
通過java程式碼實現如下:Coder類見
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.crypto.Cipher;
/**
* 證照元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class CertificateCoder extends Coder {
/**
* Java金鑰庫(Java Key Store,JKS)KEY_STORE
*/
public static final String KEY_STORE = "JKS";
public static final String X509 = "X.509";
/**
* 由KeyStore獲得私鑰
*
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKey(String keyStorePath, String alias,
String password) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, password);
PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
return key;
}
/**
* 由Certificate獲得公鑰
*
* @param certificatePath
* @return
* @throws Exception
*/
private static PublicKey getPublicKey(String certificatePath)
throws Exception {
Certificate certificate = getCertificate(certificatePath);
PublicKey key = certificate.getPublicKey();
return key;
}
/**
* 獲得Certificate
*
* @param certificatePath
* @return
* @throws Exception
*/
private static Certificate getCertificate(String certificatePath)
throws Exception {
CertificateFactory certificateFactory = CertificateFactory
.getInstance(X509);
FileInputStream in = new FileInputStream(certificatePath);
Certificate certificate = certificateFactory.generateCertificate(in);
in.close();
return certificate;
}
/**
* 獲得Certificate
*
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
private static Certificate getCertificate(String keyStorePath,
String alias, String password) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, password);
Certificate certificate = ks.getCertificate(alias);
return certificate;
}
/**
* 獲得KeyStore
*
* @param keyStorePath
* @param password
* @return
* @throws Exception
*/
private static KeyStore getKeyStore(String keyStorePath, String password)
throws Exception {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore ks = KeyStore.getInstance(KEY_STORE);
ks.load(is, password.toCharArray());
is.close();
return ks;
}
/**
* 私鑰加密
*
* @param data
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
String alias, String password) throws Exception {
// 取得私鑰
PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
// 對資料加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私鑰解密
*
* @param data
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
String alias, String password) throws Exception {
// 取得私鑰
PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
// 對資料加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公鑰加密
*
* @param data
* @param certificatePath
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
throws Exception {
// 取得公鑰
PublicKey publicKey = getPublicKey(certificatePath);
// 對資料加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公鑰解密
*
* @param data
* @param certificatePath
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
throws Exception {
// 取得公鑰
PublicKey publicKey = getPublicKey(certificatePath);
// 對資料加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 驗證Certificate
*
* @param certificatePath
* @return
*/
public static boolean verifyCertificate(String certificatePath) {
return verifyCertificate(new Date(), certificatePath);
}
/**
* 驗證Certificate是否過期或無效
*
* @param date
* @param certificatePath
* @return
*/
public static boolean verifyCertificate(Date date, String certificatePath) {
boolean status = true;
try {
// 取得證照
Certificate certificate = getCertificate(certificatePath);
// 驗證證照是否過期或無效
status = verifyCertificate(date, certificate);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 驗證證照是否過期或無效
*
* @param date
* @param certificate
* @return
*/
private static boolean verifyCertificate(Date date, Certificate certificate) {
boolean status = true;
try {
X509Certificate x509Certificate = (X509Certificate) certificate;
x509Certificate.checkValidity(date);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 簽名
*
* @param keyStorePath
* @param alias
* @param password
*
* @return
* @throws Exception
*/
public static String sign(byte[] sign, String keyStorePath, String alias,
String password) throws Exception {
// 獲得證照
X509Certificate x509Certificate = (X509Certificate) getCertificate(
keyStorePath, alias, password);
// 獲取私鑰
KeyStore ks = getKeyStore(keyStorePath, password);
// 取得私鑰
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password
.toCharArray());
// 構建簽名
Signature signature = Signature.getInstance(x509Certificate
.getSigAlgName());
signature.initSign(privateKey);
signature.update(sign);
return encryptBASE64(signature.sign());
}
/**
* 驗證簽名
*
* @param data
* @param sign
* @param certificatePath
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String sign,
String certificatePath) throws Exception {
// 獲得證照
X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
// 獲得公鑰
PublicKey publicKey = x509Certificate.getPublicKey();
// 構建簽名
Signature signature = Signature.getInstance(x509Certificate
.getSigAlgName());
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(decryptBASE64(sign));
}
/**
* 驗證Certificate
*
* @param keyStorePath
* @param alias
* @param password
* @return
*/
public static boolean verifyCertificate(Date date, String keyStorePath,
String alias, String password) {
boolean status = true;
try {
Certificate certificate = getCertificate(keyStorePath, alias,
password);
status = verifyCertificate(date, certificate);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 驗證Certificate
*
* @param keyStorePath
* @param alias
* @param password
* @return
*/
public static boolean verifyCertificate(String keyStorePath, String alias,
String password) {
return verifyCertificate(new Date(), keyStorePath, alias, password);
}
}
再給出一個測試類:
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class CertificateCoderTest {
private String password = "123456";
private String alias = "www.zlex.org";
private String certificatePath = "d:/zlex.cer";
private String keyStorePath = "d:/zlex.keystore";
@Test
public void test() throws Exception {
System.err.println("公鑰加密——私鑰解密");
String inputStr = "Ceritifcate";
byte[] data = inputStr.getBytes();
byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
certificatePath);
byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
keyStorePath, alias, password);
String outputStr = new String(decrypt);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
// 驗證資料一致
assertArrayEquals(data, decrypt);
// 驗證證照有效
assertTrue(CertificateCoder.verifyCertificate(certificatePath));
}
@Test
public void testSign() throws Exception {
System.err.println("私鑰加密——公鑰解密");
String inputStr = "sign";
byte[] data = inputStr.getBytes();
byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
keyStorePath, alias, password);
byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
certificatePath);
String outputStr = new String(decodedData);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
assertEquals(inputStr, outputStr);
System.err.println("私鑰簽名——公鑰驗證簽名");
// 產生簽名
String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
password);
System.err.println("簽名:\r" + sign);
// 驗證簽名
boolean status = CertificateCoder.verify(encodedData, sign,
certificatePath);
System.err.println("狀態:\r" + status);
assertTrue(status);
}
}
控制檯輸出:
公鑰加密——私鑰解密 加密前: Ceritificate 解密後: Ceritificate 私鑰加密——公鑰解密 加密前: sign 解密後: sign 私鑰簽名——公鑰驗證簽名 簽名: pqBn5m6PJlfOjH0A6U2o2mUmBsfgyEY1NWCbiyA/I5Gc3gaVNVIdj/zkGNZRqTjhf3+J9a9z9EI7 6F2eWYd7punHx5oh6hfNgcKbVb52EfItl4QEN+djbXiPynn07+Lbg1NOjULnpEd6ZhLP1YwrEAuM OfvX0e7/wplxLbySaKQ= 狀態: true
由此完成了證照驗證體系!
同樣,我們可以對程式碼做簽名——程式碼簽名!
通過工具JarSigner可以完成程式碼簽名。
這裡我們對tools.jar做程式碼簽名,命令如下:
jarsigner -storetype jks -keystore zlex.keystore -verbose tools.jar www.zlex.org
控制檯輸出:
輸入金鑰庫的口令短語: 正在更新: META-INF/WWW_ZLEX.SF 正在更新: META-INF/WWW_ZLEX.RSA 正在簽名: org/zlex/security/Security.class 正在簽名: org/zlex/tool/Main$1.class 正在簽名: org/zlex/tool/Main$2.class 正在簽名: org/zlex/tool/Main.class 警告: 簽名者證照將在六個月內過期。
此時,我們可以對簽名後的jar做驗證!
驗證tools.jar,命令如下:
jarsigner -verify -verbose -certs tools.jar
控制檯輸出:
402 Sat Jun 20 16:25:14 CST 2009 META-INF/MANIFEST.MF 532 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.SF 889 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.RSA sm 590 Wed Dec 10 13:03:42 CST 2008 org/zlex/security/Security.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證照將在 09-9-18 下午3:27 到期] sm 705 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$1.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證照將在 09-9-18 下午3:27 到期] sm 779 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$2.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證照將在 09-9-18 下午3:27 到期] sm 12672 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證照將在 09-9-18 下午3:27 到期] s = 已驗證簽名 m = 在清單中列出條目 k = 在金鑰庫中至少找到了一個證照 i = 在身份作用域內至少找到了一個證照 jar 已驗證。 警告: 此 jar 包含簽名者證照將在六個月內過期的條目。
程式碼簽名認證的用途主要是對釋出的軟體做驗證,支援 Sun Java .jar (Java Applet) 檔案(J2SE)和 J2ME MIDlet Suite 檔案。
在中,我們模擬了一個基於RSA非對稱加密網路的安全通訊。現在我們深度瞭解一下現有的安全網路通訊——SSL。
我們需要構建一個由CA機構簽發的有效證照,這裡我們使用上文中生成的自簽名證照zlex.cer
這裡,我們將證照匯入到我們的金鑰庫。
keytool -import -alias www.zlex.org -file d:/zlex.cer -keystore d:/zlex.keystore
其中
-import表示匯入
-alias指定別名,這裡是www.zlex.org
-file指定演算法,這裡是d:/zlex.cer
-keystore指定儲存位置,這裡是d:/zlex.keystore
在這裡我使用的密碼為654321
控制檯輸出:
輸入keystore密碼: 再次輸入新密碼: 所有者:CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 簽發人:CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 序列號:4a1e48df 有效期: Thu May 28 16:18:39 CST 2009 至Wed Aug 26 16:18:39 CST 2009 證照指紋: MD5:19:CA:E6:36:E2:DF:AD:96:31:97:2F:A9:AD:FC:37:6A SHA1:49:88:30:59:29:45:F1:69:CA:97:A9:6D:8A:CF:08:D2:C3:D5:C0:C4 簽名演算法名稱:SHA1withRSA 版本: 3 信任這個認證? [否]: y 認證已新增至keystore中
OK,最複雜的準備工作已經完成。
接下來我們將域名www.zlex.org定位到本機上。開啟C:\Windows\System32\drivers\etc\hosts檔案,將www.zlex.org繫結在本機上。在檔案末尾追加127.0.0.1 www.zlex.org。現在通過位址列訪問http://www.zlex.org,或者通過ping命令,如果能夠定位到本機,域名對映就搞定了。
現在,配置tomcat。先將zlex.keystore拷貝到tomcat的conf目錄下,然後配置server.xml。將如下內容加入配置檔案
<Connector SSLEnabled="true" URIEncoding="UTF-8" clientAuth="false" keystoreFile="conf/zlex.keystore" keystorePass="123456" maxThreads="150" port="443" protocol="HTTP/1.1" scheme="https" secure="true" sslProtocol="TLS" />
注意clientAuth="false"測試階段,置為false,正式使用時建議使用true。現在啟動tomcat,訪問https://www.zlex.org/。
顯然,證照未能通過認證,這個時候你可以選擇安裝證照(上文中的zlex.cer檔案就是證照),作為受信任的根證照頒發機構匯入,再次重啟瀏覽器(IE,其他瀏覽器對於域名www.zlex.org不支援本地方式訪問),訪問https://www.zlex.org/,你會看到位址列中會有個小鎖,就說明安裝成功。所有的瀏覽器聯網操作已經在RSA加密解密系統的保護之下了。但似乎我們感受不到。
這個時候很多人開始懷疑,如果我們要手工做一個這樣的https的訪問是不是需要把瀏覽器的這些個功能都實現呢?不需要!
接著上篇內容,給出如下程式碼實現:
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.crypto.Cipher;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/**
* 證照元件
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public abstract class CertificateCoder extends Coder {
/**
* Java金鑰庫(Java Key Store,JKS)KEY_STORE
*/
public static final String KEY_STORE = "JKS";
public static final String X509 = "X.509";
public static final String SunX509 = "SunX509";
public static final String SSL = "SSL";
/**
* 由KeyStore獲得私鑰
*
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKey(String keyStorePath, String alias,
String password) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, password);
PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
return key;
}
/**
* 由Certificate獲得公鑰
*
* @param certificatePath
* @return
* @throws Exception
*/
private static PublicKey getPublicKey(String certificatePath)
throws Exception {
Certificate certificate = getCertificate(certificatePath);
PublicKey key = certificate.getPublicKey();
return key;
}
/**
* 獲得Certificate
*
* @param certificatePath
* @return
* @throws Exception
*/
private static Certificate getCertificate(String certificatePath)
throws Exception {
CertificateFactory certificateFactory = CertificateFactory
.getInstance(X509);
FileInputStream in = new FileInputStream(certificatePath);
Certificate certificate = certificateFactory.generateCertificate(in);
in.close();
return certificate;
}
/**
* 獲得Certificate
*
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
private static Certificate getCertificate(String keyStorePath,
String alias, String password) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, password);
Certificate certificate = ks.getCertificate(alias);
return certificate;
}
/**
* 獲得KeyStore
*
* @param keyStorePath
* @param password
* @return
* @throws Exception
*/
private static KeyStore getKeyStore(String keyStorePath, String password)
throws Exception {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore ks = KeyStore.getInstance(KEY_STORE);
ks.load(is, password.toCharArray());
is.close();
return ks;
}
/**
* 私鑰加密
*
* @param data
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
String alias, String password) throws Exception {
// 取得私鑰
PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
// 對資料加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私鑰解密
*
* @param data
* @param keyStorePath
* @param alias
* @param password
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
String alias, String password) throws Exception {
// 取得私鑰
PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
// 對資料加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公鑰加密
*
* @param data
* @param certificatePath
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
throws Exception {
// 取得公鑰
PublicKey publicKey = getPublicKey(certificatePath);
// 對資料加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公鑰解密
*
* @param data
* @param certificatePath
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
throws Exception {
// 取得公鑰
PublicKey publicKey = getPublicKey(certificatePath);
// 對資料加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 驗證Certificate
*
* @param certificatePath
* @return
*/
public static boolean verifyCertificate(String certificatePath) {
return verifyCertificate(new Date(), certificatePath);
}
/**
* 驗證Certificate是否過期或無效
*
* @param date
* @param certificatePath
* @return
*/
public static boolean verifyCertificate(Date date, String certificatePath) {
boolean status = true;
try {
// 取得證照
Certificate certificate = getCertificate(certificatePath);
// 驗證證照是否過期或無效
status = verifyCertificate(date, certificate);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 驗證證照是否過期或無效
*
* @param date
* @param certificate
* @return
*/
private static boolean verifyCertificate(Date date, Certificate certificate) {
boolean status = true;
try {
X509Certificate x509Certificate = (X509Certificate) certificate;
x509Certificate.checkValidity(date);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 簽名
*
* @param keyStorePath
* @param alias
* @param password
*
* @return
* @throws Exception
*/
public static String sign(byte[] sign, String keyStorePath, String alias,
String password) throws Exception {
// 獲得證照
X509Certificate x509Certificate = (X509Certificate) getCertificate(
keyStorePath, alias, password);
// 獲取私鑰
KeyStore ks = getKeyStore(keyStorePath, password);
// 取得私鑰
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password
.toCharArray());
// 構建簽名
Signature signature = Signature.getInstance(x509Certificate
.getSigAlgName());
signature.initSign(privateKey);
signature.update(sign);
return encryptBASE64(signature.sign());
}
/**
* 驗證簽名
*
* @param data
* @param sign
* @param certificatePath
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String sign,
String certificatePath) throws Exception {
// 獲得證照
X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
// 獲得公鑰
PublicKey publicKey = x509Certificate.getPublicKey();
// 構建簽名
Signature signature = Signature.getInstance(x509Certificate
.getSigAlgName());
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(decryptBASE64(sign));
}
/**
* 驗證Certificate
*
* @param keyStorePath
* @param alias
* @param password
* @return
*/
public static boolean verifyCertificate(Date date, String keyStorePath,
String alias, String password) {
boolean status = true;
try {
Certificate certificate = getCertificate(keyStorePath, alias,
password);
status = verifyCertificate(date, certificate);
} catch (Exception e) {
status = false;
}
return status;
}
/**
* 驗證Certificate
*
* @param keyStorePath
* @param alias
* @param password
* @return
*/
public static boolean verifyCertificate(String keyStorePath, String alias,
String password) {
return verifyCertificate(new Date(), keyStorePath, alias, password);
}
/**
* 獲得SSLSocektFactory
*
* @param password
* 密碼
* @param keyStorePath
* 金鑰庫路徑
*
* @param trustKeyStorePath
* 信任庫路徑
* @return
* @throws Exception
*/
private static SSLSocketFactory getSSLSocketFactory(String password,
String keyStorePath, String trustKeyStorePath) throws Exception {
// 初始化金鑰庫
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(SunX509);
KeyStore keyStore = getKeyStore(keyStorePath, password);
keyManagerFactory.init(keyStore, password.toCharArray());
// 初始化信任庫
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(SunX509);
KeyStore trustkeyStore = getKeyStore(trustKeyStorePath, password);
trustManagerFactory.init(trustkeyStore);
// 初始化SSL上下文
SSLContext ctx = SSLContext.getInstance(SSL);
ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory
.getTrustManagers(), null);
SSLSocketFactory sf = ctx.getSocketFactory();
return sf;
}
/**
* 為HttpsURLConnection配置SSLSocketFactory
*
* @param conn
* HttpsURLConnection
* @param password
* 密碼
* @param keyStorePath
* 金鑰庫路徑
*
* @param trustKeyStorePath
* 信任庫路徑
* @throws Exception
*/
public static void configSSLSocketFactory(HttpsURLConnection conn,
String password, String keyStorePath, String trustKeyStorePath)
throws Exception {
conn.setSSLSocketFactory(getSSLSocketFactory(password, keyStorePath,
trustKeyStorePath));
}
}
增加了configSSLSocketFactory方法供外界呼叫,該方法為 HttpsURLConnection配置了SSLSocketFactory。當HttpsURLConnection配置了 SSLSocketFactory後,我們就可以通過HttpsURLConnection的getInputStream、 getOutputStream,像往常使用HttpURLConnection做操作了。尤其要說明一點,未配置SSLSocketFactory
前,HttpsURLConnection的getContentLength()獲得值永遠都是-1。
給出相應測試類:
import static org.junit.Assert.*;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class CertificateCoderTest {
private String password = "123456";
private String alias = "www.zlex.org";
private String certificatePath = "d:/zlex.cer";
private String keyStorePath = "d:/zlex.keystore";
private String clientKeyStorePath = "d:/zlex-client.keystore";
private String clientPassword = "654321";
@Test
public void test() throws Exception {
System.err.println("公鑰加密——私鑰解密");
String inputStr = "Ceritifcate";
byte[] data = inputStr.getBytes();
byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
certificatePath);
byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
keyStorePath, alias, password);
String outputStr = new String(decrypt);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
// 驗證資料一致
assertArrayEquals(data, decrypt);
// 驗證證照有效
assertTrue(CertificateCoder.verifyCertificate(certificatePath));
}
@Test
public void testSign() throws Exception {
System.err.println("私鑰加密——公鑰解密");
String inputStr = "sign";
byte[] data = inputStr.getBytes();
byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
keyStorePath, alias, password);
byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
certificatePath);
String outputStr = new String(decodedData);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
assertEquals(inputStr, outputStr);
System.err.println("私鑰簽名——公鑰驗證簽名");
// 產生簽名
String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
password);
System.err.println("簽名:\r" + sign);
// 驗證簽名
boolean status = CertificateCoder.verify(encodedData, sign,
certificatePath);
System.err.println("狀態:\r" + status);
assertTrue(status);
}
@Test
public void testHttps() throws Exception {
URL url = new URL("https://www.zlex.org/examples/");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
CertificateCoder.configSSLSocketFactory(conn, clientPassword,
clientKeyStorePath, clientKeyStorePath);
InputStream is = conn.getInputStream();
int length = conn.getContentLength();
DataInputStream dis = new DataInputStream(is);
byte[] data = new byte[length];
dis.readFully(data);
dis.close();
System.err.println(new String(data));
conn.disconnect();
}
}
注意testHttps方法,幾乎和我們往常做HTTP訪問沒有差別,我們來看控制檯輸出:
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE> <META http-equiv=Content-Type content="text/html"> </HEAD> <BODY> <P> <H3>Apache Tomcat Examples</H3> <P></P> <ul> <li><a href="http://javaeye.shaduwang.com/?snowolf/blog/servlets">Servlets examples</a></li> <li><a href="http://javaeye.shaduwang.com/?snowolf/blog/jsp">JSP Examples</a></li> </ul> </BODY></HTML>
通過瀏覽器直接訪問https://www.zlex.org/examples/你 也會獲得上述內容。也就是說應用甲方作為伺服器構建tomcat服務,乙方可以通過上述方式訪問甲方受保護的SSL應用,並且不需要考慮具體的加密解密問
題。甲乙雙方可以經過相應配置,通過雙方的tomcat配置有效的SSL服務,簡化上述程式碼實現,完全通過證照配置完成SSL雙向認證!
我們使用自簽名證照完成了認證。接下來,我們使用第三方CA簽名機構完成證照籤名。
這裡我們使用thawte提供的測試用21天免費ca證照。
1.要在該網站上註明你的域名,這裡使用www.zlex.org作為測試用域名(請勿使用該域名作為你的域名地址,該域名受法律保護!請使用其他非註冊域名!)。
2.如果域名有效,你會收到郵件要求你訪問https://www.thawte.com/cgi/server/try.exe獲得ca證照。
3.複述金鑰庫的建立。
keytool -genkey -validity 36000 -alias www.zlex.org -keyalg RSA -keystore d:\zlex.keystore
在這裡我使用的密碼為 123456
控制檯輸出:
輸入keystore密碼: 再次輸入新密碼: 您的名字與姓氏是什麼? [Unknown]: www.zlex.org 您的組織單位名稱是什麼? [Unknown]: zlex 您的組織名稱是什麼? [Unknown]: zlex 您所在的城市或區域名稱是什麼? [Unknown]: BJ 您所在的州或省份名稱是什麼? [Unknown]: BJ 該單位的兩字母國家程式碼是什麼 [Unknown]: CN CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 正確嗎? [否]: Y 輸入<tomcat>的主密碼 (如果和 keystore 密碼相同,按回車): 再次輸入新密碼:
4.通過如下命令,從zlex.keystore中匯出CA證照申請。
keytool -certreq -alias www.zlex.org -file d:\zlex.csr -keystore d:\zlex.keystore -v
你會獲得zlex.csr檔案,可以用記事本開啟,內容如下格式:
-----BEGIN NEW CERTIFICATE REQUEST----- MIIBnDCCAQUCAQAwXDELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkJKMQswCQYDVQQHEwJCSjENMAsG A1UEChMEemxleDENMAsGA1UECxMEemxleDEVMBMGA1UEAxMMd3d3LnpsZXgub3JnMIGfMA0GCSqG SIb3DQEBAQUAA4GNADCBiQKBgQCR6DXU9Mp+mCKO7cv9JPsj0n1Ec/GpM09qvhpgX3FNad/ZWSDc vU77YXZSoF9hQp3w1LC+eeKgd2MlVpXTvbVwBNVd2HiQPp37ic6BUUjSaX8LHtCl7l0BIEye9qQ2 j8G0kak7e8ZA0s7nb3Ymq/K8BV7v0MQIdhIc1bifK9ZDewIDAQABoAAwDQYJKoZIhvcNAQEFBQAD gYEAMA1r2fbZPtNx37U9TRwadCH2TZZecwKJS/hskNm6ryPKIAp9APWwAyj8WJHRBz5SpZM4zmYO oMCI8BcnY2A4JP+R7/SwXTdH/xcg7NVghd9A2SCgqMpF7KMfc5dE3iygdiPu+UhY200Dvpjx8gmJ 1UbH3+nqMUyCrZgURFslOUY= -----END NEW CERTIFICATE REQUEST-----
5.將上述檔案內容拷貝到https://www.thawte.com/cgi/server/try.exe中,點選next,獲得迴應內容,這裡是p7b格式。
內容如下:
-----BEGIN PKCS7----- MIIF3AYJKoZIhvcNAQcCoIIFzTCCBckCAQExADALBgkqhkiG9w0BBwGgggWxMIID EDCCAnmgAwIBAgIQA/mx/pKoaB+KGX2hveFU9zANBgkqhkiG9w0BAQUFADCBhzEL MAkGA1UEBhMCWkExIjAgBgNVBAgTGUZPUiBURVNUSU5HIFBVUlBPU0VTIE9OTFkx HTAbBgNVBAoTFFRoYXd0ZSBDZXJ0aWZpY2F0aW9uMRcwFQYDVQQLEw5URVNUIFRF U1QgVEVTVDEcMBoGA1UEAxMTVGhhd3RlIFRlc3QgQ0EgUm9vdDAeFw0wOTA1Mjgw MDIxMzlaFw0wOTA2MTgwMDIxMzlaMFwxCzAJBgNVBAYTAkNOMQswCQYDVQQIEwJC SjELMAkGA1UEBxMCQkoxDTALBgNVBAoTBHpsZXgxDTALBgNVBAsTBHpsZXgxFTAT BgNVBAMTDHd3dy56bGV4Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA keg11PTKfpgiju3L/ST7I9J9RHPxqTNPar4aYF9xTWnf2Vkg3L1O+2F2UqBfYUKd 8NSwvnnioHdjJVaV0721cATVXdh4kD6d+4nOgVFI0ml/Cx7Qpe5dASBMnvakNo/B tJGpO3vGQNLO5292JqvyvAVe79DECHYSHNW4nyvWQ3sCAwEAAaOBpjCBozAMBgNV HRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBABgNVHR8E OTA3MDWgM6Axhi9odHRwOi8vY3JsLnRoYXd0ZS5jb20vVGhhd3RlUHJlbWl1bVNl cnZlckNBLmNybDAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9v Y3NwLnRoYXd0ZS5jb20wDQYJKoZIhvcNAQEFBQADgYEATPuxZbtJJSPmXvfrr1yz xqM06IwTZ6UU0lZRG7I0WufMjNMKdpn8hklUhE17mxAhGSpewLVVeLR7uzBLFkuC X7wMXxhoYdJZtNai72izU6Rd1oknao7diahvRxPK4IuQ7y2oZ511/4T4vgY6iRAj q4q76HhPJrVRL/sduaiu+gYwggKZMIICAqADAgECAgEAMA0GCSqGSIb3DQEBBAUA MIGHMQswCQYDVQQGEwJaQTEiMCAGA1UECBMZRk9SIFRFU1RJTkcgUFVSUE9TRVMg T05MWTEdMBsGA1UEChMUVGhhd3RlIENlcnRpZmljYXRpb24xFzAVBgNVBAsTDlRF U1QgVEVTVCBURVNUMRwwGgYDVQQDExNUaGF3dGUgVGVzdCBDQSBSb290MB4XDTk2 MDgwMTAwMDAwMFoXDTIwMTIzMTIxNTk1OVowgYcxCzAJBgNVBAYTAlpBMSIwIAYD VQQIExlGT1IgVEVTVElORyBQVVJQT1NFUyBPTkxZMR0wGwYDVQQKExRUaGF3dGUg Q2VydGlmaWNhdGlvbjEXMBUGA1UECxMOVEVTVCBURVNUIFRFU1QxHDAaBgNVBAMT E1RoYXd0ZSBUZXN0IENBIFJvb3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB ALV9kG+Os6x/DOhm+tKUQfzVMWGhE95sFmEtkMMTX2Zi4n6i6BvzoReJ5njzt1LF cqu4EUk9Ji20egKKfmqRzmQFLP7+1niSdfJEUE7cKY40QoI99270PTrLjJeaMcCl +AYl+kD+RL5BtuKKU3PurYcsCsre6aTvjMcqpTJOGeSPAgMBAAGjEzARMA8GA1Ud EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAgozj7BkD9O8si2V0v+EZ/t7E fz/LC8y6mD7IBUziHy5/53ymGAGLtyhXHvX+UIE6UWbHro3IqVkrmY5uC93Z2Wew A/6edK3KFUcUikrLeewM7gmqsiASEKx2mKRKlu12jXyNS5tXrPWRDvUKtFC1uL9a 12rFAQS2BkIk7aU+ghYxAA== -----END PKCS7-----
將其儲存為zlex.p7b
6.將由CA簽發的證照匯入金鑰庫。
keytool -import -trustcacerts -alias www.zlex.org -file d:\zlex.p7b -keystore d:\zlex.keystore -v
在這裡我使用的密碼為 123456
控制檯輸出:
輸入keystore密碼: 回覆中的最高階認證: 所有者:CN=Thawte Test CA Root, OU=TEST TEST TEST, O=Thawte Certification, ST=FOR TESTING PURPOSES ONLY, C=ZA 簽發人:CN=Thawte Test CA Root, OU=TEST TEST TEST, O=Thawte Certification, ST=FOR TESTING PURPOSES ONLY, C=ZA 序列號:0 有效期: Thu Aug 01 08:00:00 CST 1996 至Fri Jan 01 05:59:59 CST 2021 證照指紋: MD5:5E:E0:0E:1D:17:B7:CA:A5:7D:36:D6:02:DF:4D:26:A4 SHA1:39:C6:9D:27:AF:DC:EB:47:D6:33:36:6A:B2:05:F1:47:A9:B4:DA:EA 簽名演算法名稱:MD5withRSA 版本: 3 擴充套件: #1: ObjectId: 2.5.29.19 Criticality=true BasicConstraints:[ CA:true PathLen:2147483647 ] ... 是不可信的。 還是要安裝回復? [否]: Y 認證回覆已安裝在 keystore中 [正在儲存 d:\zlex.keystore]
7.域名定位
將域名www.zlex.org定位到本機上。開啟C:\Windows\System32\drivers\etc\hosts檔案,將 www.zlex.org繫結在本機上。在檔案末尾追加127.0.0.1 www.zlex.org。現在通過位址列訪問http://www.zlex.org,或者通過ping命令,如果能夠定位到本機,域名對映就搞定 了。
8.配置server.xml
<Connector keystoreFile="conf/zlex.keystore" keystorePass="123456" truststoreFile="conf/zlex.keystore" truststorePass="123456" SSLEnabled="true" URIEncoding="UTF-8" clientAuth="false" maxThreads="150" port="443" protocol="HTTP/1.1" scheme="https" secure="true" sslProtocol="TLS" />
將檔案zlex.keystore拷貝到tomcat的conf目錄下,重新啟動tomcat。訪問https://www.zlex.org/,我們發現聯網有些遲鈍。大約5秒鐘後,網頁正常顯示,同時有如下圖所示:
瀏覽器驗證了該CA機構的有效性。
開啟證照,如下圖所示:
調整測試類:
import static org.junit.Assert.*;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import org.junit.Test;
/**
*
* @author 樑棟
* @version 1.0
* @since 1.0
*/
public class CertificateCoderTest {
private String password = "123456";
private String alias = "www.zlex.org";
private String certificatePath = "d:/zlex.cer";
private String keyStorePath = "d:/zlex.keystore";
@Test
public void test() throws Exception {
System.err.println("公鑰加密——私鑰解密");
String inputStr = "Ceritifcate";
byte[] data = inputStr.getBytes();
byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
certificatePath);
byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
keyStorePath, alias, password);
String outputStr = new String(decrypt);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
// 驗證資料一致
assertArrayEquals(data, decrypt);
// 驗證證照有效
assertTrue(CertificateCoder.verifyCertificate(certificatePath));
}
@Test
public void testSign() throws Exception {
System.err.println("私鑰加密——公鑰解密");
String inputStr = "sign";
byte[] data = inputStr.getBytes();
byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
keyStorePath, alias, password);
byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
certificatePath);
String outputStr = new String(decodedData);
System.err.println("加密前: " + inputStr + "\n\r" + "解密後: " + outputStr);
assertEquals(inputStr, outputStr);
System.err.println("私鑰簽名——公鑰驗證簽名");
// 產生簽名
String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
password);
System.err.println("簽名:\r" + sign);
// 驗證簽名
boolean status = CertificateCoder.verify(encodedData, sign,
certificatePath);
System.err.println("狀態:\r" + status);
assertTrue(status);
}
@Test
public void testHttps() throws Exception {
URL url = new URL("https://www.zlex.org/examples/");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
CertificateCoder.configSSLSocketFactory(conn, password, keyStorePath,
keyStorePath);
InputStream is = conn.getInputStream();
int length = conn.getContentLength();
DataInputStream dis = new DataInputStream(is);
byte[] data = new byte[length];
dis.readFully(data);
dis.close();
conn.disconnect();
System.err.println(new String(data));
}
}
再次執行,驗證通過!
由此,我們了基於SSL協議的認證過程。測試類的testHttps方法模擬了一次瀏覽器的HTTPS訪問。
相關文章
- HTTPS-各種加密方式HTTP加密
- 加密,各種加密,耙梳加密演算法(Encryption)種類以及開發場景中的運用(Python3.10)加密演算法Python
- C#各種加密方法,字典排序C#加密排序
- Python幾種加密演算法Python加密演算法
- Java各種規則引擎Java
- Apache Commons Codec:各種加密瞭解一下Apache加密
- Java中各種Log的使用Java
- Java 各種鎖的小結Java
- 掌握Java各種日誌框架Java框架
- 求逆序對的各種演算法演算法
- 微信小程式及各種平臺對接常用可逆加密演算法aes256微信小程式加密演算法
- Java中Blowfish加密演算法Java加密演算法
- Java 常用加密解密演算法Java加密解密演算法
- Java 常用的 4 種加密方式Java加密
- Java獲取Spring的各種物件JavaSpring物件
- Java中的各種關鍵字Java
- Java安全之安全加密演算法Java加密演算法
- java各種集合的執行緒安全Java執行緒
- 帶你掌握Java各種日誌框架Java框架
- 深入理解JVM(③)各種垃圾收集演算法JVM演算法
- Java中Blowfish加密演算法實現Java加密演算法
- QQ TEA加密演算法 JAVA實現加密演算法Java
- java md5加密的幾種方式Java加密
- 8分鐘搞懂Java中的各種鎖Java
- java中的Static、final、Static final各種用法Java
- Java 的各種內部類、Lambda表示式Java
- mysql-connector-java各種版本下載地址MySqlJava
- 單例模式的各種實現方式(Java)單例模式Java
- 各種加速
- 圖論(三)--各種基礎圖演算法總結圖論演算法
- 【資料結構】 各種排序演算法的實現資料結構排序演算法
- java日期時間各種變換及處理Java
- java高版本下各種JNDI Bypass方法復現Java
- 各種各樣的映象加速
- AES線上加密解密-附AES128,192,256,CBC,CFB,ECB,OFB,PCBC各種加密解密原始碼加密解密原始碼
- Java實現常用加密演算法-SM4Java加密演算法
- Java實際工作裡用到的幾種加密方式Java加密
- Java和SpringBoot安全加密方式選擇哪種? - foojayJavaSpring Boot加密
- JAVA各種OOM程式碼樣例及解決方法JavaOOM