使用openssl生成RSA密钥对

生成秘钥对

生成RSA私钥,以X509编码,指定生成的密钥的位数为2048位,该步生成的私钥只是临时文件,以便第二步将私钥转为PKCS#8编码。

1
openssl genrsa -out rsa_private_key_2048.pem 2048

将上一步生成的RSA私钥转换成PKCS#8编码,作为最终使用的私钥。

1
openssl pkcs8 -topk8 -in rsa_private_key_2048.pem -out pkcs8_rsa_private_key_2048.pem -nocrypt

导出RSA公钥,以X509编码,作为最终交换的公钥。

1
openssl rsa -in rsa_private_key_2048.pem -out rsa_public_key_2048.pem -pubout

注:一般Linux系统都装有openssl工具,在windows下可以安装OpenSSL工具包。

用法

加密工具

CryptoUtil.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
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
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import lombok.extern.log4j.Log4j2;
import java.io.*;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
* @author kangyonggan
* @since 2016/12/27
*/
@Log4j2
public class CryptoUtil {

/**
* 获取公钥对象
*
* @param inputStream 公钥输入流
* @param keyAlgorithm 密钥算法
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(InputStream inputStream, String keyAlgorithm) throws Exception {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String readLine;
while ((readLine = br.readLine()) != null) {
if (readLine.charAt(0) == '-') {
continue;
} else {
sb.append(readLine);
sb.append('\r');
}
}
X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64.decodeBase64(sb.toString()));
KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
PublicKey publicKey = keyFactory.generatePublic(pubX509);

return publicKey;
} catch (FileNotFoundException e) {
throw new Exception("公钥路径文件不存在");
} catch (IOException e) {
throw new Exception("读取公钥异常");
} catch (NoSuchAlgorithmException e) {
throw new Exception(String.format("生成密钥工厂时没有[%s]此类算法", keyAlgorithm));
} catch (InvalidKeySpecException e) {
throw new Exception("生成公钥对象异常");
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
log.error(e);
}
}
}

/**
* 获取私钥对象
*
* @param inputStream 私钥输入流
* @param keyAlgorithm 密钥算法
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(InputStream inputStream, String keyAlgorithm) throws Exception {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String readLine;
while ((readLine = br.readLine()) != null) {
if (readLine.charAt(0) == '-') {
continue;
} else {
sb.append(readLine);
sb.append('\r');
}
}
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decodeBase64(sb.toString()));
KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
PrivateKey privateKey = keyFactory.generatePrivate(priPKCS8);

return privateKey;
} catch (FileNotFoundException e) {
throw new Exception("私钥路径文件不存在");
} catch (IOException e) {
throw new Exception("读取私钥异常");
} catch (NoSuchAlgorithmException e) {
throw new Exception("生成私钥对象异常");
} catch (InvalidKeySpecException e) {
throw new Exception("生成私钥对象异常");
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
log.error(e);
}
}
}

/**
* 数字签名函数入口
*
* @param plainBytes 待签名明文字节数组
* @param privateKey 签名使用私钥
* @param signAlgorithm 签名算法
* @return 签名后的字节数组
* @throws Exception
*/
public static byte[] digitalSign(byte[] plainBytes, PrivateKey privateKey, String signAlgorithm) throws Exception {
try {
Signature signature = Signature.getInstance(signAlgorithm);
signature.initSign(privateKey);
signature.update(plainBytes);
byte[] signBytes = signature.sign();

return signBytes;
} catch (NoSuchAlgorithmException e) {
throw new Exception(String.format("数字签名时没有[%s]此类算法", signAlgorithm));
} catch (InvalidKeyException e) {
throw new Exception("数字签名时私钥无效");
} catch (SignatureException e) {
throw new Exception("数字签名时出现异常");
}
}

/**
* 验证数字签名函数入口
*
* @param plainBytes 待验签明文字节数组
* @param signBytes 待验签签名后字节数组
* @param publicKey 验签使用公钥
* @param signAlgorithm 签名算法
* @return 验签是否通过
* @throws Exception
*/
public static boolean verifyDigitalSign(byte[] plainBytes, byte[] signBytes, PublicKey publicKey, String signAlgorithm) throws Exception {
boolean isValid;
try {
Signature signature = Signature.getInstance(signAlgorithm);
signature.initVerify(publicKey);
signature.update(plainBytes);
isValid = signature.verify(signBytes);
return isValid;
} catch (NoSuchAlgorithmException e) {
throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm));
} catch (InvalidKeyException e) {
throw new Exception("验证数字签名时公钥无效");
} catch (SignatureException e) {
throw new Exception("验证数字签名时出现异常");
}
}

/**
* 加密
*
* @param plainBytes 明文字节数组
* @param publicKey 公钥
* @param keyLength 密钥bit长度
* @param reserveSize padding填充字节数,预留11字节
* @param cipherAlgorithm 加解密算法,一般为RSA/ECB/PKCS1Padding
* @return 加密后字节数组,不经base64编码
* @throws Exception
*/
public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
int keyByteSize = keyLength / 8; // 密钥字节数
int encryptBlockSize = keyByteSize - reserveSize; // 加密块大小=密钥字节数-padding填充字节数
int nBlock = plainBytes.length / encryptBlockSize;// 计算分段加密的block数,向上取整
if ((plainBytes.length % encryptBlockSize) != 0) { // 余数非0,block数再加1
nBlock += 1;
}

try {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

// 输出buffer,大小为nBlock个keyByteSize
ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
// 分段加密
for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
int inputLen = plainBytes.length - offset;
if (inputLen > encryptBlockSize) {
inputLen = encryptBlockSize;
}

// 得到分段加密结果
byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
// 追加结果到输出buffer中
outbuf.write(encryptedBlock);
}

outbuf.flush();
outbuf.close();
return outbuf.toByteArray();
} catch (NoSuchAlgorithmException e) {
throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
} catch (NoSuchPaddingException e) {
throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
} catch (InvalidKeyException e) {
throw new Exception("无效密钥");
} catch (IllegalBlockSizeException e) {
throw new Exception("加密块大小不合法");
} catch (BadPaddingException e) {
throw new Exception("错误填充模式");
} catch (IOException e) {
throw new Exception("字节输出流异常");
}
}

/**
* RSA解密
*
* @param encryptedBytes 加密后字节数组
* @param privateKey 私钥
* @param keyLength 密钥bit长度
* @param reserveSize padding填充字节数,预留11字节
* @param cipherAlgorithm 加解密算法,一般为RSA/ECB/PKCS1Padding
* @return 解密后字节数组,不经base64编码
* @throws Exception
*/
public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
int keyByteSize = keyLength / 8; // 密钥字节数
int decryptBlockSize = keyByteSize - reserveSize; // 解密块大小=密钥字节数-padding填充字节数
int nBlock = encryptedBytes.length / keyByteSize;// 计算分段解密的block数,理论上能整除

try {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, privateKey);

// 输出buffer,大小为nBlock个decryptBlockSize
ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
// 分段解密
for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
// block大小: decryptBlock 或 剩余字节数
int inputLen = encryptedBytes.length - offset;
if (inputLen > keyByteSize) {
inputLen = keyByteSize;
}

// 得到分段解密结果
byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
// 追加结果到输出buffer中
outbuf.write(decryptedBlock);
}

outbuf.flush();
outbuf.close();
return outbuf.toByteArray();
} catch (NoSuchAlgorithmException e) {
throw new Exception(String.format("没有[%s]此类解密算法", cipherAlgorithm));
} catch (NoSuchPaddingException e) {
throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
} catch (InvalidKeyException e) {
throw new Exception("无效密钥");
} catch (IllegalBlockSizeException e) {
throw new Exception("解密块大小不合法");
} catch (BadPaddingException e) {
throw new Exception("错误填充模式");
} catch (IOException e) {
throw new Exception("字节输出流异常");
}
}

/**
* 字符数组转16进制字符串
*
* @param bytes
* @return
*/
public static String bytes2string(byte[] bytes, int radix) {
// 2个16进制字符占用1个字节,8个二进制字符占用1个字节
int size = 2;
if (radix == 2) {
size = 8;
}
StringBuilder sb = new StringBuilder(bytes.length * size);
for (int i = 0; i < bytes.length; i++) {
int integer = bytes[i];
while (integer < 0) {
integer = integer + 256;
}
String str = Integer.toString(integer, radix);
sb.append(StringUtils.leftPad(str.toUpperCase(), size, "0"));
}
return sb.toString();
}
}

签名

1
byte[] signBytes = CryptoUtil.digitalSign(xml.getBytes("UTF-8"), privateKey, "SHA1WithRSA");

加密

1
byte[] encryptedBytes = CryptoUtil.encrypt(xmlBytes, publicKey, 2048, 11, "RSA/ECB/PKCS1Padding");

解密

1
byte[] xmlBytes = CryptoUtil.decrypt(encryptedBytes, privateKey, 2048, 11, "RSA/ECB/PKCS1Padding");

验签

1
boolean isValid = CryptoUtil.verifyDigitalSign(xmlBytes, signBytes, publicKey, "SHA1WithRSA");

加载私钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 加载私钥
*/
private void loadPrivateKey() {
String privateKeyPath = null;
InputStream inputStream = null;
try {
// TODO
privateKeyPath = "私钥路径";
inputStream = new FileInputStream(privateKeyPath);
privateKey = CryptoUtil.getPrivateKey(inputStream, "RSA");
} catch (Exception e) {
log.error("无法加载己方私钥[{}]", privateKeyPath);
log.error(e.getMessage(), e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
log.error(e);
}
}
}

加载公钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 加载公钥
*/
private void loadPublicKey() {
String publicKeyPath = null;
InputStream inputStream = null;
try {
// TODO
publicKeyPath = "公钥路径";
inputStream = new FileInputStream(publicKeyPath);
publicKey = CryptoUtil.getPublicKey(inputStream, "RSA");
} catch (Exception e) {
log.error("无法加载对方公钥[{}]", publicKeyPath);
log.error(e.getMessage(), e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
log.error(e);
}
}
}