目录
RSA加密算法基础
RSA加密算法是最常用的非对称加密算法,CFCA在证书服务中离不了它。但是有不少新来的同事对它不太了解,恰好看到一本书中作者用实例对它进行了简化而生动的描述,使得高深的数学理论能够被容易地理解。我们经过整理和改写特别推荐给大家阅读,希望能够对时间紧张但是又想了解它的同事有所帮助。
RSA是第一个比较完善的公开密钥算法,它既能用于加密,也能用于数字签名。RSA以它的三个发明者Ron Rivest, Adi Shamir, Leonard Adleman的名字首字母命名,这个算法经受住了多年深入的密码分析,虽然密码分析者既不能证明也不能否定RSA的安全性,但这恰恰说明该算法有一定的可信性,目前它已经成为最流行的公开密钥算法。
RSA的安全基于大数分解的难度。其公钥和私钥是一对大素数(100到200位十进制数或更大)的函数。从一个公钥和密文恢复出明文的难度,等价于分解两个大素数之积(这是公认的数学难题)。
RSA的公钥、私钥的组成,以及加密、解密的公式可见于下表:
可能各位同事好久没有接触数学了,看了这些公式不免一头雾水。别急,在没有正式讲解RSA加密算法以前,让我们先复习一下数学上的几个基本概念,它们在后面的介绍中要用到:
一、 什么是“素数”?
素数是这样的整数,它除了能表示为它自己和1的乘积以外,不能表示为任何其它两个整数的乘积。例如,15=3*5,所以15不是素数;又如,12=6*2=4*3,所以12也不是素数。另一方面,13除了等于13*1以外,不能表示为其它任何两个整数的乘积,所以13是一个素数。素数也称为“质数”。
二、什么是“互质数”(或“互素数”)?
小学数学教材对互质数是这样定义的:“公约数只有1的两个数,叫做互质数。”这里所说的“两个数”是指自然数。
判别方法主要有以下几种(不限于此):
(1)两个质数一定是互质数。例如,2与7、13与19。
(2)一个质数如果不能整除另一个合数,这两个数为互质数。例如,3与10、5与 26。
(3)1不是质数也不是合数,它和任何一个自然数在一起都是互质数。如1和9908。
(4)相邻的两个自然数是互质数。如 15与 16。
(5)相邻的两个奇数是互质数。如 49与 51。
(6)大数是质数的两个数是互质数。如97与88。
(7)小数是质数,大数不是小数的倍数的两个数是互质数。如 7和 16。
(8)两个数都是合数(二数差又较大),小数所有的质因数,都不是大数的约数,这两个数是互质数。如357与715,357=3×7×17,而3、7和17都不是715的约数,这两个数为互质数。等等。
三、什么是模指数运算?
指数运算谁都懂,不必说了,先说说模运算。模运算是整数运算,有一个整数m,以n为模做模运算,即m mod n。怎样做呢?让m去被n整除,只取所得的余数作为结果,就叫做模运算。例如,10 mod 3=1;26 mod 6=2;28 mod 2 =0等等。
模指数运算就是先做指数运算,取其结果再做模运算。如
好,现在开始正式讲解RSA加密算法。
算法描述:
(1)选择一对不同的、足够大的素数p,q。
(2)计算n=pq。
(3)计算f(n)=(p-1)(q-1),同时对p, q严加保密,不让任何人知道。
(4)找一个与f(n)互质的数e,且1<e<f(n)。
(5)计算d,使得de≡1 mod f(n)。这个公式也可以表达为d ≡e-1 mod f(n)
这里要解释一下,≡是数论中表示同余的符号。公式中,≡符号的左边必须和符号右边同余,也就是两边模运算结果相同。显而易见,不管f(n)取什么值,符号右边1 mod f(n)的结果都等于1;符号的左边d与e的乘积做模运算后的结果也必须等于1。这就需要计算出d的值,让这个同余等式能够成立。
(6)公钥KU=(e,n),私钥KR=(d,n)。
(7)加密时,先将明文变换成0至n-1的一个整数M。若明文较长,可先分割成适当的组,然后再进行交换。设密文为C,则加密过程为:。
(8)解密过程为:。
实例描述:
在这篇科普小文章里,不可能对RSA算法的正确性作严格的数学证明,但我们可以通过一个简单的例子来理解RSA的工作原理。为了便于计算。在以下实例中只选取小数值的素数p,q,以及e,假设用户A需要将明文“key”通过RSA加密后传递给用户B,过程如下:
(1)设计公私密钥(e,n)和(d,n)
令p=3,q=11,得出n=p×q=3×11=33;f(n)=(p-1)(q-1)=2×10=20;取e=3,(3与20互质)则e×d≡1 mod f(n),即3×d≡1 mod 20。
d怎样取值呢?可以用试算的办法来寻找。试算结果见下表:
通过试算我们找到,当d=7时,e×d≡1 mod f(n)同余等式成立。因此,可令d=7。从而我们可以设计出一对公私密钥,加密密钥(公钥)为:KU =(e,n)=(3,33),解密密钥(私钥)为:KR =(d,n)=(7,33)。
(2)英文数字化
将明文信息数字化,并将每块两个数字分组。假定明文英文字母编码表为按字母顺序排列数值,即:
则得到分组后的key的明文信息为:11,05,25。
(3)明文加密
用户加密密钥(3,33) 将数字化明文分组信息加密成密文。由得:
因此,得到相应的密文信息为:11,31,16。
(4)密文解密。
用户B收到密文,若将其解密,只需要计算,即:
用户B得到明文信息为:11,05,25。根据上面的编码表将其转换为英文,我们又得到了恢复后的原文“key”。
你看,它的原理就可以这么简单地解释!
当然,实际运用要比这复杂得多,由于RSA算法的公钥私钥的长度(模长度)要到1024位甚至2048位才能保证安全,因此,p、q、e的选取、公钥私钥的生成,加密解密模指数运算都有一定的计算程序,需要仰仗计算机高速完成。
最后简单谈谈RSA的安全性
首先,我们来探讨为什么RSA密码难于破解?
在RSA密码应用中,公钥KU是被公开的,即e和n的数值可以被第三方窃听者得到。破解RSA密码的问题就是从已知的e和n的数值(n等于pq),想法求出d的数值,这样就可以得到私钥来破解密文。从上文中的公式:d ≡e-1 (mod((p-1)(q-1)))或de≡1 (mod((p-1)(q-1))) 我们可以看出。密码破解的实质问题是:从Pq的数值,去求出(p-1)和(q-1)。换句话说,只要求出p和q的值,我们就能求出d的值而得到私钥。
当p和q是一个大素数的时候,从它们的积pq去分解因子p和q,这是一个公认的数学难题。比如当pq大到1024位时,迄今为止还没有人能够利用任何计算工具去完成分解因子的任务。因此,RSA从提出到现在已近二十年,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一。
然而,虽然RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价。即RSA的重大缺陷是无法从理论上把握它的保密性能如何。
此外,RSA的缺点还有:A)产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。B)分组长度太大,为保证安全性,n 至少也要 600 bits 以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级;且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。因此,使用RSA只能加密少量数据,大量的数据加密还要靠对称密码算法。
非对称加解密,私钥和公钥到底是谁来加密,谁来解密
第一种用法:公钥加密,私钥解密。—用于加解密
第二种用法:私钥签名,公钥验签。—用于签名
有点混乱,不要去硬记,总结一下:
你只要想:
既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;
既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。
同一种道理,我在换种说法:
私钥和公钥是一对,谁都可以加解密,只是谁加密谁解密是看情景来用的:
第一种情景是签名,使用私钥加密,公钥解密,用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改.但是不用来保证内容不被他人获得。
第二种情景是加密,用公钥加密,私钥解密,用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。
比如加密情景:
如果甲想给乙发一个安全的保密的数据,那么应该甲乙各自有一个私钥,甲先用乙的公钥加密这段数据,再用自己的私钥加密这段加密后的数据.最后再发给乙,这样确保了内容即不会被读取,也不会被篡改.
RSA加密类
下面是tp5.1下写的,使用了链式调用,实际是使用RSA类,去调用RSAUtil类里的方法:
RSA类,只是一个代理,实际调用的是RSAUtil类:
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 |
<?php namespace app\lib\tools; use app\lib\tools\libs\RSAUtil; /** * Class RSA * 以下是将可以公开使用的方法映射到这个类里面 * @package app\lib\tools * @method libs privateKeyBits($bits=1024) static 配置 指定应该使用多少位来生成私钥 * @method libs privateKeyType($type=OPENSSL_KEYTYPE_RSA) static 配置 选择在创建CSR时应该使用哪些扩展 * @method libs config($opensslConfPath='') static 配置 自定义 openssl.conf 文件的路径 * @method libs padding($type=OPENSSL_PKCS1_PADDING) static 配置 RSA加密解密的填充方式 * @method libs dn($dn=[]) static 配置 生成证书个人信息 * @method libs privkeypass($privkeypass='') static 配置 加密私钥密码 * @method libs outDays($outDays=1) static 配置 公钥私钥过期时间 * @method libs pubKeyPath($path='',$filename='') static 配置 公钥保存路径 * @method libs priKeyPath($path='',$filename='') static 配置 私钥保存路径 * @method libs setPrivateKey($privateKey='',$format=false,$is_encry=false) static 设置私钥 * @method libs setPublicKey($publicKey='',$format=false) static 设置公钥 * @method libs generate($createFile=false) static 生成公私钥 * @method libs sign($msg, $algorithm=OPENSSL_ALGO_SHA256) static 签名 * @method libs verify($msg, $sign, $algorithm=OPENSSL_ALGO_SHA256) static 验签 * @method libs publicEncrypt($source_data) static 公钥加密 * @method libs privateDecrypt($eccryptData) static 私钥解密 * @method libs privateEncrypt($source_data) static 私钥加密 * @method libs publicDecrypt($eccryptData) static 公钥解密 */ class RSA { //链式调用的核心 public static function __callStatic($name,$values){ //实例类库 return call_user_func_array([(new RSAUtil()),$name],$values); } } |
(这才是真正的操作类)RSAUtil类如下:
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 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
<?php namespace app\lib\tools\libs; use think\facade\App; class RSAUtil { //生成公钥、私钥需要的参数.链式调用,默认使用配置文件的参数。 private $generateParams = [ 'config'=>'',//自定义 openssl.conf 文件的路径。 'private_key_bits'=>1024,//指定应该使用多少位来生成私钥 'private_key_type'=>OPENSSL_KEYTYPE_RSA,//选择在创建CSR时应该使用哪些扩展。可选值有 OPENSSL_KEYTYPE_DSA, OPENSSL_KEYTYPE_DH, OPENSSL_KEYTYPE_RSA 或 OPENSSL_KEYTYPE_EC. 'padding'=>OPENSSL_PKCS1_PADDING,//RSA加密解密的填充方式,不同编程语言之间交互,需要注意这个。OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING 'dn'=>[],//生成证书个人信息 'privkeypass'=>null,//加密私钥密码 'outDays'=>1,//过期时间 'pubKeyPath'=>'',//公钥存放路径 'priKeyPath'=>'',//私钥存放路径 'pubKeyName'=>'',//公钥文件名 'priKeyName'=>'',//私钥文件名 ]; //链式动态赋予公私钥 private $key = [ 'private_key' =>'', 'public_key' =>'', ]; //项目根目录,公钥、私钥文件都是基于这个根目录 private $rootPath = ''; public function __construct() { //将配置文件的配置读入到 generateParams 作为默认参数 $config = config('openssl.rsa.config.config'); $private_key_bits = config('openssl.rsa.config.private_key_bits'); $private_key_type = config('openssl.rsa.config.private_key_type'); $padding = config('openssl.rsa.padding'); $dn = config('openssl.rsa.dn'); $privkeypass = config('openssl.rsa.privkeypass'); $outDays = config('openssl.rsa.outDays'); $pubKeyPath = config('openssl.rsa.pubKeyPath'); $priKeyPath = config('openssl.rsa.priKeyPath'); $privateKeyName = config('openssl.rsa.private_key_name'); $publicKeyName = config('openssl.rsa.public_key_name'); //初始化给根路径赋值 $this->rootPath = App::getRootPath(); $this->generateParams['config'] = empty($config)?'':$config; $this->generateParams['private_key_bits'] = empty($private_key_bits)?'':$private_key_bits; $this->generateParams['private_key_type'] = empty($private_key_type)?OPENSSL_KEYTYPE_RSA:$private_key_type; $this->generateParams['padding'] = empty($padding)?'':$padding; $this->generateParams['dn'] = empty($dn)?[]:$dn; $this->generateParams['privkeypass'] = empty($privkeypass)?null:$privkeypass; $this->generateParams['outDays'] = empty($outDays)?1:$outDays; $this->generateParams['pubKeyPath'] = empty($pubKeyPath)?'':$pubKeyPath; $this->generateParams['priKeyPath'] = empty($priKeyPath)?'':$priKeyPath; $this->generateParams['pubKeyName'] = empty($publicKeyName)?'rsa_public_key.pem':$publicKeyName; $this->generateParams['priKeyName'] = empty($privateKeyName)?'rsa_private_key.pem':$privateKeyName; } //获取生成一个私钥的配置 private function getConfig() { $conf = [ 'private_key_bits' => $this->generateParams['private_key_bits'], 'private_key_type' => $this->generateParams['private_key_type'] ]; if (!empty($this->generateParams['config'])){ $conf = array_merge($conf,['config'=>$this->generateParams['config']]); } return $conf; } /**获取私钥的完整路径 * @return string */ private function getPrivateKeyPath() { $priKeyPath = $this->generateParams['priKeyPath']; $filepath = $this->fullPath($priKeyPath); return $filepath; } /**获取公钥的完整路径 * @return string */ private function getPublicKeyPath() { $pubKeyPath = $this->generateParams['pubKeyPath']; $filepath = $this->fullPath($pubKeyPath); return $filepath; } /** 补全路径 * @param string $uri 相对路径.输入demo/test,返回的路径:项目根目录路径+demo/test * @return string */ private function fullPath($uri='') { $root_path = $this->rootPath; $path =$root_path.$uri; return $path; } /** 链式调用 配置 指定应该使用多少位来生成私钥 * @param int $bits 位数 * @return $this */ public function privateKeyBits($bits=1024) { $this->generateParams['private_key_bits'] = $bits; return $this; } /** 链式调用 配置 选择在创建CSR时应该使用哪些扩展。 * 可选值有 OPENSSL_KEYTYPE_DSA, OPENSSL_KEYTYPE_DH, OPENSSL_KEYTYPE_RSA 或 OPENSSL_KEYTYPE_EC. * @param int $type * @return $this */ public function privateKeyType($type=OPENSSL_KEYTYPE_RSA) { $this->generateParams['private_key_type'] = $type; return $this; } /**链式调用 配置 自定义 openssl.conf 文件的路径。 * @param string $opensslConfPath * @return $this */ public function config($opensslConfPath='') { $this->generateParams['config'] = $opensslConfPath; return $this; } /**链式调用 配置 RSA加密解密的填充方式,不同编程语言之间交互,需要注意这个。 * OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING * @param int $type * @return $this */ public function padding($type=OPENSSL_PKCS1_PADDING) { $this->generateParams['padding'] = $type; return $this; } /**链式调用 配置 生成证书个人信息 * @param array $dn * @return $this */ public function dn($dn=[]) { $this->generateParams['dn'] = $dn; return $this; } /** 链式调用 配置 加密私钥密码 * @param string $privkeypass 给私钥加密的密码 * @return $this */ public function privkeypass($privkeypass='') { $this->generateParams['privkeypass'] = $privkeypass; return $this; } /** 链式调用 配置 公钥私钥过期时间 * @param int $outDays 过期时间,单位:天 * @return $this */ public function outDays($outDays=1) { $this->generateParams['outDays'] = $outDays; return $this; } /** 链式调用 配置 公钥保存路径 * @param string $path 路径 * @param string $filename 文件名 * @return $this */ public function pubKeyPath($path='',$filename='') { $this->generateParams['pubKeyPath'] = $path; $this->generateParams['pubKeyName'] = $filename; return $this; } /** 链式调用 配置 私钥保存路径 * @param string $path 路径 * @param string $filename 文件名 * @return $this */ public function priKeyPath($path='',$filename='') { $this->generateParams['priKeyPath'] = $path; $this->generateParams['priKeyName'] = $filename; return $this; } /** 链式调用 设置私钥 * @param string $privateKey 私钥内容 * @param bool $format 格式化标准的pem文件格式 * @param bool $is_encry 私钥有密码保护。当$format为true,此选项生效 * @return $this */ public function setPrivateKey($privateKey='',$format=false,$is_encry=false) { if ($format){ $privateKey = chunk_split($privateKey,64,"\n"); // transfer to pem format if ($is_encry){ $privateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n".$privateKey."-----END ENCRYPTED PRIVATE KEY-----\n"; }else{ $privateKey = "-----BEGIN PRIVATE KEY-----\n".$privateKey."-----END PRIVATE KEY-----\n"; } } $this->key['private_key'] = $privateKey; return $this; } /**链式调用 设置公钥 * @param string $publicKey 公钥内容 * @param bool $format 是否格式化为pem内容 * @return $this */ public function setPublicKey($publicKey='',$format=false) { if ($format){ $publicKey = chunk_split(base64_encode($publicKey),64,"\n"); // transfer to pem format $publicKey = "-----BEGIN CERTIFICATE-----\n".$publicKey."-----END CERTIFICATE-----\n"; } $this->key['public_key'] = $publicKey; return $this; } /** 生成公私钥 * @param bool $createFile 以文件形式保存 * @return array|bool * @throws \Exception */ public function generate($createFile=false){ // 获取生成公私钥的配置信息 $conf = $this->getConfig(); //根据配置生成一个新的私钥和公钥对。 $res = openssl_pkey_new($conf); //成功,返回资源标识符,错误则返回 FALSE 。 if( $res ) { $d= openssl_pkey_get_details($res);//返回包含密钥详情的数组 $pub = $d['key'];//读取公钥 $bits = $d['bits'];//读取公私钥的位数 //根据资源标识算出私钥 openssl_pkey_export($res, $pri, $this->generateParams['privkeypass'], $conf); //把公私钥以数组形式返回 $result = [ "private_key"=>$pri, "public_key"=>$pub, "keysize"=>$bits ]; if ($createFile){ //设置私钥文件保存路径 $privateKeyFilePath = $this->getPrivateKeyPath().DIRECTORY_SEPARATOR.$this->generateParams['priKeyName']; //设置公钥文件保存路径 $publicKeyFilePath = $this->getPublicKeyPath().$this->generateParams['pubKeyName']; //生成公钥文件 $fp = fopen($publicKeyFilePath,'w'); fwrite($fp,$pub,strlen($pub)); fclose($fp); //生成私钥文件 $result = openssl_pkey_export_to_file($res, $privateKeyFilePath, $this->generateParams['privkeypass'], $conf); } //释放密钥资源 openssl_free_key($res); return $result; }else { exception('新私钥生成失败'); }; } /** 非对称 加密,使用hash_hmac * @param $msg 明文 * @param $key 秘钥 * @param string $method 加密方法 * @param int $options 输出模式 * @return string 密文 */ public function encrypt($msg, $key, $method="AES-128-CBC", $options=OPENSSL_RAW_DATA){ $ivlen = openssl_cipher_iv_length($method); $iv = openssl_random_pseudo_bytes($ivlen); $cipher = openssl_encrypt($msg, $method, $key, $options, $iv); $hmac = hash_hmac('sha256', $cipher, $key, $as_binary=true); $cipher = base64_encode( $iv.$hmac.$cipher ); return $cipher; } /**非对称 解密,使用hash_hmac * @param $cipher 密文 * @param $key 秘钥 * @param string $method 解密方法 * @param int $options 输出模式 * @return bool|string 解密是否成功|明文 */ public function decrypt($cipher, $key, $method="AES-128-CBC", $options=OPENSSL_RAW_DATA){ $c = base64_decode($cipher); $ivlen = openssl_cipher_iv_length($method); $iv = substr($c, 0, $ivlen); $hmac = substr($c, $ivlen, $sha2len=32); $cipher = substr($c, $ivlen+$sha2len); $msg = openssl_decrypt($cipher, $method, $key, $options, $iv); $calcmac = hash_hmac('sha256', $cipher, $key, $as_binary=true); if( hash_equals($hmac, $calcmac) ) return $msg;//PHP 5.6+ timing attack safe comparison return false; } /** * 取得公钥内容 * @return resource 公钥内容 */ private function getPublicKey() { //先到链式读取,没有再读取文件 $pem = empty($this->key['public_key'])?file_get_contents($this->getPublicKeyPath().$this->generateParams['pubKeyName']):$this->key['public_key']; $publicKey = openssl_pkey_get_public($pem); return $publicKey; } /**取得私钥内容 * @return bool|resource 成功|私钥内容 */ private function getPrivateKey() { //先到链式读取,没有再读取文件 $pem = empty($this->key['private_key'])?file_get_contents($this->getPublicKeyPath().$this->generateParams['priKeyName']):$this->key['private_key']; $privateKey = openssl_pkey_get_private($pem,$this->generateParams['privkeypass']); return $privateKey; } /** 签名 * @param $msg 签名前的字符串 * @param int $algorithm 签名算法 OPENSSL_ALGO_SHA256、OPENSSL_ALGO_MD5、OPENSSL_ALGO_SHA1 * @return string 签名 */ public function sign($msg, $algorithm=OPENSSL_ALGO_SHA256){ $sign = ""; $key = $this->getPrivateKey();//取得私钥 openssl_sign($msg, $sign, $key, $algorithm);//使用私钥对明文签名 $sign = base64_encode($sign);//签名通过base64加密 openssl_free_key($key);//释放资源 return $sign; } /**验签 * @param $msg 签名前的字符串 * @param $sign 签名 * @param int $algorithm 验签算法 * @return int 验签结果 1:成功,0:失败 */ public function verify($msg, $sign, $algorithm=OPENSSL_ALGO_SHA256){ //签名是经过base64加密了的,这里需要解密 $sign = base64_decode($sign); //取得公钥 $key = $this->getPublicKey(); //使用公钥验签 $result = openssl_verify($msg, $sign, $key, $algorithm); //释放资源 openssl_free_key($key); //返回验签结果 return $result; } /**取得公私钥的位数 * @param $resouce_id 公私钥资源id * @return int 位数 */ private function getKeySize($resouce_id) { return openssl_pkey_get_details($resouce_id)['bits']; } /**公钥加密 * @param $source_data 明文 * @return string 密文 */ public function publicEncrypt($source_data) { $data = ""; $key = $this->getPublicKey();//获取公钥 $key_size = $this->getKeySize($key);//获取公钥位数 /*解决: 密钥是1024bit长的(openssl genrsa -out rsa_private_key.pem 1024)那么明文长度最多只能就是128-11=117字节。 如果超出,那么这些openssl加解密函数会返回false。 */ //把明文分段, 1024bit && OPENSSL_PKCS1_PADDING 不大于117即可 $dataArray = str_split($source_data, $key_size/8); //一段段的加密 foreach ($dataArray as $value) { $encryptedTemp = ""; //公钥加密 openssl_public_encrypt($value,$encryptedTemp,$key,$this->generateParams['padding']); //每段加密结果都拼接起来,最终成为一个完整的加密 $data .= $encryptedTemp; } //释放资源 openssl_free_key($key); //返回加密后的数据,rsa加密后密文是16进制的,并非都是可以打印的字符。通常的做法是密文出来以后做一个 base64 编码。 return base64_encode($data); } /**私钥解密 * @param $eccryptData 密文 * @return string 明文 */ public function privateDecrypt($eccryptData) { $decrypted = ""; $decodeStr = base64_decode($eccryptData); $key = $this->getPrivateKey();//取得私钥 $key_size = $this->getKeySize($key);//取得私钥的位数 /*解决:密钥是1024bit长的(openssl genrsa -out rsa_private_key.pem 1024)那么明文长度最多只能就是128-11=117字节。 如果超出,那么这些openssl加解密函数会返回false。*/ //把密文分段 $enArray = str_split($decodeStr, $key_size/8); //一段段的解密 foreach ($enArray as $va) { $decryptedTemp = ""; //私钥解密 openssl_private_decrypt($va,$decryptedTemp,$key,$this->generateParams['padding']); //把每段解密拼接起来,组成完整的明文 $decrypted .= $decryptedTemp; } //释放资源 openssl_free_key($key); //返回明文 return $decrypted; } /** 私钥加密 * @param $source_data 明文 * @return string 密文 */ public function privateEncrypt($source_data) { $data = ""; $key = $this->getPrivateKey();//取得私钥 $key_size = $this->getKeySize($key);//取得私钥位数 /*解决: 密钥是1024bit长的(openssl genrsa -out rsa_private_key.pem 1024)那么明文长度最多只能就是128-11=117字节。 如果超出,那么这些openssl加解密函数会返回false。 */ //把明文分段, 1024bit && OPENSSL_PKCS1_PADDING 不大于117即可 $dataArray = str_split($source_data, $key_size/8); //一段段的加密 foreach ($dataArray as $value) { $encryptedTemp = ""; //私钥加密 openssl_private_encrypt($value,$encryptedTemp,$key,$this->generateParams['padding']); //每段加密结果都拼接起来,最终成为一个完整的加密 $data .= $encryptedTemp; } //释放资源 openssl_free_key($key); //返回加密后的数据,rsa加密后密文是16进制的,并非都是可以打印的字符。通常的做法是密文出来以后做一个 base64 编码。 return base64_encode($data); } /**公钥解密 * @param $eccryptData 密文 * @return string 明文 */ public function publicDecrypt($eccryptData) { $decrypted = ""; $decodeStr = base64_decode($eccryptData); $key = self::getPublicKey();//取得公钥 $key_size = $this->getKeySize($key);//取得公钥位数 /*解决:密钥是1024bit长的(openssl genrsa -out rsa_private_key.pem 1024)那么明文长度最多只能就是128-11=117字节。 如果超出,那么这些openssl加解密函数会返回false。*/ //把密文分段 $enArray = str_split($decodeStr, $key_size/8); //一段段的解密 foreach ($enArray as $va) { $decryptedTemp = ""; //公钥解密 openssl_public_decrypt($va,$decryptedTemp,$key,$this->generateParams['padding']); //把每段解密拼接起来,组成完整的明文 $decrypted .= $decryptedTemp; } //释放资源 openssl_free_key($key); //返回明文 return $decrypted; } } |
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php halt(RSA::generate(true));//生成公私钥 $plain = "利用公钥加密,私钥解密做数据保密通信!"; $cipher = RSA::publicEncrypt($plain); // $cipher = "填入Java生成的密文(Base64编码)以解密"; $msg = RSA::privateDecrypt($cipher); halt(['明文'=>$plain, '解密'=>$msg, '密文'=>$cipher]); $plain = "利用私钥加密,公钥解密可以做身份验证"; $cipher = RSA::privateEncrypt($plain); $msg = RSA::publicDecrypt($cipher); halt(['明文'=>$plain, '解密'=>$msg, '密文'=>$cipher]); $msg = 'a=123'; $sign = RSA::sign($msg); $verify = RSA::verify($msg, $sign); halt(['预签'=>$msg, '签名'=>$sign, '验证'=>$verify==1?"PASS":"FAIL"]); |