如基本的單向加密算法:
-
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代碼實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* 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代碼實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 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代碼實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* 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代碼實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* 初始化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);
}
|
給出一個完整類,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
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);
}
}
|
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
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類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
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對字符串加密生成密鑰,這是比較常見的密鑰生成方式。
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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);就可以了,此外就是密鑰長度不同了。
1
2
3
4
5
6
7
8
|
/**
* 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類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
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);
}
}
|
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
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;
}
}
|
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
import
static
org.junit.Assert.*;
import
org.junit.Before;
import
org.junit.Test;
import
java.util.Map;
/**
*
* @author 梁棟
* @version 1.0
* @since 1.0
*/
public
class
RSACoderTest {
private
String publicKey;
private
String privateKey;
@Before
public
void
setUp()
throws
Exception {
Map<String, Object> keyMap = RSACoder.initKey();
publicKey = RSACoder.getPublicKey(keyMap);
privateKey = RSACoder.getPrivateKey(keyMap);
System.err.println(
"公鑰: \n\r"
+ publicKey);
System.err.println(
"私鑰: \n\r"
+ privateKey);
}
@Test
public
void
test()
throws
Exception {
System.err.println(
"公鑰加密——私鑰解密"
);
String inputStr =
"abc"
;
byte
[] data = inputStr.getBytes();
byte
[] encodedData = RSACoder.encryptByPublicKey(data, publicKey);
byte
[] decodedData = RSACoder.decryptByPrivateKey(encodedData,
privateKey);
String outputStr =
new
String(decodedData);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
assertEquals(inputStr, outputStr);
}
@Test
public
void
testSign()
throws
Exception {
System.err.println(
"私鑰加密——公鑰解密"
);
String inputStr =
"sign"
;
byte
[] data = inputStr.getBytes();
byte
[] encodedData = RSACoder.encryptByPrivateKey(data, privateKey);
byte
[] decodedData = RSACoder
.decryptByPublicKey(encodedData, publicKey);
String outputStr =
new
String(decodedData);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
assertEquals(inputStr, outputStr);
System.err.println(
"私鑰簽名——公鑰驗證簽名"
);
// 產生簽名
String sign = RSACoder.sign(encodedData, privateKey);
System.err.println(
"簽名:\r"
+ sign);
// 驗證簽名
boolean
status = RSACoder.verify(encodedData, publicKey, sign);
System.err.println(
"狀態:\r"
+ status);
assertTrue(status);
}
}
|
控制台輸出:
公鑰: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYU/+I0+z1aBl5X6DUUOHQ7FZpmBSDbKTtx89J EcB64jFCkunELT8qiKly7fzEqD03g8ALlu5XvX+bBqHFy7YPJJP0ekE2X3wjUnh2NxlqpH3/B/xm 1ZdSlCwDIkbijhBVDjA/bu5BObhZqQmDwIxlQInL9oVz+o6FbAZCyHBd7wIDAQAB 私鑰: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJhT/4jT7PVoGXlfoNRQ4dDsVmmY FINspO3Hz0kRwHriMUKS6cQtPyqIqXLt/MSoPTeDwAuW7le9f5sGocXLtg8kk/R6QTZffCNSeHY3 GWqkff8H/GbVl1KULAMiRuKOEFUOMD9u7kE5uFmpCYPAjGVAicv2hXP6joVsBkLIcF3vAgMBAAEC gYBvZHWoZHmS2EZQqKqeuGr58eobG9hcZzWQoJ4nq/CarBAjw/VovUHE490uK3S9ht4FW7Yzg3LV /MB06Huifh6qf/X9NQA7SeZRRC8gnCQk6JuDIEVJOud5jU+9tyumJakDKodQ3Jf2zQtNr+5ZdEPl uwWgv9c4kmpjhAdyMuQmYQJBANn6pcgvyYaia52dnu+yBUsGkaFfwXkzFSExIbi0MXTkhEb/ER/D rLytukkUu5S5ecz/KBa8U4xIslZDYQbLz5ECQQCy5dutt7RsxN4+dxCWn0/1FrkWl2G329Ucewm3 QU9CKu4D+7Kqdj+Ha3lXP8F0Etaaapi7+EfkRUpukn2ItZV/AkEAlk+I0iphxT1rCB0Q5CjWDY5S Df2B5JmdEG5Y2o0nLXwG2w44OLct/k2uD4cEcuITY5Dvi/4BftMCZwm/dnhEgQJACIktJSnJwxLV o9dchENPtlsCM9C/Sd2EWpqISSUlmfugZbJBwR5pQ5XeMUqKeXZYpP+HEBj1nS+tMH9u2/IGEwJA fL8mZiZXan/oBKrblAbplNcKWGRVD/3y65042PAEeghahlJMiYquV5DzZajuuT0wbJ5xQuZB01+X nfpFpBJ2dw== 公鑰加密——私鑰解密 加密前: abc 解密后: abc 公鑰: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdOj40yEB48XqWxmPILmJAc7UecIN7F32etSHF 9rwbuEh3+iTPOGSxhoSQpOED0vOb0ZIMkBXZSgsxLaBSin2RZ09YKWRjtpCA0kDkiD11gj4tzTiM l9qq1kwSK7ZkGAgodEn3yIILVmQDuEImHOXFtulvJ71ka07u3LuwUNdB/wIDAQAB 私鑰: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAN06PjTIQHjxepbGY8guYkBztR5w g3sXfZ61IcX2vBu4SHf6JM84ZLGGhJCk4QPS85vRkgyQFdlKCzEtoFKKfZFnT1gpZGO2kIDSQOSI PXWCPi3NOIyX2qrWTBIrtmQYCCh0SffIggtWZAO4QiYc5cW26W8nvWRrTu7cu7BQ10H/AgMBAAEC gYEAz2JWBizjI31bqhP4XiP9PuY5F3vqBW4T+L9cFbQiyumKJc58yzTWUAUGKIIn3enXLG7dNqGr mbJro4JeFIJ3CiVDpXR9+FluIgI4SXm7ioGKF2NOMA9LR5Fu82W+pLfpTN2y2SaLYWEDZyp53BxY j9gUxaxi1MQs+C1ZgDF2xmECQQDy70bQntbRfysP+ppCtd56YRnES1Tyekw0wryS2tr+ivQJl7JF gp5rPAOXpgrq36xHDwUspQ0sJ0vj0O7ywxr1AkEA6SAaLhrJJrYucC0jxwAhUYyaPN+aOsWymaRh 9jA/Wc0wp29SbGTh5CcMuGpXm1g0M+FKW3dGiHgS3rVUKim4owJAbnxgapUzAgiiHxxMeDaavnHW 9C2GrtjsO7qtZOTgYI/1uT8itvZW8lJTF+9OW8/qXE76fXl7ai9dFnl5kzMk2QJBALfHz/vCsArt mkRiwY6zApE4Z6tPl1V33ymSVovvUzHnOdD1SKQdD5t+UV/crb3QVi8ED0t2B0u0ZSPfDT/D7kMC QDpwdj9k2F5aokLHBHUNJPFDAp7a5QMaT64gv/d48ITJ68Co+v5WzLMpzJBYXK6PAtqIhxbuPEc2 I2k1Afmrwyw= 私鑰加密——公鑰解密 加密前: sign 解密后: sign 私鑰簽名——公鑰驗證簽名 簽名: ud1RsIwmSC1pN22I4IXteg1VD2FbiehKUfNxgVSHzvQNIK+d20FCkHCqh9djP3h94iWnIUY0ifU+ mbJkhAl/i5krExOE0hknOnPMcEP+lZV1RbJI2zG2YooSp2XDleqrQk5e/QF2Mx0Zxt8Xsg7ucVpn i3wwbYWs9wSzIf0UjlM= 狀態: true
簡要總結一下,使用公鑰加密、私鑰解密,完成了乙方到甲方的一次數據傳遞,通過私鑰加密、公鑰解密,同時通過私鑰簽名、公鑰驗證簽名,完成了一次甲方到乙方的數據傳遞與驗證,兩次數據傳遞完成一整套的數據交互!
類似數字簽名,數字信封是這樣描述的:
數字信封
數字信封用加密技術來保證只有特定的收信人才能閱讀信的內容。
流程:
信息發送方采用對稱密鑰來加密信息,然后再用接收方的公鑰來加密此對稱密鑰(這部分稱為數字信封),再將它和信息一起發送給接收方;接收方先用相應的私鑰打開數字信封,得到對稱密鑰,然后使用對稱密鑰再解開信息。
接下來我們分析DH加密算法,一種適基於密鑰一致協議的加密算法。
DH
Diffie- Hellman算法(D-H算法),密鑰一致協議。是由公開密鑰密碼體制的奠基人Diffie和Hellman所提出的一種思想。簡單的說就是允許兩名用 戶在公開媒體上交換信息以生成"一致"的、可以共享的密鑰。換句話說,就是由甲方產出一對密鑰(公鑰、私鑰),乙方依照甲方公鑰產生乙方密鑰對(公鑰、私 鑰)。以此為基線,作為數據傳輸保密基礎,同時雙方使用同一種對稱加密算法構建本地密鑰(SecretKey)對數據加密。這樣,在互通了本地密鑰 (SecretKey)算法后,甲乙雙方公開自己的公鑰,使用對方的公鑰和剛才產生的私鑰加密數據,同時可以使用對方的公鑰和自己的私鑰對數據解密。不單 單是甲乙雙方兩方,可以擴展為多方共享數據通訊,這樣就完成了網絡交互數據的安全通訊!該算法源於中國的同余定理——中國餘數定理。
流程分析:
1.甲方構建密鑰對兒,將公鑰公布給乙方,將私鑰保留;雙方約定數據加密算法;乙方通過甲方公鑰構建密鑰對兒,將公鑰公布給甲方,將私鑰保留。
2.甲方使用私鑰、乙方公鑰、約定數據加密算法構建本地密鑰,然后通過本地密鑰加密數據,發送給乙方加密后的數據;乙方使用私鑰、甲方公鑰、約定數據加密算法構建本地密鑰,然后通過本地密鑰對數據解密。
3.乙方使用私鑰、甲方公鑰、約定數據加密算法構建本地密鑰,然后通過本地密鑰加密數據,發送給甲方加密后的數據;甲方使用私鑰、乙方公鑰、約定數據加密算法構建本地密鑰,然后通過本地密鑰對數據解密。
通過java代碼實現如下:Coder類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
import
java.security.Key;
import
java.security.KeyFactory;
import
java.security.KeyPair;
import
java.security.KeyPairGenerator;
import
java.security.PublicKey;
import
java.security.spec.PKCS8EncodedKeySpec;
import
java.security.spec.X509EncodedKeySpec;
import
java.util.HashMap;
import
java.util.Map;
import
javax.crypto.Cipher;
import
javax.crypto.KeyAgreement;
import
javax.crypto.SecretKey;
import
javax.crypto.interfaces.DHPrivateKey;
import
javax.crypto.interfaces.DHPublicKey;
import
javax.crypto.spec.DHParameterSpec;
/**
* DH安全編碼組件
*
* @author 梁棟
* @version 1.0
* @since 1.0
*/
public
abstract
class
DHCoder
extends
Coder {
public
static
final
String ALGORITHM =
"DH"
;
/**
* 默認密鑰字節數
*
* <pre>
* DH
* Default Keysize 1024
* Keysize must be a multiple of 64, ranging from 512 to 1024 (inclusive).
* </pre>
*/
private
static
final
int
KEY_SIZE =
1024
;
/**
* DH加密下需要一種對稱加密算法對數據加密,這里我們使用DES,也可以使用其他對稱加密算法。
*/
public
static
final
String SECRET_ALGORITHM =
"DES"
;
private
static
final
String PUBLIC_KEY =
"DHPublicKey"
;
private
static
final
String PRIVATE_KEY =
"DHPrivateKey"
;
/**
* 初始化甲方密鑰
*
* @return
* @throws Exception
*/
public
static
Map<String, Object> initKey()
throws
Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator
.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 甲方公鑰
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
// 甲方私鑰
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap =
new
HashMap<String, Object>(
2
);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return
keyMap;
}
/**
* 初始化乙方密鑰
*
* @param key
* 甲方公鑰
* @return
* @throws Exception
*/
public
static
Map<String, Object> initKey(String key)
throws
Exception {
// 解析甲方公鑰
byte
[] keyBytes = decryptBASE64(key);
X509EncodedKeySpec x509KeySpec =
new
X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// 由甲方公鑰構建乙方密鑰
DHParameterSpec dhParamSpec = ((DHPublicKey) pubKey).getParams();
KeyPairGenerator keyPairGenerator = KeyPairGenerator
.getInstance(keyFactory.getAlgorithm());
keyPairGenerator.initialize(dhParamSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 乙方公鑰
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
// 乙方私鑰
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap =
new
HashMap<String, Object>(
2
);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return
keyMap;
}
/**
* 加密<br>
*
* @param data
* 待加密數據
* @param publicKey
* 甲方公鑰
* @param privateKey
* 乙方私鑰
* @return
* @throws Exception
*/
public
static
byte
[] encrypt(
byte
[] data, String publicKey,
String privateKey)
throws
Exception {
// 生成本地密鑰
SecretKey secretKey = getSecretKey(publicKey, privateKey);
// 數據加密
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return
cipher.doFinal(data);
}
/**
* 解密<br>
*
* @param data
* 待解密數據
* @param publicKey
* 乙方公鑰
* @param privateKey
* 乙方私鑰
* @return
* @throws Exception
*/
public
static
byte
[] decrypt(
byte
[] data, String publicKey,
String privateKey)
throws
Exception {
// 生成本地密鑰
SecretKey secretKey = getSecretKey(publicKey, privateKey);
// 數據解密
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return
cipher.doFinal(data);
}
/**
* 構建密鑰
*
* @param publicKey
* 公鑰
* @param privateKey
* 私鑰
* @return
* @throws Exception
*/
private
static
SecretKey getSecretKey(String publicKey, String privateKey)
throws
Exception {
// 初始化公鑰
byte
[] pubKeyBytes = decryptBASE64(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
X509EncodedKeySpec x509KeySpec =
new
X509EncodedKeySpec(pubKeyBytes);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// 初始化私鑰
byte
[] priKeyBytes = decryptBASE64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec =
new
PKCS8EncodedKeySpec(priKeyBytes);
Key priKey = keyFactory.generatePrivate(pkcs8KeySpec);
KeyAgreement keyAgree = KeyAgreement.getInstance(keyFactory
.getAlgorithm());
keyAgree.init(priKey);
keyAgree.doPhase(pubKey,
true
);
// 生成本地密鑰
SecretKey secretKey = keyAgree.generateSecret(SECRET_ALGORITHM);
return
secretKey;
}
/**
* 取得私鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public
static
String getPrivateKey(Map<String, Object> keyMap)
throws
Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return
encryptBASE64(key.getEncoded());
}
/**
* 取得公鑰
*
* @param keyMap
* @return
* @throws Exception
*/
public
static
String getPublicKey(Map<String, Object> keyMap)
throws
Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return
encryptBASE64(key.getEncoded());
}
}
|
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
import
static
org.junit.Assert.*;
import
java.util.Map;
import
org.junit.Test;
/**
*
* @author 梁棟
* @version 1.0
* @since 1.0
*/
public
class
DHCoderTest {
@Test
public
void
test()
throws
Exception {
// 生成甲方密鑰對兒
Map<String, Object> aKeyMap = DHCoder.initKey();
String aPublicKey = DHCoder.getPublicKey(aKeyMap);
String aPrivateKey = DHCoder.getPrivateKey(aKeyMap);
System.err.println(
"甲方公鑰:\r"
+ aPublicKey);
System.err.println(
"甲方私鑰:\r"
+ aPrivateKey);
// 由甲方公鑰產生本地密鑰對兒
Map<String, Object> bKeyMap = DHCoder.initKey(aPublicKey);
String bPublicKey = DHCoder.getPublicKey(bKeyMap);
String bPrivateKey = DHCoder.getPrivateKey(bKeyMap);
System.err.println(
"乙方公鑰:\r"
+ bPublicKey);
System.err.println(
"乙方私鑰:\r"
+ bPrivateKey);
String aInput =
"abc "
;
System.err.println(
"原文: "
+ aInput);
// 由甲方公鑰,乙方私鑰構建密文
byte
[] aCode = DHCoder.encrypt(aInput.getBytes(), aPublicKey,
bPrivateKey);
// 由乙方公鑰,甲方私鑰解密
byte
[] aDecode = DHCoder.decrypt(aCode, bPublicKey, aPrivateKey);
String aOutput = (
new
String(aDecode));
System.err.println(
"解密: "
+ aOutput);
assertEquals(aInput, aOutput);
System.err.println(
" ===============反過來加密解密================== "
);
String bInput =
"def "
;
System.err.println(
"原文: "
+ bInput);
// 由乙方公鑰,甲方私鑰構建密文
byte
[] bCode = DHCoder.encrypt(bInput.getBytes(), bPublicKey,
aPrivateKey);
// 由甲方公鑰,乙方私鑰解密
byte
[] bDecode = DHCoder.decrypt(bCode, aPublicKey, bPrivateKey);
String bOutput = (
new
String(bDecode));
System.err.println(
"解密: "
+ bOutput);
assertEquals(bInput, bOutput);
}
}
|
控制台輸出:
甲方公鑰: MIHfMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHz W5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG kx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgANDAAJAdAWBVmIzqcko Ej6qFjLDL2+Y3FPq1iRbnOyOpDj71yKaK1K+FhTv04B0zy4DKcvAASV7/Gv0W+bgqdmffRkqrQ== 甲方私鑰: MIHRAgEAMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYX rgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpD TWSGkx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgAQyAjACJRfy1LyR eHyD+4Hfb+xR0uoIGR1oL9i9Nk6g2AAuaDPgEVWHn+QXID13yL/uDos= 乙方公鑰: MIHfMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYXrgHz W5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpDTWSG kx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgANDAAJAVEYSfBA+I9nr dWw3OBv475C+eBrWBBYqt0m6/eu4ptuDQHwV4MmUtKAC2wc2nNrdb1wmBhY1X8RnWkJ1XmdDbQ== 乙方私鑰: MIHSAgEAMIGXBgkqhkiG9w0BAwEwgYkCQQD8poLOjhLKuibvzPcRDlJtsHiwXt7LzR60ogjzrhYX rgHzW5Gkfm32NBPF4S7QiZvNEyrNUNmRUb3EPuc3WS4XAkBnhHGyepz0TukaScUUfbGpqvJE8FpD TWSGkx0tFCcbnjUDC3H9c9oXkGmzLik1Yw4cIGI1TQ2iCmxBblC+eUykAgIBgAQzAjEAqaZiCdXp 2iNpdBlHRaO9ir70wo2n32xNlIzIX19VLSPCDdeUWkgRv4CEj/8k+/yd 原文: abc 解密: abc ===============反過來加密解密================== 原文: def 解密: def
如我所言,甲乙雙方在獲得對方公鑰后可以對發送給對方的數據加密,同時也能對接收到的數據解密,達到了數據安全通信的目的!
接下來我們介紹DSA數字簽名,非對稱加密的另一種實現。
DSA
DSA-Digital Signature Algorithm 是Schnorr和ElGamal簽名算法的變種,被美國NIST作為DSS(DigitalSignature Standard)。簡單的說,這是一種更高級的驗證方式,用作數字簽名。不單單只有公鑰、私鑰,還有數字簽名。私鑰加密生成數字簽名,公鑰驗證數據及簽 名。如果數據和簽名不匹配則認為驗證失敗!數字簽名的作用就是校驗數據在傳輸過程中不被修改。數字簽名,是單向加密的升級!
通過java代碼實現如下:Coder類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
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());
}
}
|
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
|
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類,驗證程序。
照舊提供一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import
static
org.junit.Assert.*;
import
java.math.BigInteger;
import
java.security.spec.ECFieldF2m;
import
java.security.spec.ECParameterSpec;
import
java.security.spec.ECPoint;
import
java.security.spec.ECPrivateKeySpec;
import
java.security.spec.ECPublicKeySpec;
import
java.security.spec.EllipticCurve;
import
java.util.Map;
import
org.junit.Test;
/**
*
* @author 梁棟
* @version 1.0
* @since 1.0
*/
public
class
ECCCoderTest {
@Test
public
void
test()
throws
Exception {
String inputStr =
"abc"
;
byte
[] data = inputStr.getBytes();
Map<String, Object> keyMap = ECCCoder.initKey();
String publicKey = ECCCoder.getPublicKey(keyMap);
String privateKey = ECCCoder.getPrivateKey(keyMap);
System.err.println(
"公鑰: \n"
+ publicKey);
System.err.println(
"私鑰: \n"
+ privateKey);
byte
[] encodedData = ECCCoder.encrypt(data, publicKey);
byte
[] decodedData = ECCCoder.decrypt(encodedData, privateKey);
String outputStr =
new
String(decodedData);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
assertEquals(inputStr, outputStr);
}
}
|
控制台輸出:
公鑰: MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAv4TwFN7vBGsqgfXk95ObV5clO7oAokHD7BdOP9YMh8u gAU21TjM2qPZ 私鑰: MDICAQAwEAYHKoZIzj0CAQYFK4EEAAEEGzAZAgEBBBTYJsR3BN7TFw7JHcAHFkwNmfil7w== 加密前: abc 解密后: abc
本篇的主要內容為Java證書體系的實現。
在構建Java代碼實現前,我們需要完成證書的制作。
1.生成keyStroe文件
在命令行下執行以下命令:
keytool -genkey -validity 36000 -alias www.zlex.org -keyalg RSA -keystore d:\zlex.keystore
其中
-genkey表示生成密鑰
-validity指定證書有效期,這里是36000天
-alias指定別名,這里是www.zlex.org
-keyalg指定算法,這里是RSA
-keystore指定存儲位置,這里是d:\zlex.keystore
在這里我使用的密碼為 123456
控制台輸出:
輸入keystore密碼: 再次輸入新密碼: 您的名字與姓氏是什么? [Unknown]: www.zlex.org 您的組織單位名稱是什么? [Unknown]: zlex 您的組織名稱是什么? [Unknown]: zlex 您所在的城市或區域名稱是什么? [Unknown]: BJ 您所在的州或省份名稱是什么? [Unknown]: BJ 該單位的兩字母國家代碼是什么 [Unknown]: CN CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 正確嗎? [否]: Y 輸入<tomcat>的主密碼 (如果和 keystore 密碼相同,按回車): 再次輸入新密碼:
這時,在D盤下會生成一個zlex.keystore的文件。
2.生成自簽名證書
光有keyStore文件是不夠的,還需要證書文件,證書才是直接提供給外界使用的公鑰憑證。
導出證書:
keytool -export -keystore d:\zlex.keystore -alias www.zlex.org -file d:\zlex.cer -rfc
其中
-export指定為導出操作
-keystore指定keystore文件
-alias指定導出keystore文件中的別名
-file指向導出路徑
-rfc以文本格式輸出,也就是以BASE64編碼輸出
這里的密碼是 123456
控制台輸出:
輸入keystore密碼: 保存在文件中的認證 <d:\zlex.cer>
當然,使用方是需要導入證書的!
可以通過自簽名證書完成CAS單點登錄系統的構建!
Ok,准備工作完成,開始Java實現!
通過java代碼實現如下:Coder類見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
|
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);
}
}
|
再給出一個測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
import
static
org.junit.Assert.*;
import
org.junit.Test;
/**
*
* @author 梁棟
* @version 1.0
* @since 1.0
*/
public
class
CertificateCoderTest {
private
String password =
"123456"
;
private
String alias =
"www.zlex.org"
;
private
String certificatePath =
"d:/zlex.cer"
;
private
String keyStorePath =
"d:/zlex.keystore"
;
@Test
public
void
test()
throws
Exception {
System.err.println(
"公鑰加密——私鑰解密"
);
String inputStr =
"Ceritifcate"
;
byte
[] data = inputStr.getBytes();
byte
[] encrypt = CertificateCoder.encryptByPublicKey(data,
certificatePath);
byte
[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
keyStorePath, alias, password);
String outputStr =
new
String(decrypt);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
// 驗證數據一致
assertArrayEquals(data, decrypt);
// 驗證證書有效
assertTrue(CertificateCoder.verifyCertificate(certificatePath));
}
@Test
public
void
testSign()
throws
Exception {
System.err.println(
"私鑰加密——公鑰解密"
);
String inputStr =
"sign"
;
byte
[] data = inputStr.getBytes();
byte
[] encodedData = CertificateCoder.encryptByPrivateKey(data,
keyStorePath, alias, password);
byte
[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
certificatePath);
String outputStr =
new
String(decodedData);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
assertEquals(inputStr, outputStr);
System.err.println(
"私鑰簽名——公鑰驗證簽名"
);
// 產生簽名
String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
password);
System.err.println(
"簽名:\r"
+ sign);
// 驗證簽名
boolean
status = CertificateCoder.verify(encodedData, sign,
certificatePath);
System.err.println(
"狀態:\r"
+ status);
assertTrue(status);
}
}
|
控制台輸出:
公鑰加密——私鑰解密 加密前: Ceritificate 解密后: Ceritificate 私鑰加密——公鑰解密 加密前: sign 解密后: sign 私鑰簽名——公鑰驗證簽名 簽名: pqBn5m6PJlfOjH0A6U2o2mUmBsfgyEY1NWCbiyA/I5Gc3gaVNVIdj/zkGNZRqTjhf3+J9a9z9EI7 6F2eWYd7punHx5oh6hfNgcKbVb52EfItl4QEN+djbXiPynn07+Lbg1NOjULnpEd6ZhLP1YwrEAuM OfvX0e7/wplxLbySaKQ= 狀態: true
由此完成了證書驗證體系!
同樣,我們可以對代碼做簽名——代碼簽名!
通過工具JarSigner可以完成代碼簽名。
這里我們對tools.jar做代碼簽名,命令如下:
jarsigner -storetype jks -keystore zlex.keystore -verbose tools.jar www.zlex.org
控制台輸出:
輸入密鑰庫的口令短語: 正在更新: META-INF/WWW_ZLEX.SF 正在更新: META-INF/WWW_ZLEX.RSA 正在簽名: org/zlex/security/Security.class 正在簽名: org/zlex/tool/Main$1.class 正在簽名: org/zlex/tool/Main$2.class 正在簽名: org/zlex/tool/Main.class 警告: 簽名者證書將在六個月內過期。
此時,我們可以對簽名后的jar做驗證!
驗證tools.jar,命令如下:
jarsigner -verify -verbose -certs tools.jar
控制台輸出:
402 Sat Jun 20 16:25:14 CST 2009 META-INF/MANIFEST.MF 532 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.SF 889 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.RSA sm 590 Wed Dec 10 13:03:42 CST 2008 org/zlex/security/Security.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證書將在 09-9-18 下午3:27 到期] sm 705 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$1.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證書將在 09-9-18 下午3:27 到期] sm 779 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$2.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證書將在 09-9-18 下午3:27 到期] sm 12672 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main.class X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN [證書將在 09-9-18 下午3:27 到期] s = 已驗證簽名 m = 在清單中列出條目 k = 在密鑰庫中至少找到了一個證書 i = 在身份作用域內至少找到了一個證書 jar 已驗證。 警告: 此 jar 包含簽名者證書將在六個月內過期的條目。
代碼簽名認證的用途主要是對發布的軟件做驗證,支持 Sun Java .jar (Java Applet) 文件(J2SE)和 J2ME MIDlet Suite 文件。
在中,我們模擬了一個基於RSA非對稱加密網絡的安全通信。現在我們深度了解一下現有的安全網絡通信——SSL。
我們需要構建一個由CA機構簽發的有效證書,這里我們使用上文中生成的自簽名證書zlex.cer
這里,我們將證書導入到我們的密鑰庫。
keytool -import -alias www.zlex.org -file d:/zlex.cer -keystore d:/zlex.keystore
其中
-import表示導入
-alias指定別名,這里是www.zlex.org
-file指定算法,這里是d:/zlex.cer
-keystore指定存儲位置,這里是d:/zlex.keystore
在這里我使用的密碼為654321
控制台輸出:
輸入keystore密碼: 再次輸入新密碼: 所有者:CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 簽發人:CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 序列號:4a1e48df 有效期: Thu May 28 16:18:39 CST 2009 至Wed Aug 26 16:18:39 CST 2009 證書指紋: MD5:19:CA:E6:36:E2:DF:AD:96:31:97:2F:A9:AD:FC:37:6A SHA1:49:88:30:59:29:45:F1:69:CA:97:A9:6D:8A:CF:08:D2:C3:D5:C0:C4 簽名算法名稱:SHA1withRSA 版本: 3 信任這個認證? [否]: y 認證已添加至keystore中
OK,最復雜的准備工作已經完成。
接下來我們將域名www.zlex.org定位到本機上。打開C:\Windows\System32\drivers\etc\hosts文件,將www.zlex.org綁定在本機上。在文件末尾追加127.0.0.1 www.zlex.org。現在通過地址欄訪問http://www.zlex.org,或者通過ping命令,如果能夠定位到本機,域名映射就搞定了。
現在,配置tomcat。先將zlex.keystore拷貝到tomcat的conf目錄下,然后配置server.xml。將如下內容加入配置文件
<Connector SSLEnabled="true" URIEncoding="UTF-8" clientAuth="false" keystoreFile="conf/zlex.keystore" keystorePass="123456" maxThreads="150" port="443" protocol="HTTP/1.1" scheme="https" secure="true" sslProtocol="TLS" />
注意clientAuth="false"測試階段,置為false,正式使用時建議使用true。現在啟動tomcat,訪問https://www.zlex.org/。
顯然,證書未能通過認證,這個時候你可以選擇安裝證書(上文中的zlex.cer文件就是證書),作為受信任的根證書頒發機構導入,再次重啟瀏覽器(IE,其他瀏覽器對於域名www.zlex.org不支持本地方式訪問),訪問https://www.zlex.org/,你會看到地址欄中會有個小鎖,就說明安裝成功。所有的瀏覽器聯網操作已經在RSA加密解密系統的保護之下了。但似乎我們感受不到。
這個時候很多人開始懷疑,如果我們要手工做一個這樣的https的訪問是不是需要把瀏覽器的這些個功能都實現呢?不需要!
接着上篇內容,給出如下代碼實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
|
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。
給出相應測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import
static
org.junit.Assert.*;
import
java.io.DataInputStream;
import
java.io.InputStream;
import
java.net.URL;
import
javax.net.ssl.HttpsURLConnection;
import
org.junit.Test;
/**
*
* @author 梁棟
* @version 1.0
* @since 1.0
*/
public
class
CertificateCoderTest {
private
String password =
"123456"
;
private
String alias =
"www.zlex.org"
;
private
String certificatePath =
"d:/zlex.cer"
;
private
String keyStorePath =
"d:/zlex.keystore"
;
private
String clientKeyStorePath =
"d:/zlex-client.keystore"
;
private
String clientPassword =
"654321"
;
@Test
public
void
test()
throws
Exception {
System.err.println(
"公鑰加密——私鑰解密"
);
String inputStr =
"Ceritifcate"
;
byte
[] data = inputStr.getBytes();
byte
[] encrypt = CertificateCoder.encryptByPublicKey(data,
certificatePath);
byte
[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
keyStorePath, alias, password);
String outputStr =
new
String(decrypt);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
// 驗證數據一致
assertArrayEquals(data, decrypt);
// 驗證證書有效
assertTrue(CertificateCoder.verifyCertificate(certificatePath));
}
@Test
public
void
testSign()
throws
Exception {
System.err.println(
"私鑰加密——公鑰解密"
);
String inputStr =
"sign"
;
byte
[] data = inputStr.getBytes();
byte
[] encodedData = CertificateCoder.encryptByPrivateKey(data,
keyStorePath, alias, password);
byte
[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
certificatePath);
String outputStr =
new
String(decodedData);
System.err.println(
"加密前: "
+ inputStr +
"\n\r"
+
"解密后: "
+ outputStr);
assertEquals(inputStr, outputStr);
System.err.println(
"私鑰簽名——公鑰驗證簽名"
);
// 產生簽名
String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
password);
System.err.println(
"簽名:\r"
+ sign);
// 驗證簽名
boolean
status = CertificateCoder.verify(encodedData, sign,
certificatePath);
System.err.println(
"狀態:\r"
+ status);
assertTrue(status);
}
@Test
public
void
testHttps()
throws
Exception {
URL url =
new
URL(
"https://www.zlex.org/examples/"
);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoInput(
true
);
conn.setDoOutput(
true
);
CertificateCoder.configSSLSocketFactory(conn, clientPassword,
clientKeyStorePath, clientKeyStorePath);
InputStream is = conn.getInputStream();
int
length = conn.getContentLength();
DataInputStream dis =
new
DataInputStream(is);
byte
[] data =
new
byte
[length];
dis.readFully(data);
dis.close();
System.err.println(
new
String(data));
conn.disconnect();
}
}
|
注意testHttps方法,幾乎和我們往常做HTTP訪問沒有差別,我們來看控制台輸出:
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE> <META http-equiv=Content-Type content="text/html"> </HEAD> <BODY> <P> <H3>Apache Tomcat Examples</H3> <P></P> <ul> <li><a href="http://javaeye.shaduwang.com/?snowolf/blog/servlets">Servlets examples</a></li> <li><a href="http://javaeye.shaduwang.com/?snowolf/blog/jsp">JSP Examples</a></li> </ul> </BODY></HTML>
通過瀏覽器直接訪問https://www.zlex.org/examples/你 也會獲得上述內容。也就是說應用甲方作為服務器構建tomcat服務,乙方可以通過上述方式訪問甲方受保護的SSL應用,並且不需要考慮具體的加密解密問 題。甲乙雙方可以經過相應配置,通過雙方的tomcat配置有效的SSL服務,簡化上述代碼實現,完全通過證書配置完成SSL雙向認證!
我們使用自簽名證書完成了認證。接下來,我們使用第三方CA簽名機構完成證書簽名。
這里我們使用thawte提供的測試用21天免費ca證書。
1.要在該網站上注明你的域名,這里使用www.zlex.org作為測試用域名(請勿使用該域名作為你的域名地址,該域名受法律保護!請使用其他非注冊域名!)。
2.如果域名有效,你會收到郵件要求你訪問https://www.thawte.com/cgi/server/try.exe獲得ca證書。
3.復述密鑰庫的創建。
keytool -genkey -validity 36000 -alias www.zlex.org -keyalg RSA -keystore d:\zlex.keystore
在這里我使用的密碼為 123456
控制台輸出:
輸入keystore密碼: 再次輸入新密碼: 您的名字與姓氏是什么? [Unknown]: www.zlex.org 您的組織單位名稱是什么? [Unknown]: zlex 您的組織名稱是什么? [Unknown]: zlex 您所在的城市或區域名稱是什么? [Unknown]: BJ 您所在的州或省份名稱是什么? [Unknown]: BJ 該單位的兩字母國家代碼是什么 [Unknown]: CN CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 正確嗎? [否]: Y 輸入<tomcat>的主密碼 (如果和 keystore 密碼相同,按回車): 再次輸入新密碼:
4.通過如下命令,從zlex.keystore中導出CA證書申請。
keytool -certreq -alias www.zlex.org -file d:\zlex.csr -keystore d:\zlex.keystore -v
你會獲得zlex.csr文件,可以用記事本打開,內容如下格式:
-----BEGIN NEW CERTIFICATE REQUEST----- MIIBnDCCAQUCAQAwXDELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkJKMQswCQYDVQQHEwJCSjENMAsG A1UEChMEemxleDENMAsGA1UECxMEemxleDEVMBMGA1UEAxMMd3d3LnpsZXgub3JnMIGfMA0GCSqG SIb3DQEBAQUAA4GNADCBiQKBgQCR6DXU9Mp+mCKO7cv9JPsj0n1Ec/GpM09qvhpgX3FNad/ZWSDc vU77YXZSoF9hQp3w1LC+eeKgd2MlVpXTvbVwBNVd2HiQPp37ic6BUUjSaX8LHtCl7l0BIEye9qQ2 j8G0kak7e8ZA0s7nb3Ymq/K8BV7v0MQIdhIc1bifK9ZDewIDAQABoAAwDQYJKoZIhvcNAQEFBQAD gYEAMA1r2fbZPtNx37U9TRwadCH2TZZecwKJS/hskNm6ryPKIAp9APWwAyj8WJHRBz5SpZM4zmYO oMCI8BcnY2A4JP+R7/SwXTdH/xcg7NVghd9A2SCgqMpF7KMfc5dE3iygdiPu+UhY200Dvpjx8gmJ 1UbH3+nqMUyCrZgURFslOUY= -----END NEW CERTIFICATE REQUEST-----
5.將上述文件內容拷貝到https://www.thawte.com/cgi/server/try.exe中,點擊next,獲得回應內容,這里是p7b格式。
內容如下:
-----BEGIN PKCS7----- MIIF3AYJKoZIhvcNAQcCoIIFzTCCBckCAQExADALBgkqhkiG9w0BBwGgggWxMIID EDCCAnmgAwIBAgIQA/mx/pKoaB+KGX2hveFU9zANBgkqhkiG9w0BAQUFADCBhzEL MAkGA1UEBhMCWkExIjAgBgNVBAgTGUZPUiBURVNUSU5HIFBVUlBPU0VTIE9OTFkx HTAbBgNVBAoTFFRoYXd0ZSBDZXJ0aWZpY2F0aW9uMRcwFQYDVQQLEw5URVNUIFRF U1QgVEVTVDEcMBoGA1UEAxMTVGhhd3RlIFRlc3QgQ0EgUm9vdDAeFw0wOTA1Mjgw MDIxMzlaFw0wOTA2MTgwMDIxMzlaMFwxCzAJBgNVBAYTAkNOMQswCQYDVQQIEwJC SjELMAkGA1UEBxMCQkoxDTALBgNVBAoTBHpsZXgxDTALBgNVBAsTBHpsZXgxFTAT BgNVBAMTDHd3dy56bGV4Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA keg11PTKfpgiju3L/ST7I9J9RHPxqTNPar4aYF9xTWnf2Vkg3L1O+2F2UqBfYUKd 8NSwvnnioHdjJVaV0721cATVXdh4kD6d+4nOgVFI0ml/Cx7Qpe5dASBMnvakNo/B tJGpO3vGQNLO5292JqvyvAVe79DECHYSHNW4nyvWQ3sCAwEAAaOBpjCBozAMBgNV HRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBABgNVHR8E OTA3MDWgM6Axhi9odHRwOi8vY3JsLnRoYXd0ZS5jb20vVGhhd3RlUHJlbWl1bVNl cnZlckNBLmNybDAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9v Y3NwLnRoYXd0ZS5jb20wDQYJKoZIhvcNAQEFBQADgYEATPuxZbtJJSPmXvfrr1yz xqM06IwTZ6UU0lZRG7I0WufMjNMKdpn8hklUhE17mxAhGSpewLVVeLR7uzBLFkuC X7wMXxhoYdJZtNai72izU6Rd1oknao7diahvRxPK4IuQ7y2oZ511/4T4vgY6iRAj q4q76HhPJrVRL/sduaiu+gYwggKZMIICAqADAgECAgEAMA0GCSqGSIb3DQEBBAUA MIGHMQswCQYDVQQGEwJaQTEiMCAGA1UECBMZRk9SIFRFU1RJTkcgUFVSUE9TRVMg T05MWTEdMBsGA1UEChMUVGhhd3RlIENlcnRpZmljYXRpb24xFzAVBgNVBAsTDlRF U1QgVEVTVCBURVNUMRwwGgYDVQQDExNUaGF3dGUgVGVzdCBDQSBSb290MB4XDTk2 MDgwMTAwMDAwMFoXDTIwMTIzMTIxNTk1OVowgYcxCzAJBgNVBAYTAlpBMSIwIAYD VQQIExlGT1IgVEVTVElORyBQVVJQT1NFUyBPTkxZMR0wGwYDVQQKExRUaGF3dGUg Q2VydGlmaWNhdGlvbjEXMBUGA1UECxMOVEVTVCBURVNUIFRFU1QxHDAaBgNVBAMT E1RoYXd0ZSBUZXN0IENBIFJvb3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB ALV9kG+Os6x/DOhm+tKUQfzVMWGhE95sFmEtkMMTX2Zi4n6i6BvzoReJ5njzt1LF cqu4EUk9Ji20egKKfmqRzmQFLP7+1niSdfJEUE7cKY40QoI99270PTrLjJeaMcCl +AYl+kD+RL5BtuKKU3PurYcsCsre6aTvjMcqpTJOGeSPAgMBAAGjEzARMA8GA1Ud EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAgozj7BkD9O8si2V0v+EZ/t7E fz/LC8y6mD7IBUziHy5/53ymGAGLtyhXHvX+UIE6UWbHro3IqVkrmY5uC93Z2Wew A/6edK3KFUcUikrLeewM7gmqsiASEKx2mKRKlu12jXyNS5tXrPWRDvUKtFC1uL9a 12rFAQS2BkIk7aU+ghYxAA== -----END PKCS7-----
將其存儲為zlex.p7b
6.將由CA簽發的證書導入密鑰庫。
keytool -import -trustcacerts -alias www.zlex.org -file d:\zlex.p7b -keystore d:\zlex.keystore -v
在這里我使用的密碼為 123456
控制台輸出:
輸入keystore密碼: 回復中的最高級認證: 所有者:CN=Thawte Test CA Root, OU=TEST TEST TEST, O=Thawte Certification, ST=FOR TESTING PURPOSES ONLY, C=ZA 簽發人:CN=Thawte Test CA Root, OU=TEST TEST TEST, O=Thawte Certification, ST=FOR TESTING PURPOSES ONLY, C=ZA 序列號:0 有效期: Thu Aug 01 08:00:00 CST 1996 至Fri Jan 01 05:59:59 CST 2021 證書指紋: MD5:5E:E0:0E:1D:17:B7:CA:A5:7D:36:D6:02:DF:4D:26:A4 SHA1:39:C6:9D:27:AF:DC:EB:47:D6:33:36:6A:B2:05:F1:47:A9:B4:DA:EA 簽名算法名稱:MD5withRSA 版本: 3 擴展: #1: ObjectId: 2.5.29.19 Criticality=true BasicConstraints:[ CA:true PathLen:2147483647 ] ... 是不可信的。 還是要安裝回復? [否]: Y 認證回復已安裝在 keystore中 [正在存儲 d:\zlex.keystore]
7.域名定位
將域名www.zlex.org定位到本機上。打開C:\Windows\System32\drivers\etc\hosts文件,將 www.zlex.org綁定在本機上。在文件末尾追加127.0.0.1 www.zlex.org。現在通過地址欄訪問http://www.zlex.org,或者通過ping命令,如果能夠定位到本機,域名映射就搞定 了。
8.配置server.xml
<Connector keystoreFile="conf/zlex.keystore" keystorePass="123456" truststoreFile="conf/zlex.keystore" truststorePass="123456" SSLEnabled="true" URIEncoding="UTF-8" clientAuth="false" maxThreads="150" port="443" protocol="HTTP/1.1" scheme="https" secure="true" sslProtocol="TLS" />
將文件zlex.keystore拷貝到tomcat的conf目錄下,重新啟動tomcat。訪問https://www.zlex.org/,我們發現聯網有些遲鈍。大約5秒鍾后,網頁正常顯示,同時有如下圖所示:
瀏覽器驗證了該CA機構的有效性。
打開證書,如下圖所示:
調整測試類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
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));
}
}
|
再次執行,驗證通過!