如基本的單向加密算法:
- 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類見 Java加密技術(一)
- 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類見 Java加密技術(一)
- 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類見 Java加密技術(一)
- 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
簡要總結一下,使用公鑰加密、私鑰解密,完成了乙方到甲方的一次數據傳遞,通過私鑰加密、公鑰解密,同時通過私鑰簽名、公鑰驗證簽名,完成了一次甲方到乙方的數據傳遞與驗證,兩次數據傳遞完成一整套的數據交互!
類似數字簽名,數字信封是這樣描述的:
數字信封
數字信封用加密技術來保證只有特定的收信人才能閱讀信的內容。
流程:
信息發送方采用對稱密鑰來加密信息,然后再用接收方的公鑰來加密此對稱密鑰(這部分稱為數字信封),再將它和信息一起發送給接收方;接收方先用相應的私鑰打開數字信封,得到對稱密鑰,然后使用對稱密鑰再解開信息。
接下來我們介紹DSA數字簽名,非對稱加密的另一種實現。
DSA
DSA-Digital Signature Algorithm 是Schnorr和ElGamal簽名算法的變種,被美國NIST作為DSS(DigitalSignature Standard)。簡單的說,這是一種更高級的驗證方式,用作數字簽名。不單單只有公鑰、私鑰,還有數字簽名。私鑰加密生成數字簽名,公鑰驗證數據及簽名。如果數據和簽名不匹配則認為驗證失敗!數字簽名的作用就是校驗數據在傳輸過程中不被修改。數字簽名,是單向加密的升級!
通過java代碼實現如下:Coder類見 Java加密技術(一)
- 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類見 Java加密技術(一)
- 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加密技術(四),預先了解RSA加密算法。

在構建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類見 Java加密技術(一)
- 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 文件。

我們需要構建一個由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/,你會看到地址欄中會有個小鎖

這個時候很多人開始懷疑,如果我們要手工做一個這樣的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="servlets">Servlets examples</a></li>
- <li><a href="jsp">JSP Examples</a></li>
- </ul>
- </BODY></HTML>
通過瀏覽器直接訪問 https://www.zlex.org/examples/你也會獲得上述內容。也就是說應用甲方作為服務器構建tomcat服務,乙方可以通過上述方式訪問甲方受保護的SSL應用,並且不需要考慮具體的加密解密問題。甲乙雙方可以經過相應配置,通過雙方的tomcat配置有效的SSL服務,簡化上述代碼實現,完全通過證書配置完成SSL雙向認證!

這里我們使用 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
- -----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-----
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訪問。

服務器端下發證書,客戶端接受證書。證書帶有公鑰信息,用於驗證服務器端、對數據加密/解密,起到OSI五類服務的認證(鑒別)服務和保密性服務。
這只是單向認證,為什么?因為客戶端可以驗證服務器端,但服務器端不能驗證客戶端!
如果客戶端也有這樣一個證書,服務器端也就能夠驗證客戶端,這就是雙向認證了!
換言之,當你用銀行的“U盾”之類的U盤與銀行賬戶交互時,在你驗證銀行服務器的同時,服務器也在驗證你!這種雙重驗證,正是網銀系統的安全關鍵!
單向認證見 Java加密技術(十)
雙向認證需要一個CA機構簽發這樣的客戶端、服務器端證書,首先需要CA機構構建一個根證書。keytool可以構建證書但不能構建我們需要的根證書,openssl則可以!
根證書簽發客戶端證書,根私鑰簽發服務器端證書!
我們直接使用linux下的openssl來完成CA,需要修改openssl.cnf文件,在ubuntu下的 /etc/ssl/目錄下,找到[ CA_default ]修改dir變量。
原文
#dir = ./demoCA # Where everything is kept
我們把c盤的ca目錄作為CA認證的根目錄,文件修改后如下所示:
dir = $ENV::HOME/ca # Where everything is kept
我們需要在用戶目錄下構建一個ca目錄,以及子目錄,如下所下:
ca
|__certs
|__newcerts
|__private
|__crl
執行如下操作:
- #!/bin/bash
- ca_path=ca
- certs_path=$ca_path/certs
- newcerts_path=$ca_path/newcerts
- private_path=$ca_path/private
- crl_path=$ca_path/crl
- echo 移除CA根目錄
- rm -rf ca
- echo 構建CA根目錄
- mkdir ca
- echo 構建子目錄
- mkdir certs
- mkdir newcerts
- mkdir private
- mkdir crl
- #構建文件
- touch $ca_path/index.txt
- echo 01 > $ca_path/serial
- echo
- #構建隨機數
- openssl rand -out $private_path/.rand 1000
- echo
- echo 生成根證書私鑰
- openssl genrsa -des3 -out $private_path/ca.pem 2048
- echo
- echo 查看私鑰信息
- openssl rsa -noout -text -in $private_path/ca.pem
- echo
- echo 生成根證書請求
- openssl req -new -key $private_path/ca.pem -out $certs_path/ca.csr -subj "/C=CN/ST=BJ/L=BJ/O=zlex/OU=zlex/CN=ca.zlex.org"
- echo
- echo 查看證書請求
- openssl req -in $certs_path/ca.csr -text -noout
- echo
- echo 簽發根證書
- openssl ca -create_serial -out $certs_path/ca.crt -days 3650 -batch -keyfile $private_path/ca.pem -selfsign -extensions v3_ca -infiles $certs_path/ca.csr
- #openssl x509 -req -sha1 -extensions v3_ca -signkey $private_path/ca.pem -in $certs_path/ca.csr -out $certs_path/ca.crt -days 3650
- echo
- echo 查看證書詳情
- openssl x509 -in $certs_path/ca.crt -text -noout
- echo
- echo 證書轉換——根證書
- openssl pkcs12 -export -clcerts -in $certs_path/ca.crt -inkey $private_path/ca.pem -out $certs_path/ca.p12
- echo
- echo 生成服務器端私鑰
- openssl genrsa -des3 -out $private_path/server.pem 1024
- echo
- echo 查看私鑰信息
- openssl rsa -noout -text -in $private_path/server.pem
- echo
- echo 生成服務器端證書請求
- openssl req -new -key $private_path/server.pem -out $certs_path/server.csr -subj "/C=CN/ST=BJ/L=BJ/O=zlex/OU=zlex/CN=www.zlex.org"
- echo
- echo 查看證書請求
- openssl req -in $certs_path/server.csr -text -noout
- echo
- echo 簽發服務器端證書
- openssl ca -in $certs_path/server.csr -out $certs_path/server.crt -cert $certs_path/ca.crt -keyfile $private_path/ca.pem -days 365 -notext
- #openssl x509 -req -days 365 -sha1 -extensions v3_req -CA $certs_path/ca.crt -CAkey $private_path/ca.pem -CAserial $ca_path/serial -CAcreateserial -in $certs_path/server.csr -out $certs_path/server.crt
- echo
- echo 查看證書詳情
- openssl x509 -in $certs_path/server.crt -text -noout
- echo
- echo 證書轉換——服務器端
- openssl pkcs12 -export -clcerts -in $certs_path/server.crt -inkey $private_path/server.pem -out $certs_path/server.p12
- echo
- echo 生成客戶端私鑰
- openssl genrsa -des3 -out $private_path/client.pem 1024
- echo
- echo 生成客戶端私鑰
- openssl genrsa -des3 -out $private_path/client.pem 1024
- echo
- echo 查看私鑰信息
- openssl rsa -noout -text -in $private_path/client.pem
- echo
- echo 生成客戶端證書請求
- openssl req -new -key $private_path/client.pem -out $certs_path/client.csr -subj "/C=CN/ST=BJ/L=BJ/O=zlex/OU=zlex/CN=zlex"
- echo
- echo 查看證書請求
- openssl req -in $certs_path/client.csr -text -noout
- echo
- echo 簽發客戶端證書
- openssl ca -in $certs_path/client.csr -out $certs_path/client.crt -cert $certs_path/ca.crt -keyfile $private_path/ca.pem -days 365 -notext
- #openssl x509 -req -days 365 -sha1 -extensions dir_sect -CA $certs_path/ca.crt -CAkey $private_path/ca.pem -CAserial $ca_path/serial -in $certs_path/client.csr -out $certs_path/client.crt
- echo
- echo 查看證書詳情
- openssl x509 -in $certs_path/client.crt -text -noout
- echo
- echo 證書轉換——客戶端
- openssl pkcs12 -export -clcerts -in $certs_path/client.crt -inkey $private_path/client.pem -out $certs_path/client.p12
- echo
- echo 生成證書鏈PKCS#7
- openssl crl2pkcs7 -nocrl -certfile $certs_path/server.crt -certfile $certs_path/ca.crt -certfile $certs_path/client.crt -out
- form PEM -out $certs_path/zlex.p7b
- echo
- echo 查看證書鏈
- openssl pkcs7 -in $certs_path/zlex.p7b -print_certs -noout
這個腳本就是最重要的結晶了!

執行結果,如下:
生成根證書私鑰
Generating RSA private key, 2048 bit long modulus
..................................+++
.............................................................+++
e is 65537 (0x10001)
Enter pass phrase for ca/private/ca.pem:
Verifying - Enter pass phrase for ca/private/ca.pem:
查看私鑰信息
Enter pass phrase for ca/private/ca.pem:
Private-Key: (2048 bit)
modulus:
00:d4:18:ab:5f:ad:b7:d0:09:d4:68:63:b5:db:8a:
d1:a1:db:7e:f3:bb:bb:c2:be:a7:35:17:9e:bb:20:
d3:1f:ed:63:e7:7d:29:6d:d2:7c:60:06:47:53:a6:
23:b0:bd:94:65:3f:57:1e:00:51:f3:a1:9a:1b:83:
14:a5:53:72:86:21:a2:57:22:2f:6a:a9:46:50:8c:
f0:51:cf:e6:83:5b:23:dc:f9:ea:6c:2e:51:20:61:
d1:84:9f:28:e8:01:89:b5:cb:55:68:4a:11:b1:06:
56:31:21:16:c8:ac:2b:68:31:e1:de:12:d3:21:12:
83:36:4c:ca:a8:b5:7e:b9:a7:63:4e:8e:e0:79:0f:
0e:91:36:28:7c:dd:9a:e2:e0:98:8b:91:7f:09:7d:
20:bb:37:f2:ab:aa:f0:ef:ae:68:7e:db:ca:db:33:
84:48:5a:e3:ff:0b:08:0e:96:6d:01:c8:12:35:ec:
9f:31:55:7f:53:7e:bd:fb:c4:16:b8:1f:17:29:42:
0f:0e:04:57:14:18:fd:e5:d6:3f:40:04:cd:85:dd:
d3:eb:2f:9a:bf:3c:8a:60:01:88:2f:43:0a:8b:bb:
50:13:f8:cc:68:f9:10:eb:f9:7e:63:de:62:55:32:
a8:fe:ce:51:67:79:c9:a6:3b:a3:c9:d7:81:7c:48:
f3:d1
publicExponent: 65537 (0x10001)
privateExponent:
00:b0:8a:e4:43:1c:df:6e:bc:6f:e0:80:76:c4:8a:
75:5a:0b:d1:4d:61:cb:b5:1b:6b:24:c7:47:69:ad:
b5:ee:d2:73:a1:21:4e:95:ca:69:9a:a8:3f:40:c2:
7e:dc:c3:c0:bc:d2:0f:5a:ba:9b:7c:76:dc:46:e0:
42:14:27:34:a1:af:67:68:ad:dc:d8:24:94:91:c1:
ee:db:ba:78:be:87:e3:7f:31:4b:4e:c6:f2:e2:48:
69:d4:c1:82:94:33:8b:84:15:ff:3e:72:c0:ed:20:
40:28:5e:c9:8f:39:b8:5b:df:81:89:8f:13:cc:68:
93:6d:64:58:20:3c:0a:82:ce:ec:2f:9b:b2:9d:ca:
e7:19:22:98:29:6e:7c:4d:85:45:17:50:8f:5d:b1:
45:be:42:af:1a:7f:84:26:b4:5d:a6:22:8a:07:e8:
b3:b4:5a:59:45:20:b5:ef:1c:81:25:9e:73:74:04:
d6:57:30:2c:a7:25:50:7c:d7:87:73:b3:d0:c2:8b:
c9:02:8e:15:9e:40:41:a5:7a:a9:d8:85:fb:5b:9a:
59:83:bc:80:fa:74:e6:88:14:70:33:61:d7:f5:51:
47:8f:60:51:cb:c4:97:66:65:94:f0:ed:58:ca:80:
c1:89:e0:55:68:4c:69:21:0f:08:27:e0:87:11:df:
b7:bd
prime1:
00:f7:ff:b0:40:de:62:b6:a2:e5:d0:f5:fa:28:3d:
d3:30:30:89:8f:d1:ae:df:e9:09:ee:a0:b0:a5:a5:
a4:e5:93:97:7e:e6:0b:09:70:4c:62:99:5e:7d:45:
2f:fd:21:5a:31:d9:26:7f:39:5f:6e:eb:36:02:4e:
18:99:1b:38:13:99:f5:f3:a3:6b:93:83:67:fb:58:
67:d4:07:eb:e3:2f:31:b3:97:8f:f6:86:1f:15:08:
1a:4b:b5:a8:06:97:72:9c:74:ab:53:1f:ac:ee:fb:
59:03:39:a6:5c:a8:77:43:c0:2c:14:60:0e:71:3d:
70:b6:59:09:40:86:04:54:bf
prime2:
00:da:f0:73:2c:bd:52:a5:0d:9a:40:c4:34:fc:c9:
cf:0f:67:8a:02:01:ca:e7:b8:4e:57:da:0c:0d:b2:
f9:f3:f2:e4:4c:82:61:aa:04:2c:88:39:18:bd:86:
d6:dc:d0:e9:6c:c6:6f:d9:87:59:57:9b:1a:6b:c9:
56:c1:4d:33:ce:3e:15:b9:42:4e:e0:f8:14:91:c3:
fe:63:b2:13:29:99:a7:a6:13:cc:f8:9c:38:29:28:
dd:ed:d1:a3:7c:05:2c:26:a0:84:c6:09:9e:42:ef:
7b:5e:50:c7:57:e3:bc:02:93:0b:74:a1:b5:0b:6e:
23:18:8b:82:6f:ac:3c:0b:6f
exponent1:
7c:a1:23:4b:46:37:27:7f:6f:ac:f6:a0:93:ae:96:
3e:46:76:2b:2f:7e:09:8a:8c:72:3e:90:e7:7d:fa:
03:61:8b:a5:bb:27:da:c3:73:af:ad:51:9d:f4:b2:
2c:2c:a1:ae:21:69:c6:4f:e7:d4:cf:21:a2:40:ea:
fd:ae:7f:1c:e2:a7:86:9c:1e:c8:d0:25:e6:5b:44:
3a:7b:0c:a1:6c:2b:37:0c:b8:cd:74:13:94:b7:30:
b7:d1:7f:b2:68:53:b1:aa:b4:1a:9e:f5:82:58:10:
20:9d:cd:2c:0d:81:7a:2b:ce:3b:23:16:be:f3:d8:
7b:da:fc:da:4f:3f:47:f3
exponent2:
66:c9:5c:49:34:d9:08:04:4a:d6:fd:46:a3:27:5b:
be:af:ad:6b:23:cc:4e:dd:88:6a:56:44:32:6a:44:
4e:f3:49:9b:61:da:d8:26:fd:81:36:cd:16:ad:a7:
52:24:02:72:be:f6:e3:f9:57:48:79:d8:fd:a1:98:
c9:47:a5:7a:be:4b:14:9e:bc:c9:81:ae:a6:80:8d:
7d:e0:ac:7e:6b:54:f9:f3:71:d7:86:00:17:d2:c7:
de:4e:fd:a1:cc:0b:de:56:9d:ff:1b:a4:e1:67:ed:
53:6a:39:2c:5a:0e:7a:66:ee:89:e3:21:4c:2c:78:
ed:9d:11:af:bb:fc:b4:a1
coefficient:
00:b1:23:a8:cc:b1:5e:2e:38:09:0c:b5:df:2c:c6:
15:e8:08:48:45:b9:9d:ec:6f:27:45:5b:a7:bc:b6:
b1:ec:a5:39:b4:40:8e:bc:40:1f:b9:4d:14:2e:18:
fb:87:1e:20:91:34:58:e3:ac:c3:4a:dc:a8:2a:97:
ce:aa:8d:62:0e:91:af:1f:53:d6:37:55:1d:14:9c:
01:98:34:77:28:d7:cf:f7:a0:2d:73:40:48:5e:ed:
ae:9b:15:42:06:e6:a3:5a:2b:b0:bc:ee:7a:bb:52:
e6:28:19:c2:e5:de:6f:4d:fa:fb:69:81:7b:13:2b:
01:87:bf:bf:66:8f:24:a1:8f
生成根證書請求
Enter pass phrase for ca/private/ca.pem:
查看證書請求
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=CN, ST=BJ, L=BJ, O=zlex, OU=zlex, CN=ca.zlex.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (2048 bit)
Modulus (2048 bit):
00:d4:18:ab:5f:ad:b7:d0:09:d4:68:63:b5:db:8a:
d1:a1:db:7e:f3:bb:bb:c2:be:a7:35:17:9e:bb:20:
d3:1f:ed:63:e7:7d:29:6d:d2:7c:60:06:47:53:a6:
23:b0:bd:94:65:3f:57:1e:00:51:f3:a1:9a:1b:83:
14:a5:53:72:86:21:a2:57:22:2f:6a:a9:46:50:8c:
f0:51:cf:e6:83:5b:23:dc:f9:ea:6c:2e:51:20:61:
d1:84:9f:28:e8:01:89:b5:cb:55:68:4a:11:b1:06:
56:31:21:16:c8:ac:2b:68:31:e1:de:12:d3:21:12:
83:36:4c:ca:a8:b5:7e:b9:a7:63:4e:8e:e0:79:0f:
0e:91:36:28:7c:dd:9a:e2:e0:98:8b:91:7f:09:7d:
20:bb:37:f2:ab:aa:f0:ef:ae:68:7e:db:ca:db:33:
84:48:5a:e3:ff:0b:08:0e:96:6d:01:c8:12:35:ec:
9f:31:55:7f:53:7e:bd:fb:c4:16:b8:1f:17:29:42:
0f:0e:04:57:14:18:fd:e5:d6:3f:40:04:cd:85:dd:
d3:eb:2f:9a:bf:3c:8a:60:01:88:2f:43:0a:8b:bb:
50:13:f8:cc:68:f9:10:eb:f9:7e:63:de:62:55:32:
a8:fe:ce:51:67:79:c9:a6:3b:a3:c9:d7:81:7c:48:
f3:d1
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha1WithRSAEncryption
af:91:f8:56:6f:db:de:cb:df:2c:87:93:99:ac:4b:51:12:a2:
c1:2b:09:d2:58:7c:e1:07:5c:53:9f:f3:e1:b6:3a:e9:08:e7:
65:89:3b:0a:01:83:24:a3:b5:74:65:50:a5:77:bc:30:1b:7d:
80:8b:4c:92:ec:81:91:6e:b7:8f:05:e7:1d:b2:89:84:18:8c:
5f:66:be:19:15:ba:ba:c3:f7:0d:c3:7d:7a:11:47:17:e5:cf:
87:69:2e:15:91:d7:db:9d:8e:c9:0f:81:71:fa:00:93:33:2c:
99:e1:be:76:06:f1:8a:e6:8b:1d:9b:07:70:f0:f2:44:91:ed:
a2:ed:28:91:5f:6a:8a:f3:cf:ab:0d:b3:05:30:72:19:86:ae:
c6:2d:a4:22:9f:21:cf:55:0c:b7:79:44:01:6e:36:43:a5:dc:
a0:ea:46:2a:b0:9d:b3:53:4a:57:fc:72:1b:4c:52:cc:a3:39:
d6:49:d6:f4:8c:e2:bf:5a:a6:6e:69:7c:f2:bc:7b:02:b7:f5:
91:7f:94:2b:8c:58:0f:aa:a3:72:93:46:fe:08:29:08:51:eb:
c6:a0:4e:7a:e1:bd:c6:0b:11:9d:63:96:af:22:ee:7b:79:84:
cd:e7:f0:23:17:e7:9f:a2:73:c5:15:e1:f5:a1:af:8d:58:f5:
e0:eb:57:fd
簽發根證書
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for ca/private/ca.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 1 (0x1)
Validity
Not Before: Jul 24 08:15:59 2012 GMT
Not After : Jul 22 08:15:59 2022 GMT
Subject:
countryName = CN
stateOrProvinceName = BJ
organizationName = zlex
organizationalUnitName = zlex
commonName = ca.zlex.org
X509v3 extensions:
X509v3 Subject Key Identifier:
7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
X509v3 Authority Key Identifier:
keyid:7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
DirName:/C=CN/ST=BJ/O=zlex/OU=zlex/CN=ca.zlex.org
serial:01
X509v3 Basic Constraints:
CA:TRUE
Certificate is to be certified until Jul 22 08:15:59 2022 GMT (3650 days)
Write out database with 1 new entries
Data Base Updated
查看證書詳情
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=CN, ST=BJ, O=zlex, OU=zlex, CN=ca.zlex.org
Validity
Not Before: Jul 24 08:15:59 2012 GMT
Not After : Jul 22 08:15:59 2022 GMT
Subject: C=CN, ST=BJ, O=zlex, OU=zlex, CN=ca.zlex.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (2048 bit)
Modulus (2048 bit):
00:d4:18:ab:5f:ad:b7:d0:09:d4:68:63:b5:db:8a:
d1:a1:db:7e:f3:bb:bb:c2:be:a7:35:17:9e:bb:20:
d3:1f:ed:63:e7:7d:29:6d:d2:7c:60:06:47:53:a6:
23:b0:bd:94:65:3f:57:1e:00:51:f3:a1:9a:1b:83:
14:a5:53:72:86:21:a2:57:22:2f:6a:a9:46:50:8c:
f0:51:cf:e6:83:5b:23:dc:f9:ea:6c:2e:51:20:61:
d1:84:9f:28:e8:01:89:b5:cb:55:68:4a:11:b1:06:
56:31:21:16:c8:ac:2b:68:31:e1:de:12:d3:21:12:
83:36:4c:ca:a8:b5:7e:b9:a7:63:4e:8e:e0:79:0f:
0e:91:36:28:7c:dd:9a:e2:e0:98:8b:91:7f:09:7d:
20:bb:37:f2:ab:aa:f0:ef:ae:68:7e:db:ca:db:33:
84:48:5a:e3:ff:0b:08:0e:96:6d:01:c8:12:35:ec:
9f:31:55:7f:53:7e:bd:fb:c4:16:b8:1f:17:29:42:
0f:0e:04:57:14:18:fd:e5:d6:3f:40:04:cd:85:dd:
d3:eb:2f:9a:bf:3c:8a:60:01:88:2f:43:0a:8b:bb:
50:13:f8:cc:68:f9:10:eb:f9:7e:63:de:62:55:32:
a8:fe:ce:51:67:79:c9:a6:3b:a3:c9:d7:81:7c:48:
f3:d1
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
X509v3 Authority Key Identifier:
keyid:7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
DirName:/C=CN/ST=BJ/O=zlex/OU=zlex/CN=ca.zlex.org
serial:01
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
8a:99:b8:17:fc:64:7b:88:9c:1b:91:23:60:f4:5c:51:16:9a:
9f:42:b4:d3:a5:bb:79:ca:78:e3:fc:a7:af:66:da:ec:5a:8c:
81:c1:aa:04:32:a9:59:e0:d6:6a:f2:37:38:97:70:a5:27:5d:
14:73:2e:2d:73:78:1d:37:2c:04:f7:c3:99:9d:be:0c:dd:2a:
27:2c:0f:6e:95:96:01:c7:4c:99:f7:49:69:f9:ba:cb:62:b8:
c6:43:6c:5b:b5:cd:25:42:a7:fb:81:27:bc:d8:e4:95:26:7d:
da:50:f8:b8:be:0a:3d:54:35:d0:9d:22:e7:f0:f0:4c:7d:b4:
57:2e:98:91:1a:1d:49:e5:8e:48:f6:2b:54:7e:04:fc:1c:e3:
52:f7:04:f6:9b:bb:84:25:31:f7:31:6e:7f:fa:4c:e4:15:a2:
86:0a:1a:56:8c:ad:07:49:fb:bc:28:27:a3:95:ba:eb:b3:28:
db:11:78:ef:84:fc:3c:16:df:58:39:2e:14:8d:89:fe:7a:d2:
24:eb:a7:66:11:8c:88:55:40:e1:c3:3b:95:b2:bc:af:36:0e:
92:a8:cd:62:d5:57:9c:11:1b:f6:a1:36:5f:25:6c:16:c5:e2:
68:19:e7:12:3d:4b:07:24:81:e6:71:f9:59:c5:f9:1c:62:6d:
b3:24:b9:8a
證書轉換——根證書
Enter pass phrase for ca/private/ca.pem:
Enter Export Password:
Verifying - Enter Export Password:
生成服務器端私鑰
Generating RSA private key, 1024 bit long modulus
......................................................++++++
................++++++
e is 65537 (0x10001)
Enter pass phrase for ca/private/server.pem:
Verifying - Enter pass phrase for ca/private/server.pem:
查看私鑰信息
Enter pass phrase for ca/private/server.pem:
Private-Key: (1024 bit)
modulus:
00:d8:f9:bd:0a:a8:d3:97:98:b2:22:af:29:a9:31:
76:50:52:77:c8:3b:7c:91:75:db:b3:63:88:cc:00:
be:1a:6c:e6:80:23:90:37:5f:1a:d3:80:f2:7f:b5:
77:01:ec:85:3e:4e:c0:af:0d:77:c0:a5:8b:bc:c3:
fe:70:91:66:17:a4:ec:23:08:5b:e3:df:a3:40:2f:
e6:83:bd:3f:d0:62:9c:c0:36:ad:e7:cb:13:e8:34:
d7:6a:66:57:f5:bb:94:2f:7c:d5:27:7b:ee:e6:4f:
fc:ff:c1:a4:01:96:d6:a0:b8:46:1d:93:02:a6:c5:
00:bd:d9:e9:4e:2d:87:d5:95
publicExponent: 65537 (0x10001)
privateExponent:
4d:da:15:fd:6c:24:37:c1:bf:30:f8:be:af:09:a3:
55:20:b1:ff:f3:70:37:d5:1d:16:99:c1:2c:c9:9b:
6c:69:e4:ae:d7:93:d8:7a:54:6a:cd:5a:b5:7e:0c:
0c:71:ac:41:76:0a:67:05:23:11:c9:94:81:0f:a6:
0d:07:ee:a4:26:0e:20:ff:36:6c:f7:2d:fa:8e:39:
85:f8:b8:1a:e0:be:26:f8:24:3c:d4:d0:a0:89:9c:
48:15:d9:28:de:51:dd:14:3f:ca:c9:63:ed:5d:e4:
50:b0:06:5e:1b:f8:99:b4:49:f6:d6:cb:60:8a:7b:
fa:f8:6e:86:44:55:e5:45
prime1:
00:ef:cc:38:ab:e6:98:71:09:32:5c:69:b3:e0:59:
9d:d7:7a:f9:e3:b9:cd:a8:84:74:1a:91:2a:db:2c:
96:40:5a:28:0b:99:6c:da:fa:ca:83:54:e0:59:06:
84:df:55:9a:04:9c:1c:6b:54:52:d5:31:d7:f9:0e:
9a:13:b0:ed:03
prime2:
00:e7:a2:c3:03:55:d7:54:7c:3a:38:40:f1:ac:9a:
e8:dd:3a:5c:24:a6:78:34:c4:ce:24:c8:31:de:5a:
0e:df:09:df:7c:ad:36:14:e0:be:6d:2c:58:89:c6:
7e:ec:51:82:68:81:91:ed:b5:04:ff:c0:61:8e:aa:
5b:ee:6b:f3:87
exponent1:
2a:22:0c:d7:0f:56:3b:8e:2d:1e:15:a8:78:43:e6:
ba:e4:ad:a1:78:95:0d:05:f0:cc:76:33:3c:7d:52:
0d:0e:8a:38:b7:85:6b:d8:62:da:be:80:08:c4:5f:
76:4a:39:1c:94:3d:5e:12:5b:d7:7f:c1:7d:ce:35:
fe:3d:b8:f7
exponent2:
00:94:0b:ec:36:52:84:19:04:79:35:81:14:b5:ec:
20:8f:5d:00:8d:90:34:5e:0d:b7:6f:bc:e0:5a:ac:
16:bb:29:15:45:1b:73:e8:6e:28:67:a0:a3:4a:13:
ab:05:a1:a7:06:e2:61:81:9b:64:01:8e:55:0c:19:
08:3e:df:92:3b
coefficient:
00:8e:4e:ee:04:55:cc:4f:0f:c0:02:a4:9d:08:a8:
4b:ec:72:7c:86:27:a9:0a:5e:1c:94:65:9e:c6:8a:
6a:5c:9b:76:5d:c0:ae:f8:36:61:15:3b:67:fb:15:
b3:cf:f4:2c:9b:56:66:13:89:89:69:01:d9:6e:b0:
f7:02:d4:06:c9
生成服務器端證書請求
Enter pass phrase for ca/private/server.pem:
查看證書請求
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=CN, ST=BJ, L=BJ, O=zlex, OU=zlex, CN=www.zlex.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:d8:f9:bd:0a:a8:d3:97:98:b2:22:af:29:a9:31:
76:50:52:77:c8:3b:7c:91:75:db:b3:63:88:cc:00:
be:1a:6c:e6:80:23:90:37:5f:1a:d3:80:f2:7f:b5:
77:01:ec:85:3e:4e:c0:af:0d:77:c0:a5:8b:bc:c3:
fe:70:91:66:17:a4:ec:23:08:5b:e3:df:a3:40:2f:
e6:83:bd:3f:d0:62:9c:c0:36:ad:e7:cb:13:e8:34:
d7:6a:66:57:f5:bb:94:2f:7c:d5:27:7b:ee:e6:4f:
fc:ff:c1:a4:01:96:d6:a0:b8:46:1d:93:02:a6:c5:
00:bd:d9:e9:4e:2d:87:d5:95
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha1WithRSAEncryption
2b:e9:b9:0b:e0:94:56:95:dd:59:1e:19:16:e0:f9:73:db:50:
63:d3:d4:4d:5c:9b:98:9f:a7:6d:9b:4d:ae:67:52:18:e1:42:
b0:66:7c:75:6a:db:98:bc:e6:47:08:aa:55:ca:ce:35:5c:5a:
60:8b:7b:c8:f0:10:8a:bd:5f:d7:c8:b8:48:03:18:7e:68:6e:
69:35:9c:c8:b0:c8:65:43:43:25:35:d7:d2:70:45:55:ab:78:
51:4d:22:c3:68:b2:97:b5:3c:86:e8:2b:43:de:5d:e4:b0:b5:
0e:eb:84:9d:42:81:ee:e0:0a:48:40:6a:93:a4:bd:3a:45:6f:
20:24
簽發服務器端證書
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for ca/private/ca.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 2 (0x2)
Validity
Not Before: Jul 24 08:16:15 2012 GMT
Not After : Jul 24 08:16:15 2013 GMT
Subject:
countryName = CN
stateOrProvinceName = BJ
organizationName = zlex
organizationalUnitName = zlex
commonName = www.zlex.org
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
CF:79:10:96:42:84:0C:51:DE:6E:DB:3C:5B:08:F1:E1:EB:0C:26:B9
X509v3 Authority Key Identifier:
keyid:7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
Certificate is to be certified until Jul 24 08:16:15 2013 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
查看證書詳情
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=CN, ST=BJ, O=zlex, OU=zlex, CN=ca.zlex.org
Validity
Not Before: Jul 24 08:16:15 2012 GMT
Not After : Jul 24 08:16:15 2013 GMT
Subject: C=CN, ST=BJ, O=zlex, OU=zlex, CN=www.zlex.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:d8:f9:bd:0a:a8:d3:97:98:b2:22:af:29:a9:31:
76:50:52:77:c8:3b:7c:91:75:db:b3:63:88:cc:00:
be:1a:6c:e6:80:23:90:37:5f:1a:d3:80:f2:7f:b5:
77:01:ec:85:3e:4e:c0:af:0d:77:c0:a5:8b:bc:c3:
fe:70:91:66:17:a4:ec:23:08:5b:e3:df:a3:40:2f:
e6:83:bd:3f:d0:62:9c:c0:36:ad:e7:cb:13:e8:34:
d7:6a:66:57:f5:bb:94:2f:7c:d5:27:7b:ee:e6:4f:
fc:ff:c1:a4:01:96:d6:a0:b8:46:1d:93:02:a6:c5:
00:bd:d9:e9:4e:2d:87:d5:95
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
CF:79:10:96:42:84:0C:51:DE:6E:DB:3C:5B:08:F1:E1:EB:0C:26:B9
X509v3 Authority Key Identifier:
keyid:7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
Signature Algorithm: sha1WithRSAEncryption
3d:85:0a:f5:a6:8e:f5:13:1b:fc:74:b6:50:8f:fe:0c:e6:32:
0e:0c:5a:0a:75:2d:e8:15:39:2f:93:46:29:c6:cc:27:5a:36:
a0:93:f8:bc:38:d5:2d:55:b9:19:de:81:6f:b6:5f:1f:07:65:
81:c5:12:4e:ea:3e:09:d0:d5:b8:66:c1:cd:4d:5d:51:19:a1:
7f:7b:cb:dc:bf:b0:be:3e:f8:8b:74:d3:31:a9:95:a3:ef:25:
a3:1e:98:65:0f:d4:40:51:ef:42:02:72:f0:59:26:8a:e7:d6:
ca:34:ad:fb:3d:a8:e7:05:93:a6:78:bd:b5:90:51:83:06:2b:
95:db:01:0c:89:9f:74:a4:32:89:c5:15:c6:ec:e2:61:10:29:
70:da:c5:ea:d6:9c:be:c3:4c:a1:42:6a:26:2f:23:7c:90:51:
8f:51:ee:49:c9:6b:9c:0c:15:a2:d3:dc:90:19:db:4d:d1:ad:
ca:06:d1:e1:60:20:18:b1:6d:0b:17:f7:06:e6:e8:d1:b0:0c:
6d:55:16:f1:63:54:da:c2:3f:6c:e5:99:68:7a:a0:fa:29:5c:
dc:cf:34:90:fb:91:7b:e0:5d:bb:a0:9d:91:f3:17:bd:0b:5a:
69:d7:0c:24:75:ca:b2:08:da:bf:67:35:ce:01:d0:4e:45:81:
97:bd:fb:87
證書轉換——服務器端
Enter pass phrase for ca/private/server.pem:
Enter Export Password:
Verifying - Enter Export Password:
生成客戶端私鑰
Generating RSA private key, 1024 bit long modulus
..++++++
...........++++++
e is 65537 (0x10001)
Enter pass phrase for ca/private/client.pem:
Verifying - Enter pass phrase for ca/private/client.pem:
查看私鑰信息
Enter pass phrase for ca/private/client.pem:
Private-Key: (1024 bit)
modulus:
00:b4:e9:7d:3d:6b:8b:07:94:7d:47:51:56:3e:0e:
92:2f:87:8c:60:0f:b8:cb:eb:90:6d:13:76:51:75:
e4:3e:b7:6e:1f:f0:63:5b:f7:ba:51:c0:04:1e:f1:
d0:ef:58:4a:35:47:4a:1a:11:72:fc:e9:10:82:ec:
3e:0d:ef:7d:17:a0:5e:93:b4:01:8f:a5:27:3c:3e:
a9:26:f0:00:ba:ca:24:98:92:51:3e:4b:d0:81:a7:
fc:14:e2:98:f5:27:f2:51:4c:a8:ae:b4:5f:e7:cc:
70:7e:23:57:92:6a:cf:d4:1d:6f:b3:52:8a:4a:1a:
1b:65:f0:4d:1c:0b:1f:50:eb
publicExponent: 65537 (0x10001)
privateExponent:
3a:35:b2:8d:73:af:fd:55:62:e5:f2:9e:dc:42:d5:
f8:a3:15:a0:c7:0e:3f:d6:e0:d6:a7:df:77:20:86:
bb:43:4c:14:cc:c5:3b:8f:3f:0d:14:ca:7e:a6:72:
02:c1:16:c7:83:d3:ad:05:96:49:18:38:ae:d7:92:
b3:eb:2e:05:43:d6:3d:04:3c:0b:fc:15:79:c5:85:
10:ed:21:6e:30:73:0b:a6:4f:9a:fe:db:4a:98:bc:
ec:03:7b:7f:e6:16:2f:a5:f3:5e:0d:cf:ce:eb:4a:
3e:c5:b9:7f:fc:4c:60:9e:0e:d4:aa:91:5a:46:f7:
b3:77:fc:0b:1b:62:70:b9
prime1:
00:ef:6d:7f:92:6a:af:21:59:ed:fe:49:a8:7c:4a:
1d:4d:7c:f9:38:bf:e7:dc:42:41:e1:33:f9:e1:c7:
74:45:2e:1c:e4:40:8d:5f:1a:ac:11:9e:a4:6c:1d:
00:6d:4e:aa:4d:58:e9:92:84:ac:d9:29:67:e0:79:
a8:a3:15:e3:2d
prime2:
00:c1:6f:21:c5:62:48:78:3a:0f:25:98:00:46:d6:
c2:2d:0f:96:fb:20:4b:f4:03:81:71:3f:6f:30:c0:
f3:a6:e6:f4:00:a4:fa:0b:97:e6:2a:21:8c:cb:c1:
28:eb:5f:f6:01:62:85:9a:37:98:e7:53:a4:8b:3f:
bd:77:eb:f3:77
exponent1:
00:e3:71:e0:9b:85:af:22:7e:9c:a0:50:f6:b6:43:
6d:bc:bb:b8:c0:d9:44:f8:2f:15:08:4b:68:d8:bb:
b1:cf:3a:34:05:fc:f0:8f:64:f6:0a:b2:ea:bd:2d:
7b:c7:5a:d0:5b:33:d8:86:f0:74:86:c3:57:c3:9d:
ae:be:66:3f:6d
exponent2:
00:82:4a:c9:04:9b:5f:15:1c:86:77:5c:1b:53:9b:
f4:cf:45:60:fd:66:93:c2:99:59:e7:5e:43:17:23:
e0:fa:db:36:1f:f9:00:34:2e:ec:ea:14:0f:32:6f:
b9:90:51:e2:f2:ab:da:32:36:a0:d7:b0:8f:74:fc:
4a:33:2c:cb:a1
coefficient:
51:c1:7e:d7:0d:98:86:cb:ca:41:ea:aa:54:6c:00:
49:c3:18:12:c4:5b:75:fe:0d:0c:e2:2f:0f:93:8e:
8e:01:c5:9d:ff:40:2b:20:08:24:7f:a5:f2:da:67:
96:5e:e6:7e:1e:52:32:2f:88:ef:df:20:6a:75:ec:
28:cd:fa:a0
生成客戶端證書請求
Enter pass phrase for ca/private/client.pem:
查看證書請求
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=CN, ST=BJ, L=BJ, O=zlex, OU=zlex, CN=zlex
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:b4:e9:7d:3d:6b:8b:07:94:7d:47:51:56:3e:0e:
92:2f:87:8c:60:0f:b8:cb:eb:90:6d:13:76:51:75:
e4:3e:b7:6e:1f:f0:63:5b:f7:ba:51:c0:04:1e:f1:
d0:ef:58:4a:35:47:4a:1a:11:72:fc:e9:10:82:ec:
3e:0d:ef:7d:17:a0:5e:93:b4:01:8f:a5:27:3c:3e:
a9:26:f0:00:ba:ca:24:98:92:51:3e:4b:d0:81:a7:
fc:14:e2:98:f5:27:f2:51:4c:a8:ae:b4:5f:e7:cc:
70:7e:23:57:92:6a:cf:d4:1d:6f:b3:52:8a:4a:1a:
1b:65:f0:4d:1c:0b:1f:50:eb
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha1WithRSAEncryption
91:5b:b2:2e:b3:54:14:92:7a:44:c0:59:11:0f:fe:08:50:33:
09:0f:73:d3:9d:15:43:07:66:4a:9e:7c:de:12:4d:bc:b6:3a:
7a:6b:36:40:3a:4b:ea:db:f7:2e:a1:de:ce:4f:a6:98:14:3b:
c0:f6:3d:fe:db:82:fa:c7:f1:1e:9a:6c:2b:ff:e6:a4:91:b1:
ab:20:44:91:a8:d9:1b:13:8f:9e:24:68:16:f3:c1:66:7b:3b:
29:b5:61:3d:be:88:00:d8:0a:1c:63:f0:25:6c:33:7d:86:80:
54:d5:75:db:6f:7e:9c:52:4c:70:0d:5a:88:ae:b5:1a:12:41:
e4:47
簽發客戶端證書
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for ca/private/ca.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 3 (0x3)
Validity
Not Before: Jul 24 08:16:35 2012 GMT
Not After : Jul 24 08:16:35 2013 GMT
Subject:
countryName = CN
stateOrProvinceName = BJ
organizationName = zlex
organizationalUnitName = zlex
commonName = zlex
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
FD:85:1C:BA:E0:C4:81:F5:F4:92:F1:FC:8A:59:77:33:60:6F:47:F7
X509v3 Authority Key Identifier:
keyid:7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
Certificate is to be certified until Jul 24 08:16:35 2013 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
查看證書詳情
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=CN, ST=BJ, O=zlex, OU=zlex, CN=ca.zlex.org
Validity
Not Before: Jul 24 08:16:35 2012 GMT
Not After : Jul 24 08:16:35 2013 GMT
Subject: C=CN, ST=BJ, O=zlex, OU=zlex, CN=zlex
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:b4:e9:7d:3d:6b:8b:07:94:7d:47:51:56:3e:0e:
92:2f:87:8c:60:0f:b8:cb:eb:90:6d:13:76:51:75:
e4:3e:b7:6e:1f:f0:63:5b:f7:ba:51:c0:04:1e:f1:
d0:ef:58:4a:35:47:4a:1a:11:72:fc:e9:10:82:ec:
3e:0d:ef:7d:17:a0:5e:93:b4:01:8f:a5:27:3c:3e:
a9:26:f0:00:ba:ca:24:98:92:51:3e:4b:d0:81:a7:
fc:14:e2:98:f5:27:f2:51:4c:a8:ae:b4:5f:e7:cc:
70:7e:23:57:92:6a:cf:d4:1d:6f:b3:52:8a:4a:1a:
1b:65:f0:4d:1c:0b:1f:50:eb
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
FD:85:1C:BA:E0:C4:81:F5:F4:92:F1:FC:8A:59:77:33:60:6F:47:F7
X509v3 Authority Key Identifier:
keyid:7E:C9:9A:37:37:66:AC:79:41:63:F0:61:48:CD:24:39:2F:C2:0E:E9
Signature Algorithm: sha1WithRSAEncryption
b2:31:c0:15:a1:8f:2c:6d:61:0c:4f:6e:c1:fe:7a:88:e0:60:
ce:6d:43:b4:29:d8:4d:83:4d:ea:ce:f0:8e:c1:c7:3b:bd:30:
cb:92:71:11:7d:19:04:11:58:25:5d:1b:ed:6f:22:13:91:ea:
13:7f:0e:99:00:ec:fb:b3:a5:e2:b9:ea:ea:bb:35:09:3b:ca:
f5:49:ac:a1:d3:d5:ae:ff:ce:11:a9:2f:53:74:88:24:9f:f8:
b2:bc:02:4d:1a:bb:c1:53:3e:6e:31:52:4d:ac:f8:14:bd:b1:
0d:31:1d:aa:94:43:38:5e:fb:c2:26:3e:43:ba:25:3b:23:27:
a8:7d:5d:3d:f9:97:28:71:51:1d:a4:56:44:b4:f6:51:4a:2b:
8b:47:d3:10:49:04:cd:c3:58:62:75:bc:c7:6a:4c:d5:9a:a8:
e9:9c:23:ec:f8:26:e5:de:43:4e:f2:8d:c2:75:40:70:3f:03:
0f:74:78:7a:bc:ca:6f:90:a0:3e:3a:d2:92:16:d5:ca:af:93:
28:1f:24:3a:7e:2c:b9:db:87:10:68:e0:c9:6c:0b:5d:9f:15:
be:bc:13:22:af:7b:8f:e9:14:51:04:65:7a:69:18:c2:ca:4f:
cb:e5:4c:62:41:88:b1:ee:ac:43:14:34:6d:58:af:52:b1:25:
76:f3:0e:8f
證書轉換——客戶端
Enter pass phrase for ca/private/client.pem:
Enter Export Password:
Verifying - Enter Export Password:
生成證書鏈
查看證書鏈
subject=/C=CN/ST=BJ/O=zlex/OU=zlex/CN=www.zlex.org
issuer=/C=CN/ST=BJ/O=zlex/OU=zlex/CN=ca.zlex.org
subject=/C=CN/ST=BJ/O=zlex/OU=zlex/CN=ca.zlex.org
issuer=/C=CN/ST=BJ/O=zlex/OU=zlex/CN=ca.zlex.org
subject=/C=CN/ST=BJ/O=zlex/OU=zlex/CN=zlex
issuer=/C=CN/ST=BJ/O=zlex/OU=zlex/CN=ca.zlex.org
來看一下這3套證書,如下兩幅圖所示:
CA證書
服務器證書
客戶證書
證書鏈
"ca.zlex.org"證書充當了CA根證書,"www.zlex.org"充當服務器端證書,"zlex"充當客戶端證書
使用keytool將其導入本地密鑰庫
導入CA證書
- keytool -import -v -trustcacerts -alias ca.zlex.org -file ca.crt -storepass 123456 -keystore ca.keystore
控制台輸出
所有者:CN=ca.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
簽發人:CN=ca.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
序列號:989b27ef00e53a99
有效期: Wed Jul 18 17:53:51 CST 2012 至Sat Jul 16 17:53:51 CST 2022
證書指紋:
MD5:BA:14:1F:89:3A:1E:63:7B:20:AC:5A:50:FE:65:7E:16
SHA1:E0:A4:0E:6F:09:7E:01:27:C0:FC:62:26:1A:0C:C6:7B:BF:6A:18:B3
簽名算法名稱:SHA1withRSA
版本: 1
信任這個認證? [否]: y
認證已添加至keystore中
[正在存儲 ca.keystore]
導入服務器端證書
- keytool -import -v -trustcacerts -alias www.zlex.org -file server.crt -storepass 123456 -keystore server.keystore
控制台輸出
所有者:CN=www.zlex.org, OU=zlex, O=zlex, ST=BJ, C=CN
簽發人:CN=ca.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
序列號:1
有效期: Wed Jul 18 17:54:25 CST 2012 至Thu Jul 18 17:54:25 CST 2013
證書指紋:
MD5:7E:5E:66:56:AF:E7:F5:72:0F:FC:95:85:97:07:4E:2A
SHA1:B1:E7:E8:AC:AB:C9:72:69:D8:E2:25:D5:16:A9:AF:C1:B7:4A:74:5D
簽名算法名稱:SHA1withRSA
版本: 3
擴展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: A8 49 2F E2 2D 15 9F 42 BD 76 2B 20 D3 EB A5 EE .I/.-..B.v+ ....
0010: 31 CA E7 63 1..c
]
]
#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
CA:false
PathLen: undefined
]
#3: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
[CN=ca.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN]
SerialNumber: [ 989b27ef 00e53a99]
]
#4: ObjectId: 2.16.840.1.113730.1.13 Criticality=false
信任這個認證? [否]: y
認證已添加至keystore中
[正在存儲 server.keystore]
導入客戶端證書
- keytool -import -v -trustcacerts -alias client -file client.crt -storepass 123456 -keystore client.keystore
以下是輸出內容:
所有者:CN=zlex, OU=zlex, O=zlex, ST=BJ, C=CN
簽發人:CN=ca.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
序列號:2
有效期: Wed Jul 18 17:54:49 CST 2012 至Thu Jul 18 17:54:49 CST 2013
證書指紋:
MD5:81:16:ED:92:9E:17:DB:E3:BE:DE:CD:8D:F8:E0:EE:C4
SHA1:C0:E0:42:81:79:70:4C:F8:44:D4:76:2D:C5:62:7C:67:B2:41:B3:AC
簽名算法名稱:SHA1withRSA
版本: 3
擴展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 0C 2C 25 86 C6 8D 04 88 F5 63 19 DC 09 B1 3C 5D .,%......c....<]
0010: 59 C9 72 1B Y.r.
]
]
#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
CA:false
PathLen: undefined
]
#3: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
[CN=ca.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN]
SerialNumber: [ 989b27ef 00e53a99]
]
#4: ObjectId: 2.16.840.1.113730.1.13 Criticality=false
信任這個認證? [否]: y
認證已添加至keystore中
[正在存儲 client.keystore]
PS 吊銷證書:
- echo 吊銷客戶端證書
- openssl ca -revoke $certs_path/client.crt -cert $certs_path/ca.crt -keyfile $private_path/ca.pem
Enter pass phrase for private/ca.pem:
Revoking Certificate 02.
Data Base Updated
生成證書吊銷列表文件(CRL)
執行命令如下:
- openssl ca -gencrl -out ca.crl -config "$HOME/testca/conf/testca.conf"
-crldays和-crlhours參數,說明下一個吊銷列表將在多少天后(或多少小時候)發布。
可以用以下命令檢查testca.crl的內容:
- openssl crl -in testca.crl -text -noout
引用
http://blog.csdn.net/gothicane/articles/2865818.aspx
http://www.5dlinux.com/article/7/2009/linux_35291.html
http://www.tc.umn.edu/~brams006/selfsign_ubuntu.html
http://www.tc.umn.edu/~brams006/selfsign.html
http://zhouzhk.iteye.com/blog/136943
http://bbs.cfan.com.cn/thread-743287-1-1.html
http://www.iteye.com/problems/4072
http://blog.csdn.net/jasonhwang/archive/2008/04/26/2329589.aspx
http://blog.csdn.net/jasonhwang/archive/2008/04/29/2344768.aspx
與計費系統打交道,少不了用到加密/解密實現。為了安全起見,通過非對稱加密交換對稱加密密鑰更是不可或缺。那么需要通過什么載體傳遞非對稱算法公鑰/私鑰信息?數字證書是公鑰的載體,而密鑰庫可以包含公鑰、私鑰信息。
JKS和 PKCS#12都是比較常用的兩種密鑰庫格式/標准。對於前者,搞Java開發,尤其是接觸過HTTPS平台的朋友,並不陌生。 JKS文件(通常為*.jks或*.keystore,擴展名無關)可以通過Java原生工具——KeyTool生成;而后者 PKCS#12文件(通常為*.p12或*.pfx,意味個人信息交換文件),則是通過更為常用的OpenSSL工具產生。
當然,這兩者之間是可以通過導入/導出的方式進行轉換的!當然,這種轉換需要通過KeyTool工具進行!
回歸正題,計費同事遇到一個難題:合作方交給他們一個*.pfx文件,需要他們從中提取密鑰,然后進行加密交互。其實,通過Java直接操作密鑰庫文件(或個人信息交換文件)對於一般Java開發人員來說,這都是個冷門。不接觸數字安全,根本不知所雲。況且,Java原生的密鑰庫文件格式為JKS,如何操作*.pfx文件?密鑰庫操作需要獲知密鑰庫別名,*.pfx別名是什么?!接下來就解決這些問題!

方案:
- 通過keytool密鑰庫導入命令importkeystore,將密鑰庫格式由PKCS#12轉換為JKS。
- 檢索新生成的密鑰庫文件,提取別名信息。
- 由密鑰庫文件導出數字證書(這里將用到別名)。
- 通過代碼提取公鑰/私鑰、簽名算法等
先看格式轉換:
- echo 格式轉換
- keytool -importkeystore -v -srckeystore zlex.pfx -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore zlex.keystore -deststoretype jks -deststorepass 123456
-importkeystore導入密鑰庫,通過格式設定,我們可以將PKCS#12文件轉換為JKS格式。
-v顯示詳情
-srckeystore源密鑰庫,這里是zlex.pfx
-srcstoretype源密鑰庫格式,這里為pkcs12
-srcstorepass源密鑰庫密碼,這里為123456
-destkeystore目標密鑰庫,這里為zlex.keystore
-deststoretype目標密鑰庫格式,這里為jks,默認值也如此
-deststorepass目標密鑰庫密碼,這里為123456
通過這個操作,我們能夠獲得所需的密鑰庫文件zlex.keystore。
這時,我們已經獲得了密鑰庫文件,只要確定對應的別名信息,就可以提取公鑰/私鑰,以及數字證書,進行加密交互了!

- echo 查看證書
- keytool -list -keystore zlex.keystore -storepass 123456 -v
-list列舉密鑰庫
-keystore密鑰庫,這里是zlex.keystore
-storepass密鑰庫密碼,這里是123456
-v顯示詳情
這里需要細致觀察一下別名信息!!!就是紅框中的數字1!!!
現在,我們把證書導出!
- echo 導出證書
- keytool -exportcert -alias 1 -keystore zlex.keystore -file zlex.crt -storepass 123456
-exportcert導出證書
-alias別名,這里是1
-keystore密鑰庫,這里是zlex.keystore
-file證書文件,這里是zlex.crt
-storepass密鑰庫密碼,這里是123456
現在證書也導出了,我們可以提取公鑰/私鑰,進行加密/解密,簽名/驗證操作了!當然,即便沒有證書,我們也能夠通過密鑰庫(JKS格式)文件獲得證書,以及公鑰/私鑰、簽名算法等。
補充代碼, 其實就是對 Java加密技術(八)的修改!

- /**
- * 2010-8-11
- */
- 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 <a href="mailto:zlex.dongliang@gmail.com">梁棟</a>
- * @since 1.0
- */
- public class CertificateCoder {
- /**
- * 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 keyStorePassword
- * @param alias
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- private static PrivateKey getPrivateKey(String keyStorePath,
- String keyStorePassword, String alias, String aliasPassword)
- throws Exception {
- KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
- PrivateKey key = (PrivateKey) ks.getKey(alias,
- aliasPassword.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 keyStorePassword
- * @param alias
- * @return
- * @throws Exception
- */
- private static Certificate getCertificate(String keyStorePath,
- String keyStorePassword, String alias) throws Exception {
- KeyStore ks = getKeyStore(keyStorePath, keyStorePassword);
- 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 keyStorePassword
- * @param alias
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
- String keyStorePassword, String alias, String aliasPassword)
- throws Exception {
- // 取得私鑰
- PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
- alias, aliasPassword);
- // 對數據加密
- Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
- cipher.init(Cipher.ENCRYPT_MODE, privateKey);
- return cipher.doFinal(data);
- }
- /**
- * 私鑰解密
- *
- * @param data
- * @param keyStorePath
- * @param alias
- * @param keyStorePassword
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
- String alias, String keyStorePassword, String aliasPassword)
- throws Exception {
- // 取得私鑰
- PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
- alias, aliasPassword);
- // 對數據加密
- 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 keyStorePassword
- * @param aliasPassword
- * @return
- * @throws Exception
- */
- public static byte[] sign(byte[] sign, String keyStorePath, String alias,
- String keyStorePassword, String aliasPassword) throws Exception {
- // 獲得證書
- X509Certificate x509Certificate = (X509Certificate) getCertificate(
- keyStorePath, keyStorePassword, alias);
- // 取得私鑰
- PrivateKey privateKey = getPrivateKey(keyStorePath, keyStorePassword,
- alias, aliasPassword);
- // 構建簽名
- Signature signature = Signature.getInstance(x509Certificate
- .getSigAlgName());
- signature.initSign(privateKey);
- signature.update(sign);
- return signature.sign();
- }
- /**
- * 驗證簽名
- *
- * @param data
- * @param sign
- * @param certificatePath
- * @return
- * @throws Exception
- */
- public static boolean verify(byte[] data, byte[] 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(sign);
- }
- /**
- * 驗證Certificate
- *
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @return
- */
- public static boolean verifyCertificate(Date date, String keyStorePath,
- String keyStorePassword, String alias) {
- boolean status = true;
- try {
- Certificate certificate = getCertificate(keyStorePath,
- keyStorePassword, alias);
- status = verifyCertificate(date, certificate);
- } catch (Exception e) {
- status = false;
- }
- return status;
- }
- /**
- * 驗證Certificate
- *
- * @param keyStorePath
- * @param keyStorePassword
- * @param alias
- * @return
- */
- public static boolean verifyCertificate(String keyStorePath,
- String keyStorePassword, String alias) {
- return verifyCertificate(new Date(), keyStorePath, keyStorePassword,
- alias);
- }
- }
相信上述代碼已經幫朋友們解決了相當多的問題!

給出測試類:
- import static org.junit.Assert.*;
- import java.util.Date;
- import org.apache.commons.codec.binary.Hex;
- import org.junit.Test;
- /**
- * 證書操作驗證類
- *
- * @author <a href="mailto:zlex.dongliang@gmail.com">梁棟</a>
- * @version 1.0
- * @since 1.0
- */
- public class CertificateCoderTest {
- private String certificatePath = "zlex.crt";
- private String keyStorePath = "zlex.keystore";
- private String keyStorePassword = "123456";
- private String aliasPassword = "123456";
- private String alias = "1";
- @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, keyStorePassword, aliasPassword);
- 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, keyStorePassword, alias, aliasPassword);
- byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
- certificatePath);
- String outputStr = new String(decodedData);
- System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
- assertEquals(inputStr, outputStr);
- System.err.println("私鑰簽名——公鑰驗證簽名");
- // 產生簽名
- byte[] sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
- keyStorePassword, aliasPassword);
- System.err.println("簽名:\r" + Hex.encodeHexString(sign));
- // 驗證簽名
- boolean status = CertificateCoder.verify(encodedData, sign,
- certificatePath);
- System.err.println("狀態:\r" + status);
- assertTrue(status);
- }
- @Test
- public void testVerify() throws Exception {
- System.err.println("密鑰庫證書有效期驗證");
- boolean status = CertificateCoder.verifyCertificate(new Date(),
- keyStorePath, keyStorePassword, alias);
- System.err.println("證書狀態:\r" + status);
- assertTrue(status);
- }
- }
第一個測試方法,用於提取公鑰/私鑰進行加密/解密操作。
第二個測試方法,用於提取簽名算法進行簽名/驗證操作。
第三個測試方法,用於測試密鑰庫該別名對應的證書,當前日期下,是否有效。

OK,任務完成,密鑰成功提取,剩下的都是代碼基本功了!
