<?php /** * 生成RSA密钥对(公钥和私钥)的命令: * [root@localhost ~]# openssl genrsa -out rsa_private_key.pem 2048 * [root@localhost ~]# openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem * [root@localhost ~]# openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem * 命令①:生成原始RSA私钥文件,参数2048为密钥长度,通常使用2048即可,如果对安全性要求较高可以使用更长的,该数字一般为1024的整数倍。 * 命令②:将原始RSA私钥文件转换为pkcs8格式。 * 命令③:根据原始RSA私钥文件生成RSA公钥文件。 * 备注①:公钥发放给客户端用于加密数据,私钥则要留在服务端用于解密客户端发送过来的加密数据。 * 备注②:本类支持不同长度的密钥的加解密,网上很多代码都是加密按117个字节将数据分割然后分段加密,而解密则按128个字节将密文分割然后分 * 段解密,这两个数字实际上是针对长度为1024的密钥,如果换成了非1024长度(如2048)的密钥就会出问题。这两个数字是可以根据密钥长 * 度算出来的,所以不应该在代码中写固定数字,而是获取密钥长度将它们算出来,这样更换密钥就不需要修改代码了。 */ class RSA { private static $publicKeyPath = __DIR__ . '/rsa_public_key.pem'; // 公钥文件路径 private static $privateKeyPath = __DIR__ . '/rsa_private_key.pem'; // 私钥文件路径 private static $signatureAlgo = OPENSSL_ALGO_SHA512; // 签名算法 private static $padding = OPENSSL_PKCS1_OAEP_PADDING; // 加密算法使用的填充方案,强烈建议使用“OPENSSL_PKCS1_OAEP_PADDING” // 使用不同填充方案占用的字节数 private static $pkcs1 = [ OPENSSL_PKCS1_PADDING => 11, // 使用OPENSSL_PKCS1_PADDING填充需占用11字节 OPENSSL_PKCS1_OAEP_PADDING => 42, // 使用OPENSSL_PKCS1_OAEP_PADDING填充需占用42字节 ]; /** * (使用公钥)加密 * 备注:相同的数据每次加密产生的密文都是不同的。 * * @param string $data 要加密的数据 * @return string 数据加密后的密文 */ public static function encrypt($data) { $encrypt = ''; // 用于保存密文的变量 // 获取公钥的内容(即字符串) $publicKeyContents = file_get_contents(self::$publicKeyPath); // 获取公钥的资源(获取失败返回FALSE) $publicKeyResource = openssl_pkey_get_public($publicKeyContents); // 获取密钥长度,并根据密钥长度算出可加密最大数据长度 $bits = self::getKeyBits($publicKeyResource); // 密钥长度 $max = $bits / 8 - self::$pkcs1[self::$padding]; // 可加密最大数据长度(受填充方案影响) // 按照可加密最大数据长度对数据进行切割,然后分段加密,从而实现超长数据加密 $dataSplit = str_split($data, $max); foreach ($dataSplit as $split) { $splitEncrypt = ''; openssl_public_encrypt($split, $splitEncrypt, $publicKeyResource, self::$padding); // 加密 $encrypt .= $splitEncrypt; } self::freeKey($publicKeyResource); return base64_encode($encrypt); } /** * (使用私钥)解密 * * @param string $data 要解密的数据 * @return string 数据解密后的明文 */ public static function decrypt($data) { $decrypt = ''; // 用于保存明文的变量 // 获取私钥的内容(即字符串) $privateKeyContents = file_get_contents(self::$privateKeyPath); // 获取私钥的资源(获取失败返回FALSE) $privateKeyResource = openssl_pkey_get_private($privateKeyContents); // 加密是分段的,解密也要分段解 $bits = self::getKeyBits($privateKeyResource); // 密钥长度 $max = $bits / 8; $data = base64_decode($data); // 加密方法对密文进行了base64_encode()处理,故这里要进行逆操作 $dataSplit = str_split($data, $max); foreach ($dataSplit as $split) { $splitDecrypt = ''; openssl_private_decrypt($split, $splitDecrypt, $privateKeyResource, self::$padding); // 解密 $decrypt .= $splitDecrypt; } self::freeKey($privateKeyResource); return $decrypt; } /** * (使用私钥)生成签名 * 备注:相同的数据每次生成的签名都是相同的。 * * @param string $data 要生成签名的数据 * @return string 签名字符串 */ public static function signature($data) { // 获取私钥的内容(即字符串) $privateKeyContents = file_get_contents(self::$privateKeyPath); // 获取私钥的资源(获取失败返回FALSE) $privateKeyResource = openssl_pkey_get_private($privateKeyContents); $signature = ''; openssl_sign($data, $signature, $privateKeyResource, self::$signatureAlgo); self::freeKey($privateKeyResource); return base64_encode($signature); } /** * (使用公钥)验证签名 * * @param string $data 数据(必须要和生成签名时的数据一样。PS;签名就是防止伪造数据) * @param string $signature 签名字符串 * @return int 验证结果:1=签名正确,0=签名错误,-1=内部发生错误 */ public static function signatureVerify($data, $signature) { // 获取公钥的内容(即字符串) $publicKeyContents = file_get_contents(self::$publicKeyPath); // 获取公钥的资源(获取失败返回FALSE) $publicKeyResource = openssl_pkey_get_public($publicKeyContents); $signature = base64_decode($signature); $verify = openssl_verify($data, $signature, $publicKeyResource, self::$signatureAlgo); self::freeKey($publicKeyResource); return $verify; } /** * 获取密钥(公钥或私钥均可)的长度 * * @param resource $key OpenSSLAsymmetricKey实例(即公钥或私钥资源) * @return int|false 密钥(公钥或私钥均可)的长度,若获取失败则返回false */ private static function getKeyBits($key) { $details = openssl_pkey_get_details($key); if ($details === false) { return false; } return $details['bits']; } /** * 释放密钥资源 * * @param resource $key 参数说明 * @return void */ private static function freeKey($key) { // openssl_free_key()函数已自 PHP 8.0.0 起被废弃 if (PHP_VERSION_ID < 80000) { openssl_free_key($key); } } } $data = '北国风光,千里冰封,万里雪飘。'; $data .= '望长城内外,惟余莽莽;大河上下,顿失滔滔。'; $data .= '山舞银蛇,原驰蜡象,欲与天公试比高。'; $data .= '须晴日,看红装素裹,分外妖娆。'; $data .= '江山如此多娇,引无数英雄竞折腰。'; $data .= '惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。'; $data .= '一代天骄,成吉思汗,只识弯弓射大雕。'; $data .= '俱往矣,数风流人物,还看今朝。'; echo "数据:$data" . PHP_EOL; // 数据:北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡…… // 备注:由于这里只是简单演示如何使用RSA加解密,故原始数据简单粗暴地使用一串字符串,实际使用中原始数据都是数组格式,开发者需要按照规 // 则将数组转成字符串,通常规则都是将数组进行参数名ASCII字典序排序,然后转成“参数1=参数值1&参数2=参数值2&参数3=参数值3”格式的 // 字符串,然后将字符串进行base64_encode()处理,最后将base64_encode()得出的字符串加密即可,代码如下: // ksort($data); // $dataStr = urldecode(http_build_query($data)); // $dataEncode = base64_encode($dataStr); // $encrypt = RSA::encrypt($dataEncode); // 对原始数据进行加密(备注:相同的数据每次加密产生的密文都是不同的。) $encrypt = RSA::encrypt($data); echo "密文:$encrypt" . PHP_EOL; // 密文:HTKJpal5qVXNxI2XBeHPgYF1dUVYNepLlbshQt8CDIRe11doDIf0mjRLQdTeeFQVIBh4mfabrELXs0bbha…… // 对密文进行解密 $decrypt = RSA::decrypt($encrypt); echo "明文:$decrypt" . PHP_EOL; // 明文:北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原…… // 生成签名(备注:相同的数据每次生成的签名都是相同的。) $signature = RSA::signature($data); echo "签名:$signature" . PHP_EOL; // 签名:jJ5bNkNNCF17mOatDkYPGj/nT9FFdg+zmaYWepIgEWOO4QDAy8qLBirsF4Y6IjYwHSJ0X7bDkvFhlbY2…… // 验证签名,验证结果:1=签名正确,0=签名错误,-1=内部发生错误 $verify = RSA::signatureVerify($data, $signature); echo '验签:' . ($verify === 1 ? '合法' : '非法') . PHP_EOL; // 验签:合法 //========== 总结 ==========// // 1、使用公钥加密可以通过私钥解密,使用私钥加密也可以通过公钥解密。但是使用公钥加密不能通过公钥解密,如果公钥加密还能公钥解密就不是 // 非对称加密了,而且最根本在于如果能这样做那加密将毫无意义,因为公钥是公开的,这意味着任何人都能解密获得消息原文。 // 2、使用公钥加密每次得到的密文都是不同的,而使用私钥加密每次得到的密文都是相同的。 // 3、公钥和私钥是一对一的关系,即一个公钥有且只有一个与之对应的私钥,一个私钥有且只有一个与之对应的公钥。
Copyright © 2024 码农人生. All Rights Reserved