Python
RSA
加密是一种非对称加密,通常使用公钥加密,私钥解密,私钥签名,公钥验签。而通常 公钥比较短,私钥比较长。
在公开密钥密码体制中,加密密钥(即公开密钥)PK
是公开信息,而解密密钥(即秘密密钥)SK
是需要保密的。 RSA
算法通常是先生成一对 RSA
密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开。
生成公钥 && 私钥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto import Random
from Crypto.PublicKey import RSA
# 伪随机数生成器
random_generator = Random . new () . read
# rsa算法生成实例
rsa = RSA . generate ( 1024 , random_generator )
# 私钥的生成
private_pem = rsa . exportKey ()
with open ( "private.pem" , "wb" ) as f :
f . write ( private_pem )
# 公钥的生成
public_pem = rsa . publickey () . exportKey ()
with open ( "public.pem" , "wb" ) as f :
f . write ( public_pem )
运行完以后会生成两个文件,private.pem
和 public.pem
。
生成的公钥私钥格式是固定的,秘钥中间无空格无换行,秘钥末尾也空格无换行。
公钥长这样,一般比较短。
1
2
3
4
5
6
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDepM9gd1VPEbPUAvzRsBcmQyaz
DDMTe8/flREAtIX2aR2wKO2Yoj5DQIgjputv3nfoZcQtZIIe7S5Jmi+9nKAvaF62
cbgl8uzxL/ghCSOuAqXyu5O9XNm1N7DHHt4wv01gN40pfTYuOATXGdWegacnTwJi
JcB2FzSx1u4+BsqQvwIDAQAB
-----END PUBLIC KEY-----
私钥长这样,一般比较长。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDepM9gd1VPEbPUAvzRsBcmQyazDDMTe8/flREAtIX2aR2wKO2Y
oj5DQIgjputv3nfoZcQtZIIe7S5Jmi+9nKAvaF62cbgl8uzxL/ghCSOuAqXyu5O9
XNm1N7DHHt4wv01gN40pfTYuOATXGdWegacnTwJiJcB2FzSx1u4+BsqQvwIDAQAB
AoGAFEZN3CsBycGC7ruW6kFu2j0jOQrTW+LaezHH6piF2nqYCYKwoEWmBdnKi/SX
XAiWQsOBqeCNOOPAY1N3JX0t2PfZP5D1roD9Q39AscDAYFy9JOu300HyP/BLjYHX
d3Rc2zl+5ODhu2zqNji5wSJLM4Nqe2lz7nnJUh21w5AHrwECQQDos2zztkl1Otlc
0d4B6S6c0OY199NEYU45lJVexZXIh+J9QSG9qbhTvkyYURUxPGv7UkBSyMTStBZi
/JZYurWJAkEA9O+aCnqtiNvEPglkmKN1OgOl4Qe2M6/p+NAbNp4KU+1Q2A+0BqSj
7wPVMIg7PgsVS4m/grIIh22Q2z3IYAZKBwJALNPLzFM0aXnxv5jKTNHv4dO4VXMt
ZsHcAOmnsL8dcKkEr55pcpEMak2BkeIk0/xQzPR2Ybw9dl2s5lrEV8l4CQJAHOLZ
Bt1a8+X/KdXPwFSesZ3WhUh0i3n2nPpmCzXuP+GgBCst7w6hqsGH9fZLDlEeTM9M
/6vhZ59bUw/hPrnfJwJAWMszZzuiNp5isHfs8Ww4jIs7XbffVuRW1aHi6xto1oAp
5EyEAoBkrlAbQWqPrLQwCS21LAdiNyKpijd2fm7vww==
-----END RSA PRIVATE KEY-----
使用公钥加密
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
# rsa加密
def rsa_encrypt ( message ):
public_key = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDepM9gd1VPEbPUAvzRsBcmQyaz
DDMTe8/flREAtIX2aR2wKO2Yoj5DQIgjputv3nfoZcQtZIIe7S5Jmi+9nKAvaF62
cbgl8uzxL/ghCSOuAqXyu5O9XNm1N7DHHt4wv01gN40pfTYuOATXGdWegacnTwJi
JcB2FzSx1u4+BsqQvwIDAQAB
-----END PUBLIC KEY-----"""
rsa_key = RSA . importKey ( public_key )
cipher = Cipher_pkcs1_v1_5 . new ( rsa_key ) # 创建用于执行 pkcs1_v1_5 加密或解密的密码
cipher_text = base64 . b64encode ( cipher . encrypt ( message . encode ( "utf-8" )))
return cipher_text . decode ( "utf-8" )
if __name__ == "__main__" :
message = "Hello, 你好, 这是一段RSA加密测试代码!"
result = rsa_encrypt ( message )
print ( result )
多次运行对比结果,发现每次使用公钥加密后的结果都不一致,跟对数据的 padding
即填充有关。
使用私钥解密
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
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
# rsa解密
def rsa_decrypt ( cipher_text ):
private_key = """-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDepM9gd1VPEbPUAvzRsBcmQyazDDMTe8/flREAtIX2aR2wKO2Y
oj5DQIgjputv3nfoZcQtZIIe7S5Jmi+9nKAvaF62cbgl8uzxL/ghCSOuAqXyu5O9
XNm1N7DHHt4wv01gN40pfTYuOATXGdWegacnTwJiJcB2FzSx1u4+BsqQvwIDAQAB
AoGAFEZN3CsBycGC7ruW6kFu2j0jOQrTW+LaezHH6piF2nqYCYKwoEWmBdnKi/SX
XAiWQsOBqeCNOOPAY1N3JX0t2PfZP5D1roD9Q39AscDAYFy9JOu300HyP/BLjYHX
d3Rc2zl+5ODhu2zqNji5wSJLM4Nqe2lz7nnJUh21w5AHrwECQQDos2zztkl1Otlc
0d4B6S6c0OY199NEYU45lJVexZXIh+J9QSG9qbhTvkyYURUxPGv7UkBSyMTStBZi
/JZYurWJAkEA9O+aCnqtiNvEPglkmKN1OgOl4Qe2M6/p+NAbNp4KU+1Q2A+0BqSj
7wPVMIg7PgsVS4m/grIIh22Q2z3IYAZKBwJALNPLzFM0aXnxv5jKTNHv4dO4VXMt
ZsHcAOmnsL8dcKkEr55pcpEMak2BkeIk0/xQzPR2Ybw9dl2s5lrEV8l4CQJAHOLZ
Bt1a8+X/KdXPwFSesZ3WhUh0i3n2nPpmCzXuP+GgBCst7w6hqsGH9fZLDlEeTM9M
/6vhZ59bUw/hPrnfJwJAWMszZzuiNp5isHfs8Ww4jIs7XbffVuRW1aHi6xto1oAp
5EyEAoBkrlAbQWqPrLQwCS21LAdiNyKpijd2fm7vww==
-----END RSA PRIVATE KEY-----"""
decode_text = base64 . b64decode ( cipher_text ) # 对传入的 Base64 编码的密文进行解码,得到二进制的加密数据
cipher = Cipher_pkcs1_v1_5 . new ( RSA . importKey ( private_key )) # 首先通过 RSA.importKey(private_key) 导入私钥,然后创建一个 PKCS#1 v1.5 的解密器对象cipher
decrypt_text = cipher . decrypt ( decode_text , b "rsa" ) # 使用解密器对象 cipher 对解码后的密文 decode_text 进行解密。b"rsa" 参数是解密失败时返回的默认值(在此上下文中不会用到)。
return decrypt_text . decode ( "utf-8" ) # 将解密后的数据转换为UTF-8编码的字符串并返回
if __name__ == "__main__" :
# 加密文本
result = "QCC/XrMziFHxZu4RRUIOEojNkG153vx/EVOwxXSgQ425lsbObd36i0w8ooLBqwsJJZ8KWv0XvVwZ7OytULQ11c8RvGait/hazkytkZ3dL2qhtRr9ac6mTR0hTdiNVrR6jUGevJw0qa1WgoEH/nTAS3/wMamZ6eXA0Bf05o0mxXg="
message = rsa_decrypt ( result )
print ( message )
运行结果,可以看到,和加密前的数据一模一样。
使用私钥加签
私钥签名的流程
1.生成消息的 SHA-1
哈希值:
对原始消息进行 SHA-1
哈希处理,得到消息的摘要(哈希值)。这个摘要是一个固定长度的唯一表示,代表了消息的内容。
2.使用私钥创建签名器对象:
导入私钥,并用它创建一个签名器对象(通常使用 PKCS#1
v1.5
标准)。
3.对 SHA-1
哈希值进行签名:
使用签名器对象对消息的SHA-1哈希值进行签名。签名的过程实际上是对哈希值进行加密,生成签名。
4.将签名进行 Base64
编码:
为了便于传输或存储,将签名后的数据进行 Base64
编码,得到最终的签名字符串。
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
import base64 # 用于将生成的签名进行Base64编码
from Crypto.Hash import SHA # 用于生成消息的SHA-1哈希值
from Crypto.PublicKey import RSA # 用于处理RSA密钥
from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5 # 用于执行PKCS#1 v1.5标准的签名操作
# 私钥加签名
def rsa_encrypt ( message ):
private_key = """-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDepM9gd1VPEbPUAvzRsBcmQyazDDMTe8/flREAtIX2aR2wKO2Y
oj5DQIgjputv3nfoZcQtZIIe7S5Jmi+9nKAvaF62cbgl8uzxL/ghCSOuAqXyu5O9
XNm1N7DHHt4wv01gN40pfTYuOATXGdWegacnTwJiJcB2FzSx1u4+BsqQvwIDAQAB
AoGAFEZN3CsBycGC7ruW6kFu2j0jOQrTW+LaezHH6piF2nqYCYKwoEWmBdnKi/SX
XAiWQsOBqeCNOOPAY1N3JX0t2PfZP5D1roD9Q39AscDAYFy9JOu300HyP/BLjYHX
d3Rc2zl+5ODhu2zqNji5wSJLM4Nqe2lz7nnJUh21w5AHrwECQQDos2zztkl1Otlc
0d4B6S6c0OY199NEYU45lJVexZXIh+J9QSG9qbhTvkyYURUxPGv7UkBSyMTStBZi
/JZYurWJAkEA9O+aCnqtiNvEPglkmKN1OgOl4Qe2M6/p+NAbNp4KU+1Q2A+0BqSj
7wPVMIg7PgsVS4m/grIIh22Q2z3IYAZKBwJALNPLzFM0aXnxv5jKTNHv4dO4VXMt
ZsHcAOmnsL8dcKkEr55pcpEMak2BkeIk0/xQzPR2Ybw9dl2s5lrEV8l4CQJAHOLZ
Bt1a8+X/KdXPwFSesZ3WhUh0i3n2nPpmCzXuP+GgBCst7w6hqsGH9fZLDlEeTM9M
/6vhZ59bUw/hPrnfJwJAWMszZzuiNp5isHfs8Ww4jIs7XbffVuRW1aHi6xto1oAp
5EyEAoBkrlAbQWqPrLQwCS21LAdiNyKpijd2fm7vww==
-----END RSA PRIVATE KEY-----"""
rsa_key = RSA . importKey ( private_key ) # 使用 RSA.importKey() 函数将私钥字符串导入为一个RSA密钥对象
signer = Signature_pkcs1_v1_5 . new ( rsa_key ) # 创建一个 PKCS#1 v1.5 签名器对象,它将使用导入的私钥进行签名
digest = SHA . new () # 创建一个 SHA-1 哈希对象
digest . update ( message . encode ( "utf-8" )) # 将消息转换为 UTF-8 编码的字节串,并生成其 SHA-1 哈希值
sign = signer . sign ( digest ) # 使用私钥对消息的哈希值进行签名,生成二进制签名数据
signature = base64 . b64encode ( sign ) # 将生成的二进制签名数据进行 Base64 编码,以便于传输或存储
return signature . decode ( "utf-8" ) # 将 Base64 编码的签名数据转换为字符串并返回
if __name__ == "__main__" :
message = "Hello, 你好, 这是一段RSA加密测试代码!"
signature = rsa_encrypt ( message )
print ( signature )
多次运行,会发现用私钥加签,每次签名是一致的。
使用公钥验签
公钥验证签名的流程
1.使用公钥创建验证器对象:
导入公钥,并用它创建一个验证器对象(同样使用 PKCS#1
v1.5
标准)。
2.生成待验证消息的 SHA-1
哈希值:
对待验证的消息进行 SHA-1
哈希处理,得到消息的摘要(哈希值)。
3.解码签名字符串:
对私钥签名生成的 Base64
编码的签名字符串进行解码,得到原始的签名数据。
4.使用验证器对象验证签名:
使用公钥验证器对象,验证解码后的签名数据和生成的 SHA-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
import base64
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
def check_verify ():
public_key = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDepM9gd1VPEbPUAvzRsBcmQyaz
DDMTe8/flREAtIX2aR2wKO2Yoj5DQIgjputv3nfoZcQtZIIe7S5Jmi+9nKAvaF62
cbgl8uzxL/ghCSOuAqXyu5O9XNm1N7DHHt4wv01gN40pfTYuOATXGdWegacnTwJi
JcB2FzSx1u4+BsqQvwIDAQAB
-----END PUBLIC KEY-----"""
message_verify = "Hello, 你好, 这是一段RSA加密测试代码!"
signature = "R/MqJniLAmGQaUQBJ4+KC+bBLzgvMfwhTZjZVwID+QLmLkT+NgHuGe7jSgGEQUXf17LbPshB5v4Hm+MCtGHTFPgQSnBOuzK9epbpqmrnDH8OFrJhXwnO+bGr87CfiZR9KWoaPD2Mi8EJDmHfZKMX3oS63/V1wTqtvzpGQOWIjLw=" # 待验证的签名,已经通过 Base64 编码保存,需解码后再进行验证
rsa_key = RSA . importKey ( public_key ) # 使用 RSA.importKey() 函数将公钥字符串导入为一个 RSA 公钥对象
verifier = Signature_pkcs1_v1_5 . new ( rsa_key ) # 创建一个 PKCS#1 v1.5 验证器对象,它将使用导入的公钥进行签名验证
hsmsg = SHA . new () # 创建一个 SHA-1 哈希对象
hsmsg . update ( message_verify . encode ( "utf-8" )) # 将消息转换为 UTF-8 编码的字节串,并生成其SHA-1哈希值
is_verify = verifier . verify ( hsmsg , base64 . b64decode ( signature )) # 使用公钥对消息的哈希值和解码后的签名进行验证。如果验证成功,返回 True;否则返回 False
print ( is_verify )
if __name__ == "__main__" :
check_verify ()
unresolved reference ‘Crypto’
原因:Windows/Mac
文件名默认是不区分大小的,安装 crypto
后,生成的目录名是 crypto
,再安装 pycryptodome
时,就会导致 pycrypto
把文件安装到了 crypto
目录下,而不是 Crypto
目录下。
(图1
)
解决方法:
1.在项目目录进入 ./venv/lib/python3.7/site-packages
文件夹,手动将 crypto
改名为 Crypto
2.移除安装的 crypto
包和 pycryptodome
包,这次不安装 crypto
包,只安装 pycryptodome
包,在 ./venv/lib/python3.7/site-packages
文件夹中,他会生成一个 pycryptodome
目录和 Crypto
目录
Java
RSA
算法是一种非对称加解密算法。服务方生成一对 RSA
密钥,即 公钥 + 私钥
,将公钥提供给调用方,调用方使用公钥对数据进行加密后,服务方根据私钥进行解密。
基础样例
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
package com.test.utils ;
import lombok.extern.slf4j.Slf4j ;
import javax.crypto.Cipher ;
import java.io.ByteArrayOutputStream ;
import java.security.* ;
import java.security.interfaces.RSAPrivateKey ;
import java.security.interfaces.RSAPublicKey ;
import java.security.spec.InvalidKeySpecException ;
import java.security.spec.PKCS8EncodedKeySpec ;
import java.security.spec.X509EncodedKeySpec ;
import java.util.Base64 ;
import java.util.HashMap ;
import java.util.Map ;
@Slf4j
public class RSAUtil {
public static final String KEY_ALGORITHM = "RSA" ;
private static final String PUBLIC_KEY = "RSAPublicKey" ;
private static final String PRIVATE_KEY = "RSAPrivateKey" ;
// 1024 bits 的 RSA 密钥对,最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 117 ;
// 1024 bits 的 RSA 密钥对,最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 128 ;
// 生成密钥对
public static Map < String , Object > initKey ( int keysize ) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator . getInstance ( KEY_ALGORITHM );
// 设置密钥对的 bit 数,越大越安全
keyPairGen . initialize ( keysize );
KeyPair keyPair = keyPairGen . generateKeyPair ();
// 获取公钥
RSAPublicKey publicKey = ( RSAPublicKey ) keyPair . getPublic ();
// 获取私钥
RSAPrivateKey privateKey = ( RSAPrivateKey ) keyPair . getPrivate ();
Map < String , Object > keyMap = new HashMap <> ( 2 );
keyMap . put ( PUBLIC_KEY , publicKey );
keyMap . put ( PRIVATE_KEY , privateKey );
return keyMap ;
}
// 获取公钥字符串
public static String getPublicKeyStr ( Map < String , Object > keyMap ) {
// 获得 map 中的公钥对象,转为 key 对象
Key key = ( Key ) keyMap . get ( PUBLIC_KEY );
// 编码返回字符串
return encryptBASE64 ( key . getEncoded ());
}
// 获取私钥字符串
public static String getPrivateKeyStr ( Map < String , Object > keyMap ) {
// 获得 map 中的私钥对象,转为 key 对象
Key key = ( Key ) keyMap . get ( PRIVATE_KEY );
// 编码返回字符串
return encryptBASE64 ( key . getEncoded ());
}
// 获取公钥
public static PublicKey getPublicKey ( String publicKeyString ) throws NoSuchAlgorithmException , InvalidKeySpecException {
byte [] publicKeyByte = Base64 . getDecoder (). decode ( publicKeyString );
X509EncodedKeySpec keySpec = new X509EncodedKeySpec ( publicKeyByte );
KeyFactory keyFactory = KeyFactory . getInstance ( KEY_ALGORITHM );
return keyFactory . generatePublic ( keySpec );
}
// 获取私钥
public static PrivateKey getPrivateKey ( String privateKeyString ) throws Exception {
byte [] privateKeyByte = Base64 . getDecoder (). decode ( privateKeyString );
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec ( privateKeyByte );
KeyFactory keyFactory = KeyFactory . getInstance ( KEY_ALGORITHM );
return keyFactory . generatePrivate ( keySpec );
}
/**
* BASE64 编码返回加密字符串
*
* @param key 需要编码的字节数组
* @return 编码后的字符串
*/
public static String encryptBASE64 ( byte [] key ) {
return new String ( Base64 . getEncoder (). encode ( key ));
}
/**
* BASE64 解码,返回字节数组
*
* @param key 待解码的字符串
* @return 解码后的字节数组
*/
public static byte [] decryptBASE64 ( String key ) {
return Base64 . getDecoder (). decode ( key );
}
/**
* 公钥加密
*
* @param text 待加密的明文字符串
* @param publicKeyStr 公钥
* @return 加密后的密文
*/
public static String encrypt1 ( String text , String publicKeyStr ) {
try {
log . info ( "明文字符串为:[{}]" , text );
Cipher cipher = Cipher . getInstance ( KEY_ALGORITHM );
cipher . init ( Cipher . ENCRYPT_MODE , getPublicKey ( publicKeyStr ));
byte [] tempBytes = cipher . doFinal ( text . getBytes ( "UTF-8" ));
return Base64 . getEncoder (). encodeToString ( tempBytes );
} catch ( Exception e ) {
throw new RuntimeException ( "加密字符串[" + text + "]时遇到异常" , e );
}
}
/**
* 私钥解密
*
* @param secretText 待解密的密文字符串
* @param privateKeyStr 私钥
* @return 解密后的明文
*/
public static String decrypt1 ( String secretText , String privateKeyStr ) {
try {
// 生成私钥
Cipher cipher = Cipher . getInstance ( KEY_ALGORITHM );
cipher . init ( Cipher . DECRYPT_MODE , getPrivateKey ( privateKeyStr ));
// 密文解码
byte [] secretTextDecoded = Base64 . getDecoder (). decode ( secretText . getBytes ( "UTF-8" ));
byte [] tempBytes = cipher . doFinal ( secretTextDecoded );
return new String ( tempBytes );
} catch ( Exception e ) {
throw new RuntimeException ( "解密字符串[" + secretText + "]时遇到异常" , e );
}
}
public static void main ( String [] args ) throws Exception {
Map < String , Object > keyMap ;
String cipherText ;
// 原始明文
String content = "测试文字" ;
// 生成密钥对
keyMap = initKey ( 1024 );
String publicKey = getPublicKeyStr ( keyMap );
log . info ( "公钥:[{}],长度:[{}]" , publicKey , publicKey . length ());
String privateKey = getPrivateKeyStr ( keyMap );
log . info ( "私钥:[{}],长度:[{}]" , privateKey , privateKey . length ());
// 加密
cipherText = encrypt1 ( content , publicKey );
log . info ( "加密后的密文:[{}],长度:[{}]" , cipherText , cipherText . length ());
// 解密
String plainText = decrypt1 ( cipherText , privateKey );
log . info ( "解密后明文:[{}]" , plainText );
}
}
异常及分析
当原始明文长度较小时,使用上述工具类加解密,没有问题,但是需要进行数据补齐(padding
)。当明文长度过长时,会出现如下图所示的加密异常
1
2
3
4
5
Caused by: javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.test.utils.RSAUtil.encrypto(RSAUtil.java:97)
RSA
算法一次能加密的明文长度与密钥长度成正比,如 RSA
1024
实际可加密的明文长度最大是 1024 bits
。如果小于这个长度怎么办?就需要进行数据补齐(padding
),因为如果没有 padding
,用户则无法确定解密后内容的真实长度。字符串之类的内容问题还不大,以 0
作为结束符,但对二进制数据就很难理解,因为不确定后面的 0
是内容还是内容结束符。
只要用到 padding
,那么就要占用实际的明文长度。对于 1024 bits
(1024 / 8 = 128 byte[]
) 密钥对而言,如果 padding
方式使用默认的 OPENSSL_PKCS1_PADDING
(需要占用 11
字节用于填充),则明文长度最多为 128 - 11 = 117 bytes
,于是才有 117
字节的说法,即下面这种常见的说法:len_in_byte
(原始明文数据)= len_in_bit(key)/ 8 - 11
。
我们一般使用的 padding
标准有 NoPadding
、OAEPPadding
、PKCS1Padding
等,其中 PKCS1
建议的 padding
就占用了 11
个字节。对于 RSA
加密来讲,padding
也是要参与加密的,所以实际的明文只有 117
字节了。
我们在把明文送给 RSA
加密器前,要确认这个值是不是大于位长,也就是如果接近位长,那么需要先 padding
再分段加密。除非我们是 “定长定量自己可控可理解” 的加密,不需要 padding
。
padding
各种 padding
对输入数据长度的要求总结如下。
padding
最大数据长度
RSA_PKCS1_PADDING
RSA_size - 11
RSA_NO_PADDING
RSA_size - 0
RSA_X931_PADDING
RSA_size - 2
padding
最大数据长度
RSA_PKCS1_PADDING
RSA_size - 11
RSA_SSLV23_PADDING
RSA_size - 11
RSA_X931_PADDING
RSA_size - 2
RSA_NO_PADDING
RSA_size - 0
RSA_PKCS1_OAEP_PADDING
RSA_size - 2 * SHA_DIGEST_LENGTH - 2
分段加解密
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
// 分段加密
public static String encrypt2 ( String plainText , String publicKeyStr ) throws Exception {
log . info ( "明文:[{}],长度:[{}]" , plainText , plainText . length ());
byte [] plainTextArray = plainText . getBytes ( "UTF-8" );
PublicKey publicKey = getPublicKey ( publicKeyStr );
Cipher cipher = Cipher . getInstance ( KEY_ALGORITHM );
cipher . init ( Cipher . ENCRYPT_MODE , publicKey );
int inputLen = plainTextArray . length ;
ByteArrayOutputStream out = new ByteArrayOutputStream ();
int offSet = 0 ;
int i = 0 ;
byte [] cache ;
while ( inputLen - offSet > 0 ) {
if ( inputLen - offSet > MAX_ENCRYPT_BLOCK ) {
cache = cipher . doFinal ( plainTextArray , offSet , MAX_ENCRYPT_BLOCK );
} else {
cache = cipher . doFinal ( plainTextArray , offSet , inputLen - offSet );
}
out . write ( cache , 0 , cache . length );
i ++ ;
offSet = i * MAX_ENCRYPT_BLOCK ;
}
byte [] encryptText = out . toByteArray ();
out . close ();
return Base64 . getEncoder (). encodeToString ( encryptText );
}
// 分段解密
public static String decrypt2 ( String encryptTextHex , String privateKeyStr ) throws Exception {
log . info ( "密文:[{}],长度:[{}]" , encryptTextHex , encryptTextHex . length ());
byte [] encryptText = Base64 . getDecoder (). decode ( encryptTextHex );
PrivateKey privateKey = getPrivateKey ( privateKeyStr );
Cipher cipher = Cipher . getInstance ( KEY_ALGORITHM );
cipher . init ( Cipher . DECRYPT_MODE , privateKey );
int inputLen = encryptText . length ;
ByteArrayOutputStream out = new ByteArrayOutputStream ();
int offSet = 0 ;
byte [] cache ;
int i = 0 ;
// 对数据分段解密
while ( inputLen - offSet > 0 ) {
if ( inputLen - offSet > MAX_DECRYPT_BLOCK ) {
cache = cipher . doFinal ( encryptText , offSet , MAX_DECRYPT_BLOCK );
} else {
cache = cipher . doFinal ( encryptText , offSet , inputLen - offSet );
}
out . write ( cache , 0 , cache . length );
i ++ ;
offSet = i * MAX_DECRYPT_BLOCK ;
}
byte [] plainText = out . toByteArray ();
out . close ();
return new String ( plainText );
}
注意
当密钥对改为 2048 bits
时,最大加密明文大小为 2048 (bits) / 8 - 11(byte) = 245 byte
,上述代码中的两个常量就变更为
1
2
3
4
5
// 2048 bits 的 RSA 密钥对,最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 245 ;
// 2048 bits 的 RSA 密钥对,最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 256 ;
PKCS1 和 PKCS8 的区别
通常 JAVA
中需要 PKCS8
格式的密钥。
实际案例
导入坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--bcpkix-jdk15on、bcprov-jdk15on用于RSA解密-->
<dependency>
<groupId> org.bouncycastle</groupId>
<artifactId> bcpkix-jdk15on</artifactId>
<version> 1.68</version>
</dependency>
<dependency>
<groupId> org.bouncycastle</groupId>
<artifactId> bcprov-jdk15on</artifactId>
<version> 1.68</version>
</dependency>
<!--用于将数据json格式化-->
<dependency>
<groupId> com.fasterxml.jackson.core</groupId>
<artifactId> jackson-databind</artifactId>
<version> 2.17.2</version>
</dependency>
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
//密钥
private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwg......" ;
/**
* 接收一个加密的字符串,返回解密后的文本
*/
@RequestMapping ( value = "/decrypt" , method = RequestMethod . POST )
public String decrypt ( @RequestParam ( value = "string" ) String string ) throws Exception {
/*
获取一个 Cipher 实例,用于执行 RSA 加密或解密操作
Cipher 是 Java 加密框架(JCA)中的一个类,用于执行加密和解密操作。getInstance("RSA") 指定要使用的加密算法,这里是 RSA
*/
Cipher cipher = Cipher . getInstance ( "RSA" );
/*
将 Cipher 对象初始化为解密模式,并使用生成的私钥
init() 方法初始化 Cipher 实例,Cipher.DECRYPT_MODE 指定操作模式为解密,getPrivateKey(key) 是要使用的私钥对象,这个初始化过程是解密操作的准备步骤
*/
cipher . init ( Cipher . DECRYPT_MODE , getPrivateKey ( key ));
//将密文用base64解码
byte [] baseByte = Base64 . getDecoder (). decode ( string );
//将密文每128个字节切分一次
List < byte []> result = splitByteArray ( baseByte , 128 );
StringBuilder sb = new StringBuilder ();
//依次解密每段密文,将解密后的文字进行拼接
for ( byte [] chunk : result ) {
byte [] decryptedTemp = cipher . doFinal ( chunk );
sb . append ( new String ( decryptedTemp , "UTF-8" ));
}
String s = sb . toString ();
//还需要再用base64解码一次(依据个人情况而定)
byte [] decodedBytes = Base64 . getDecoder (). decode ( s );
String decodedString = new String ( decodedBytes , StandardCharsets . UTF_8 );
/*
我的明文是一个json数据,所以通过ObjectMapper将解密后的明文格式化未json格式
*/
ObjectMapper objectMapper = new ObjectMapper ();
JsonNode rootNode = objectMapper . readTree ( decodedString );
// 以格式化的方式打印JSON内容
// 注意:ObjectMapper没有直接的toString(int indentFactor)方法,但我们可以使用writerWithDefaultPrettyPrinter()
String prettyJson = objectMapper . writerWithDefaultPrettyPrinter (). writeValueAsString ( rootNode );
return prettyJson ;
}
//加载私钥对象
public static PrivateKey getPrivateKey ( String privateKeyString ) throws Exception {
byte [] privateKeyByte = Base64 . getDecoder (). decode ( privateKeyString );
/*
使用解码后的字节数组创建一个 PKCS8EncodedKeySpec 对象,用于指定私钥的格式
PKCS8EncodedKeySpec 是一个密钥规范,用于表示在 PKCS#8 编码格式下的私钥。这一步将字节数组包装成一个私钥规范对象
*/
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec ( privateKeyByte );
/*
获取一个 KeyFactory 实例,用于生成 RSA 算法的密钥对象
KeyFactory 是一个用于将密钥(通常是原始格式的密钥规范)转换为 Java 密钥对象的类。getInstance("RSA") 指定了所使用的密钥算法是 RSA
*/
KeyFactory keyFactory = KeyFactory . getInstance ( "RSA" );
/*
使用前面创建的 PKCS8EncodedKeySpec 对象生成一个 PrivateKey 实例
generatePrivate() 方法根据指定的密钥规范(keySpec)生成对应的 PrivateKey 对象。这个对象可以在加密和解密操作中使用
*/
return keyFactory . generatePrivate ( keySpec );
}
//按照指定的大小切分
public static List < byte []> splitByteArray ( byte [] inputBytes , int chunkSize ) {
List < byte []> chunks = new ArrayList <> ();
for ( int start = 0 ; start < inputBytes . length ; start += chunkSize ) {
int end = Math . min ( start + chunkSize , inputBytes . length );
byte [] chunk = new byte [ end - start ] ;
System . arraycopy ( inputBytes , start , chunk , 0 , end - start );
chunks . add ( chunk );
}
return chunks ;
}
pkcs1 与 pkcs8 格式 RSA 私钥互相转换
PKCS1 私钥转换为 PKCS8(该格式一般Java调用)
1
openssl pkcs8 -topk8 -inform PEM -in pkcs1.pem -outform pem -nocrypt -out pkcs8.pem
1
2
3
-----BEGIN RSA PRIVATE KEY-----
密钥字符串
-----END RSA PRIVATE KEY-----
PKCS8 格式私钥转换为 PKCS1(传统私钥格式)
1
openssl rsa -in pkcs8.pem -out pkcs1.pem
1
2
3
-----BEGIN PRIVATE KEY-----
密钥字符串
-----END PRIVATE KEY-----