Skip to content

Commit 4fdbb6b

Browse files
Ahmed Zetao YangJaeger
authored andcommitted
第 5 期:非对称加密( RSA )数据传送的实战 by zetaoyang (#100)
* update * correct * modify space of 'xampp'
1 parent 13a89ea commit 4fdbb6b

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
2+
> 建议先了解一下 RSA 原理,再看本文。推荐阮一峰老师的两篇文章:
3+
>* [RSA 算法原理(一)](http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html)
4+
>* [RSA 算法原理(二)](http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html)
5+
6+
因为`php`支持的密钥格式是`.pem`所以`web``java`端都要将`pem`格式转换成其语言平台支持的格式。 比如,`java`支持的格式默认是`.asn`
7+
### 密钥对的生成( php )
8+
如果系统是`Linux`的话,可以使用`openssl`命令来生成。这里展示的是在`Windows`系统下的操作(因为当时做这个东西的时候是在 Windows 8.1 系统下完成的),使用 [xampp](https://www.apachefriends.org/zh_cn/index.html) 集成好的`openssl`(其配置文件路径在 xampp 的安装路径下的php\extras\openssl\openssl.cnf,我们这次使用的就是这个文件),下面展示用`php`来生成密钥对:(n 和 e 组成公钥,n 和 d 组成私钥)
9+
```php
10+
<?php
11+
//OPENSSL_CNF为 openssl.cnf 的路径
12+
define('OPENSSL_CNF','<your openssl.cnf path >');
13+
define('SHA',"sha512");
14+
define('LENGTH',2048);
15+
16+
header("Content-type:text/html;charset=utf-8");
17+
$configargs=array(
18+
'config'=> OPENSSL_CNF,
19+
"digest_alg" => SHA,
20+
"private_key_bits" => LENGTH,
21+
"private_key_type" => OPENSSL_KEYTYPE_RSA,);
22+
$res=openssl_pkey_new($configargs);
23+
openssl_pkey_export($res,$pri,null, $configargs);
24+
25+
/*Binary Data to Decimal Data
26+
* @param string $data
27+
* @return string $result
28+
* */
29+
function hexTobin($hexString)
30+
{
31+
$hexLenght = strlen($hexString);
32+
// only hexadecimal numbers is allowed
33+
if ($hexLenght % 2 != 0 || preg_match("/[^\da-fA-F]/",$hexString)) return FALSE;
34+
35+
unset($binString);
36+
for ($x = 1; $x <= $hexLenght/2; $x++)
37+
{
38+
$binString .= chr(hexdec(substr($hexString,2 * $x - 2,2)));
39+
}
40+
41+
return $binString;
42+
}
43+
44+
/* Binary Data to Decimal Data
45+
* @param string $data
46+
* @return string $result
47+
**/
48+
function binTodec($data)
49+
{
50+
$base = "256";
51+
$radix = "1";
52+
$result = "0";
53+
54+
for($i = strlen($data) - 1; $i >= 0; $i--)
55+
{
56+
$digit = ord($data{$i});
57+
$part_res = bcmul($digit, $radix);
58+
$result = bcadd($result, $part_res);
59+
$radix = bcmul($radix, $base);
60+
}
61+
62+
return $result;
63+
}
64+
65+
$details=openssl_pkey_get_details($res);
66+
67+
$n_hex=bin2hex($details['rsa']['n']);
68+
$d_hex=bin2hex($details['rsa']['d']);
69+
$e_hex=bin2hex($details['rsa']['e']);
70+
71+
$n_dec=binTodec($details['rsa']['n']);
72+
$d_dec=binTodec($details['rsa']['d']);
73+
$e_dec=binTodec($details['rsa']['e']);
74+
75+
openssl_pkey_free($res);
76+
77+
$pub=$details['key'];
78+
79+
$crtpath = "./test.crt"; //公钥文件路径
80+
$pempath = "./test.pem"; //私钥文件路径
81+
82+
$n_hexpath = "./n_hex.key"; //n_hex文件路径
83+
$d_hexpath = "./d_hex.key"; //d_hex文件路径
84+
$e_hexpath = "./e_hex.key"; //e_hex文件路径
85+
86+
$n_decpath = "./n_dec.key"; //n_dec文件路径
87+
$d_decpath = "./d_dec.key"; //d_dec文件路径
88+
$e_decpath = "./e_dec.key"; //e_dec文件路径
89+
90+
//生成证书文件
91+
$fp = fopen($crtpath, "w");
92+
fwrite($fp, $pub);
93+
fclose($fp);
94+
95+
$fp = fopen($pempath, "w");
96+
fwrite($fp, $pri);
97+
fclose($fp);
98+
//生成n_hex,d_hex,e_hex文件
99+
$fp = fopen($n_hexpath, "w");
100+
fwrite($fp, $n_hex);
101+
fclose($fp);
102+
103+
$fp = fopen($d_hexpath, "w");
104+
fwrite($fp, $d_hex);
105+
fclose($fp);
106+
107+
$fp = fopen($e_hexpath, "w");
108+
fwrite($fp, $e_hex);
109+
fclose($fp);
110+
//生成n_dec,d_dec,e_dec文件
111+
$fp = fopen($n_decpath, "w");
112+
fwrite($fp, $n_dec);
113+
fclose($fp);
114+
115+
$fp = fopen($d_decpath, "w");
116+
fwrite($fp, $d_dec);
117+
fclose($fp);
118+
119+
$fp = fopen($e_decpath, "w");
120+
fwrite($fp, $e_dec);
121+
fclose($fp);
122+
123+
var_dump($pub);
124+
echo "<br/>";
125+
var_dump($pri);
126+
$pu_key = openssl_pkey_get_public($pub);
127+
print_r($pu_key);
128+
echo "<br/>";
129+
$data = 'plaintext data goes here.';
130+
131+
// Encrypt the data to $encrypted using the public key
132+
openssl_public_encrypt($data, $encrypted,$pub,OPENSSL_PKCS1_PADDING);
133+
134+
// Decrypt the data using the private key and store the results in $decrypted
135+
openssl_private_decrypt($encrypted, $decrypted, $pri,OPENSSL_PKCS1_PADDING);
136+
137+
echo $encrypted." ".strlen($encrypted)." ".base64_encode($encrypted)."<br/>";
138+
echo $decrypted;
139+
?>
140+
```
141+
### java
142+
> 此处的核心代码是我从互联网上搜索到的,出处已经记不清是哪了,还请原作者见谅。
143+
144+
```java
145+
private String _key;
146+
private KeyFormat _format;
147+
private Cipher _decryptProvider;
148+
private Cipher _encryptProvider;
149+
150+
public KeyWorker(String key) {
151+
this(key, KeyFormat.ASN);
152+
}
153+
154+
public KeyWorker(String key, KeyFormat format) {
155+
this._key = key;
156+
this._format = format;
157+
}
158+
159+
public String encrypt(String data) throws IllegalBlockSizeException,
160+
BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
161+
NoSuchPaddingException, InvalidKeySpecException, IOException, SAXException, ParserConfigurationException {
162+
this._makesureEncryptProvider();
163+
byte[] bytes = data.getBytes("UTF-8");
164+
bytes = this._encryptProvider.doFinal(bytes);
165+
return new BASE64Encoder().encode(bytes);
166+
}
167+
168+
public String decrypt(String data) throws IOException,
169+
IllegalBlockSizeException, BadPaddingException,
170+
InvalidKeyException, NoSuchAlgorithmException,
171+
NoSuchPaddingException, InvalidKeySpecException, SAXException, ParserConfigurationException {
172+
this._makesureDecryptProvider();
173+
174+
byte[] bytes = new BASE64Decoder().decodeBuffer(data);
175+
bytes = this._decryptProvider.doFinal(bytes);
176+
return new String(bytes, "UTF-8");
177+
}
178+
179+
private void _makesureDecryptProvider() throws NoSuchAlgorithmException,
180+
NoSuchPaddingException, IOException, InvalidKeySpecException,
181+
InvalidKeyException, SAXException, ParserConfigurationException {
182+
if (this._decryptProvider != null)
183+
return;
184+
185+
Cipher deCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
186+
switch (this._format) {
187+
188+
case PEM: {
189+
this._key = this._key.replace("-----BEGIN PUBLIC KEY-----", "")
190+
.replace("-----END PUBLIC KEY-----", "")
191+
.replace("-----BEGIN PRIVATE KEY-----", "")
192+
.replace("-----END PRIVATE KEY-----", "")
193+
.replaceAll("\r\n", "");
194+
}
195+
case ASN:
196+
default: {
197+
Boolean isPrivate = this._key.length() > 500;
198+
if (isPrivate) {
199+
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(
200+
new BASE64Decoder().decodeBuffer(this._key));
201+
202+
KeyFactory factory = KeyFactory.getInstance("RSA");
203+
RSAPrivateKey privateKey = (RSAPrivateKey) factory
204+
.generatePrivate(spec);
205+
deCipher.init(Cipher.DECRYPT_MODE, privateKey);
206+
} else {
207+
X509EncodedKeySpec spec = new X509EncodedKeySpec(
208+
new BASE64Decoder().decodeBuffer(this._key));
209+
210+
KeyFactory factory = KeyFactory.getInstance("RSA");
211+
RSAPublicKey publicKey = (RSAPublicKey) factory
212+
.generatePublic(spec);
213+
deCipher.init(Cipher.DECRYPT_MODE, publicKey);
214+
}
215+
}
216+
break;
217+
}
218+
219+
this._decryptProvider = deCipher;
220+
}
221+
222+
private void _makesureEncryptProvider() throws NoSuchAlgorithmException,
223+
NoSuchPaddingException, IOException, InvalidKeySpecException,
224+
InvalidKeyException, SAXException, ParserConfigurationException {
225+
if (this._encryptProvider != null)
226+
return;
227+
228+
Cipher enCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
229+
switch (this._format) {
230+
231+
case PEM: {
232+
this._key = this._key.replace("-----BEGIN PUBLIC KEY-----", "")
233+
.replace("-----END PUBLIC KEY-----", "")
234+
.replace("-----BEGIN PRIVATE KEY-----", "")
235+
.replace("-----END PRIVATE KEY-----", "")
236+
.replaceAll("\r\n", "");
237+
}
238+
case ASN:
239+
default: {
240+
Boolean isPrivate = this._key.length() > 500;
241+
if (isPrivate) {
242+
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(
243+
new BASE64Decoder().decodeBuffer(this._key));
244+
245+
KeyFactory factory = KeyFactory.getInstance("RSA");
246+
RSAPrivateKey privateKey = (RSAPrivateKey) factory
247+
.generatePrivate(spec);
248+
enCipher.init(Cipher.ENCRYPT_MODE, privateKey);
249+
250+
} else {
251+
X509EncodedKeySpec spec = new X509EncodedKeySpec(
252+
new BASE64Decoder().decodeBuffer(this._key));
253+
254+
KeyFactory factory = KeyFactory.getInstance("RSA");
255+
RSAPublicKey publicKey = (RSAPublicKey) factory
256+
.generatePublic(spec);
257+
enCipher.init(Cipher.ENCRYPT_MODE, publicKey);
258+
}
259+
}
260+
break;
261+
}
262+
this._encryptProvider = enCipher;
263+
}
264+
```
265+
以上代码核心就是将其它密钥格式转换成`.pem`格式,和`php`后台那边适配。
266+
### web
267+
由于是前端,所以只做加密数据。然后必须先将密钥对‘分解’成 'n' , 'd' , 'e'。上代码:
268+
```html
269+
<!DOCTYPE html>
270+
<html>
271+
<head>
272+
<meta charset="UTF-8">
273+
<title>JavaScript RSA Encryption </title>
274+
</head>
275+
276+
<script language="JavaScript" type="text/javascript" src="./js/jsbn.js"></script>
277+
<script language="JavaScript" type="text/javascript" src="./js/prng4.js"></script>
278+
<script language="JavaScript" type="text/javascript" src="./js/rng.js"></script>
279+
<script language="JavaScript" type="text/javascript" src="./js/rsa.js"></script>
280+
<script language="JavaScript" type="text/javascript" src="./js/base64.js"></script>
281+
<script language="JavaScript">
282+
//publc key and public length hex data
283+
var public_key="b3a55f5100b87959ef1bd60508fca4f547af9a0617e8eea1a69a9f7f3f669ee01d89033a7521fa25c6437c6e4c4e0237afc23dbc3f1597b3a0a2181b45aae5effbb787cf6ced26fc042168bad462d916323246ce923c6fa22b6baf62f2a8f93a753b21b3fcd4c5789d89ca02badb88081452a5ecc12d88374475bfa409627e9014600d1b821b76b8b44e6d43bf28eb9fbb68a7b40e5c778d8ff63798764277c040432b9b27a682c8e1c202e95e8c3b826d5188c389716bb4a7278761a7b22ff39ede130c1b9022449f190a79846ea616fec3e1056e8f24a7b3b508ca734ea2c8a92f2c97adc4afd391a52e04504fd69553b4e048650a44ebdaa889701256d315";
284+
var public_length="0010001";
285+
function do_encrypt() {
286+
var before = new Date();
287+
var rsa = new RSAKey();
288+
rsa.setPublic(public_key, public_length);
289+
var res = rsa.encrypt(document.rsatest.plaintext.value);
290+
var after = new Date();
291+
if(res) {
292+
document.rsatest.ciphertext.value =res;
293+
document.rsatest.cipherb64.value = hex2b64(res);
294+
document.rsatest.status.value = "Time: " + (after - before) + "ms";
295+
}
296+
}
297+
298+
</script>
299+
300+
<form name="rsatest" action="rsa.php" method="post">
301+
Plaintext (string):<br>
302+
<input name="plaintext" type="text" value="test" size=40>
303+
<input type="button" value="encrypt" onClick="do_encrypt();"><p>
304+
Ciphertext (hex):(Not used)<br>
305+
<textarea name="ciphertext" rows=4 cols=70></textarea><p>
306+
Ciphertext (base64):<br>
307+
<textarea name="cipherb64" rows=3 cols=70></textarea><p>
308+
Status:<br>
309+
<input name="status" type="text" size=40><p>
310+
<input type="submit" value="submit" />
311+
</form>
312+
<body>
313+
<html>
314+
```
315+
316+
##### 对应的 php 后台解密:
317+
```php
318+
<?php
319+
//header("Content-type:text/javascript;charset=utf-8");
320+
$encrypted=$_POST['cipherb64'];
321+
$public_key = file_get_contents("../public.crt");
322+
$private_key = file_get_contents("../private.pem");
323+
324+
//var_dump(base64_decode($encrypted));
325+
326+
$pu_key = openssl_pkey_get_public($public_key);//这个函数可用来判断公钥是否是可用的
327+
$pi_key = openssl_pkey_get_private($private_key);//这个函数可用来判断私钥是否是可用的,可用返回资源id Resource id
328+
//var_dump($pu_key);
329+
//var_dump($pi_key);
330+
openssl_private_decrypt(base64_decode($encrypted),$decrypted,$pi_key);//私钥解密
331+
echo "\n";
332+
var_dump($decrypted);
333+
echo "\n"."共".strlen($decrypted)."个字节。";
334+
?>
335+
```
336+
### 源代码分享
337+
以上全部代码在我的 Github 托管。[传送门](https://github.com/ZetaoYang/RSA)
338+
339+
340+

0 commit comments

Comments
 (0)