前情提要
在网站或应用的业务开发中,往往会使用一些加密逻辑或者与第三方对接 API 时的签名逻辑,在当下繁杂的加密/签名算法中我相信 RSA 相对来说是比较适用的算法。
那这里又有一个问题,很多人其实分不清楚 RSA 所属的公钥和私钥,到底哪个用来加密,哪个用来解密;或者说哪个用来签名,哪个用来验签。其实这个问题也是很好理解的。
如果是用来加密,那么我作为开发者肯定是不希望别人知道我的消息,所以也就是说只有我才能解密,所以可以得出公钥负责加密,私钥负责解密
如果是用来签名,那么我作为开发者肯定不希望有人能冒充我的消息,只能由我去生成这个签名,也就可以得出私钥负责签名,公钥负责验证
那好,有了上述的结论,我们接下来去实现这个加密方法。
生成 RSA 私钥和公钥
RSA是非对称加密,对加密内容长度有限制,生成 1024 位私钥的最多只能加密 127 位数据,如果加密字符串过长请生成 2048 位的秘钥
openssl genrsa -out private_key.pem 2048
openssl pkcs8 -topk8 -inform PEM -in private_key.pem -outform PEM -nocrypt -out private_key.pem
openssl rsa -in private_key.pem -pubout -out public_key.pem
通过上述操作命令,将生成的公钥与秘钥拷贝到自己的项目目录,即可继续进行下面的开发工作(使用 Laravel8)。
加密(公钥加密,私钥解密)
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Str;
class RsaController extends Controller
{
private $private_key = 'MIIEowIBAAKCAQEAyZGgkPRWyeGIlY';
private $public_key = storage_path('public_key.pem');
public function encrypt()
{
$str = 123456789;
if (Str::endsWith($this->public_key, '.pem')) {
$public_key = openssl_pkey_get_public( file_get_contents($this->public_key) );;
} else {
$public_key = "-----BEGIN PUBLIC KEY-----\n".
wordwrap($this->public_key, 64, "\n", true).
"\n-----END PUBLIC KEY-----";
}
try {
openssl_public_encrypt($str,$encrypted, $public_key);
$encrypted = urlencode(base64_encode($encrypted));
!is_resource($public_key) ?: openssl_free_key($public_key);
return $encrypted;
} catch (\Exception $exception) {
return $exception->getMessage();
}
}
public function decrypt()
{
$str = 'lj73ktX7FJWb534rbiE...';
if (Str::endsWith($this->private_key, '.pem')) {
$private_key = openssl_pkey_get_private($this->private_key);
} else {
$private_key = "-----BEGIN RSA PRIVATE KEY-----\n".
wordwrap($this->private_key, 64, "\n", true).
"\n-----END RSA PRIVATE KEY-----";
}
try {
openssl_private_decrypt(base64_decode(urldecode($str)), $decrypted, $private_key);
!is_resource($private_key) ?: openssl_free_key($private_key);
return $decrypted;
} catch (\Exception $exception) {
return $exception->getMessage();
}
}
}
签名(私钥签名,公钥验签)
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Str;
class RsaController extends Controller
{
private $private_key = 'MIIEowIBAAKCAQEAyZGgkPRWyeGIlY';
private $public_key = storage_path('public_key.pem');
public function genSign()
{
$str = 'a=1&b=2&c=3&d=5';
if (Str::endsWith($this->private_key, '.pem')) {
$private_key = openssl_pkey_get_private($this->private_key);
} else {
$private_key = "-----BEGIN RSA PRIVATE KEY-----\n".
wordwrap($this->private_key, 64, "\n", true).
"\n-----END RSA PRIVATE KEY-----";
}
try {
openssl_sign($str, $signature, $private_key);
$sign = base64_encode($signature);
!is_resource($private_key) ?: openssl_free_key($private_key);
return $sign;
} catch (\Exception $exception) {
return $exception->getMessage();
}
}
public function verifySign()
{
$str = 'a=1&b=2&c=3&d=5';
$sign = 'ZSMivQqMFZ1s36NFE9kcB83BcltwII...';
if (Str::endsWith($this->public_key, '.pem')) {
$public_key = openssl_pkey_get_public($this->public_key);
} else {
$public_key = "-----BEGIN PUBLIC KEY-----\n".
wordwrap($this->public_key, 64, "\n", true).
"\n-----END PUBLIC KEY-----";
}
try {
$result = openssl_verify($str, base64_decode($sign), $public_key );
!is_resource($public_key) ?: openssl_free_key($public_key);
return $result === 1
} catch (\Exception $exception) {
return $exception->getMessage();
}
}
}
使用 RSA2 的签名和验签说明
RSA 默认签名方式为 OPENSSL_ALGO_SHA1 如果使用RSA2的话需要在签名和验签方法中增加参数 OPENSSL_ALGO_SHA256 ,示例如下:
openssl_sign($str, $signature, $private_key, OPENSSL_ALGO_SHA256);
openssl_verify($str, base64_decode($sign), $public_key, OPENSSL_ALGO_SHA256);
RSA 和 RSA2的区别
签名算法 | 标准签名算法 | 描述 |
---|
RSA2 | SHA256WithRSA | 强制要求 RSA 密钥的长度至少为 2048。 | RSA | SHA1WithRSA | 对 RSA 密钥的长度不限制,推荐使用 2048 位以上。 |
结论
以上就是使用 RSA 进行加解密以及签名验签的全部实现了,并不是很复杂,代码稍作修改即可应用在你自己的业务中了。
另外建议在使用 RSA 做签名验证的时候建议使用 RSA2 的方式,相对而言 RSA2 的安全能力是高于 RSA 的。
|