在构建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,最复杂的准备工作已经完成。