|
| 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