RSA
Index
Core Concepts
1、Symmetric Encryption
Both parties involved in communication hold the same
key
and use analgorithm
to encrypt and decrypt the data being transmitted. Common algorithms includeAES
,DES
,RC4
, etc.
2、Asymmetric Encryption
In asymmetric encryption, both the sender and the receiver have two separate keys:
a private key
anda public key
. Theprivate key
is held by the data sender, while thepublic key
can be made public. Common algorithms includeRSA
.
3、RSA Algorithm Rules
- Data encrypted with the private key can only be decrypted with the corresponding public key, and data encrypted with the public key can only be decrypted with the corresponding private key.
- The private key can generate the public key, but the public key cannot generate the private key.
4、Hash Digest
A one-way
Hash
function is applied to a piece of data to generate a fixed-length Hash value, which is the digest or fingerprint of that data. If the data is modified in any way, the resultingHash
value will be inconsistent.
5、Digital Signature
Used to ensure data integrity and authenticate the data source. This ensures that the data cannot be tampered with and prevents others from impersonating the sender during data transmission. The private key in
RSA
signing guarantees that the data cannot be impersonated.
6、Digital Certificate
Digital signatures are mainly used to verify data integrity and authenticate the data source, while digital certificates are primarily used to securely distribute public keys. A digital certificate generally consists of three parts: the user's information, the user's public key, and the CA's signature on the certificate entity information.
Communication Process
Assume that A and B want to communicate securely:
1、
A
generates a key pair:privateKeyA
andpublicKeyA
;B
generates a key pair:privateKeyB
andpublicKeyB
.
2、A
sendspublicKeyA
toB
, andB
sendspublicKeyB
toA
. At this point,A
hasprivateKeyA
andpublicKeyB
, andB
hasprivateKeyB
andpublicKeyA
3、A
performs a hash calculation onPlaintextA
, obtainingDigestA
(to prevent data tampering). Then,A
usesprivateKeyA
to signDigestA
, resulting inSignA
(to ensure the data is sent byA
). At the same time,A
usesprivateKeyA
to encryptPlaintextA
, obtainingCiphertextA
. A then sendsCiphertextA
and SignA toB
.
4、B
receivesCiphertextA
andSignA
, then usespublicKeyA
to decryptCiphertextA
and obtainPlaintextA
.B
performs a hash calculation onPlaintextA
to getDigestB
. Simultaneously,B
usespublicKeyA
to decryptSignA
and obtainDigestA
, then comparesDigestA
andDigestB
to check if they are the same. At the same time,B
usespublicKeyA
to decryptSignA
and obtainDigestA
, then comparesDigestA
andDigestB
to check if they are the same.
【Flowchart】
Encapsulation of Utility Class
Detail
<?php
declare(strict_types=1);
namespace App\Lib\Encrypt;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\RSA\PrivateKey;
// 在线工具测试:
// https://try8.cn/tool/cipher/rsa
class RsaWithPHPSeclib
{
/**
* RSA私钥实例.
*/
private RSA|PrivateKey $privateKey;
/**
* 秘钥保存路径.
*/
private string $path;
/**
* 秘钥保存格式(PKCS8|PKCS1).
* @see https://phpseclib.com/docs/publickeys#saving-keys
*/
private string $keyFormat;
/**
* 可用的hash算法.
*/
private array $availableHash = ['md2', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'sha224'];
/**
* 可用的加密填充方式.
*/
private array $availableEncryptPadding = [RSA::ENCRYPTION_OAEP, RSA::ENCRYPTION_PKCS1, RSA::ENCRYPTION_NONE];
/**
* 私钥加签可用的填充方式.
*/
private array $availableSignaturePadding = [RSA::SIGNATURE_PKCS1, RSA::SIGNATURE_PSS, RSA::SIGNATURE_RELAXED_PKCS1];
/**
* 加解密填充方式(RSA::ENCRYPTION_OAEP, RSA::ENCRYPTION_PKCS1, RSA::ENCRYPTION_NONE).
* @see https://phpseclib.com/docs/rsa#encryption--decryption
*/
private int $encryptPadding;
/**
* 私钥加签填充方式.
* @see https://phpseclib.com/docs/rsa#creating--verifying-signatures
*/
private int $signaturePadding;
/**
* 加解(解密)|加签(验签) 单向HASH算法('md2', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'sha224').
*/
private string $hash;
/**
* 加解(解密)|加签(验签) 单向mgfHASH算法('md2', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'sha224').
*/
private string $mgfHash;
/**
* 构造函数.
*/
public function __construct(
string $password = null,
int $length = 2048,
string $keyFormat = 'PKCS8',
int $encryptPadding = RSA::ENCRYPTION_PKCS1,
string $encryptHash = 'sha256',
string $encryptMgfHash = 'sha256',
int $signaturePadding = RSA::SIGNATURE_PKCS1,
) {
$privateKey = RSA::createKey($length);
if ($password !== null) {
$privateKey = $privateKey->withPassword($password);
}
$this->privateKey = $privateKey;
$this->path = BASE_PATH . '/runtime/openssl/';
$this->keyFormat = $keyFormat;
$this->encryptPadding = $encryptPadding;
$this->hash = $encryptHash;
$this->mgfHash = $encryptMgfHash;
$this->signaturePadding = $signaturePadding;
if (! is_dir($this->path)) {
mkdir(iconv('GBK', 'UTF-8', $this->path), 0755);
}
// 初步创建并保存秘钥
$this->createKeys();
}
/**
* 公钥加密.
*/
public function publicKeyEncrypt(string|array $message): string
{
$privateKey = $this->buildPrivateKey('encrypt');
$message = is_array($message) ? json_encode($message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $message;
return base64_encode($privateKey->getPublicKey()->encrypt($message));
}
/**
* 私钥解密.
*/
public function privateKeyDecrypt(string $encryptText): array|string
{
$privateKey = $this->buildPrivateKey('encrypt');
$decryptData = $privateKey->decrypt(base64_decode($encryptText));
return json_decode($decryptData, true) ?? $decryptData;
}
/**
* 私钥加签.
*/
public function privateKeySign(string|array $message): string
{
$privateKey = $this->buildPrivateKey('signature');
$message = is_array($message) ? json_encode($message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $message;
return base64_encode($privateKey->sign($message));
}
/**
* 公钥验签.
*/
public function publicKeyVerifySign(string|array $message, string $signature): bool
{
$privateKey = $this->buildPrivateKey('signature');
$message = is_array($message) ? json_encode($message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $message;
return $privateKey->getPublicKey()->verify($message, base64_decode($signature));
}
/**
* 创建公私钥对(不存在才会创建, 不会覆盖).
*/
private function createKeys(): void
{
[$publicKey, $privateKey] = [
$this->path . 'public.pem',
$this->path . 'private.pem',
];
if (! file_exists($publicKey) || ! file_exists($privateKey)) {
file_put_contents($privateKey, $this->privateKey->toString($this->keyFormat));
file_put_contents($publicKey, $this->privateKey->getPublicKey()->toString($this->keyFormat));
}
}
/**
* 构建不同场景下的合法私钥.
* @param string $mode
* @return PrivateKey|RSA
*/
private function buildPrivateKey(string $mode = 'encrypt'): PrivateKey|RSA
{
/** @var PrivateKey|RSA $privateKey */
$privateKey = RSA::loadPrivateKey(file_get_contents($this->path . 'private.pem'));
if ($mode === 'encrypt') {
// 加签填充方式
$privateKey = in_array($this->signaturePadding, $this->availableSignaturePadding) ?
$privateKey->withPadding($this->signaturePadding) : $privateKey->withPadding(RSA::SIGNATURE_PKCS1);
} else {
// 加密填充方式
$privateKey = in_array($this->encryptPadding, $this->availableEncryptPadding) ?
$privateKey->withPadding($this->encryptPadding) : $privateKey->withPadding(RSA::ENCRYPTION_PKCS1);
}
// HASH方式
$privateKey = in_array($this->hash, $this->availableHash) ?
$privateKey->withHash($this->hash) : $privateKey->withHash('sha256');
// MGF HASH方式(只有padding为RSA::ENCRYPTION_OAEP可用)
return in_array($this->mgfHash, $this->availableHash) ?
$privateKey->withMGFHash($this->mgfHash) : $privateKey->withHash('sha256');
}
}
Usage
See:https://try8.cn/tool/cipher/rsa
Generate Key Pair
/**
* 创建RSA公私钥秘钥对.
* @param EncryptRequest $request 请求验证器
* @return array|MessageInterface|ResponseInterface 响应
*/
#[PostMapping(path: 'rsa/create')]
#[Scene(scene: 'rsa_create')]
public function createRsa(EncryptRequest $request): array|MessageInterface|ResponseInterface
{
$keyFormat = $request->input('key_format'); // PKCS1 || PKCS8
$keyLength = $request->input('key_length'); // 1024 || 2048 || 3072 || 4096
$isDownload = $request->input('is_download', false);
$certificatePassword = $request->input('certificate_password');
$result = $this->service->createRSA($keyFormat, $keyLength, $certificatePassword, $isDownload);
if (is_array($result)) {
return $this->result->setData($result)->getResult();
}
return $result;
}
/**
* 获取RSA公私钥秘钥对.
* @param string $keyFormat 秘钥格式
* @param int $keyLen 密钥长度
* @param null|string $certificatePassword 证书密码
* @param bool $isDownload 是否下载公私钥
* @return array|MessageInterface|ResponseInterface 响应
*/
public function createRSA(
string $keyFormat,
int $keyLen = 2048,
string $certificatePassword = null,
bool $isDownload = false,
): array|MessageInterface|ResponseInterface {
$rsaInstance = new RsaWithPHPSeclib($certificatePassword, $keyLen, $keyFormat);
if ($isDownload) {
$certificateList = [
BASE_PATH . '/runtime/openssl/private.pem',
BASE_PATH . '/runtime/openssl/public.pem',
];
$zipName = 'rsa-' . Carbon::now()->timestamp . '.zip';
$zipPath = BASE_PATH . '/runtime/openssl/' . $zipName;
Zip::compress($zipPath, $certificateList);
$response = new Response();
return $response->withHeader('content-description', 'File Transfer')
->withHeader('content-type', 'application/zip')
->withHeader('content-disposition', 'attachment; filename="' . $zipName . '"')
->withHeader('content-transfer-encoding', 'binary')
->withBody(new SwooleStream((string) file_get_contents($zipPath)));
}
return [
'public_key' => $rsaInstance->getPublicKeyString(),
'private_key' => $rsaInstance->getPrivateKeyString(),
];
}
Public Key Encryption
/**
* 公钥加密.
* @param EncryptRequest $request 请求验证器
* @return array ['code' => '200', 'msg' => 'ok', 'status' => true, 'data' => []]
*/
#[PostMapping(path: 'rsa/public_key/encrypt')]
#[Scene(scene: 'encrypt_decrypt')]
public function publicKeyEncrypt(EncryptRequest $request): array
{
[$key, $padding, $hash, $mgfHash, $data] = [
$request->input('key'), // 公钥
$request->input('padding'), // 加密填充方式
$request->input('hash'), // 哈希算法
$request->input('mgf_hash', ''), // mgf哈希算法 当加密填充模式为OAEP或签名填充模式为PSS使用
$request->input('data'), // 待加密数据
];
$encryptResult = $this->service->publicKeyEncrypt($key, $padding, $hash, $data, $mgfHash);
return $this->result->setData(['encrypt_result' => $encryptResult])->getResult();
}
/**
* 公钥加密.
* @param string $key 公钥
* @param string $encryptPadding 加解密填充方式
* @param string $hash 单向哈希
* @param array|string $data 待加密数据
* @param string $mgfHash 当加密填充模式为OAEP或签名填充模式为PSS使用
* @return string 加密结果
*/
public function publicKeyEncrypt(
string $key,
string $encryptPadding,
string $hash,
string|array $data,
string $mgfHash = 'sha256',
): string {
/** @var PublicKey|RSA $publicKey */
$publicKey = RSA::loadPublicKey($key);
// 是否需要mgfHash
if ($encryptPadding === 'ENCRYPTION_OAEP') {
$publicKey = $publicKey->withHash($hash)->withMGFHash($mgfHash);
} else {
$publicKey = $publicKey->withHash($hash);
}
$encryptPadding = match ($encryptPadding) {
'ENCRYPTION_OAEP' => RSA::ENCRYPTION_OAEP,
'ENCRYPTION_PKCS1' => RSA::ENCRYPTION_PKCS1,
default => RSA::ENCRYPTION_NONE,
};
$publicKey = $publicKey->withPadding($encryptPadding);
$data = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $data;
return base64_encode($publicKey->encrypt($data));
}
Private Key Decryption
/**
* 私钥解密.
* @param EncryptRequest $request 请求验证器
* @return array ['code' => '200', 'msg' => 'ok', 'status' => true, 'data' => []]
*/
#[PostMapping(path: 'rsa/private_key/decrypt')]
#[Scene(scene: 'encrypt_decrypt')]
public function privateKeyDecrypt(EncryptRequest $request): array
{
[$key, $padding, $hash, $mgfHash, $data, $password] = [
$request->input('key'), // 私钥
$request->input('padding'), // 加密填充方式
$request->input('hash'), // 哈希算法
$request->input('mgf_hash', ''), // mgf哈希算法 当加密填充模式为OAEP或签名填充模式为PSS使用
$request->input('data'), // 待解密数据
$request->input('password', ''), // 证书密码
];
$decryptResult = $this->service->privateKeyDecrypt($key, $padding, $hash, $data, $mgfHash, $password);
return $this->result->setData(['decrypt_result' => $decryptResult])->getResult();
}
/**
* 私钥解密.
* @param string $key 私钥
* @param string $encryptPadding 加解密填充方式
* @param string $hash 单向哈希
* @param array|string $data 待加密数据
* @param string $mgfHash 当加密填充模式为OAEP或签名填充模式为PSS使用
* @param string $password 证书密码
* @return mixed 解密结果
*/
public function privateKeyDecrypt(
string $key,
string $encryptPadding,
string $hash,
string|array $data,
string $mgfHash = 'sha256',
string $password = '',
): mixed {
/** @var PrivateKey|RSA $privateKey */
$privateKey = RSA::loadPrivateKey($key, $password);
// 是否需要mgfHash
if ($encryptPadding === 'ENCRYPTION_OAEP') {
$privateKey = $privateKey->withHash($hash)->withMGFHash($mgfHash);
} else {
$privateKey = $privateKey->withHash($hash);
}
$encryptPadding = match ($encryptPadding) {
'ENCRYPTION_OAEP' => RSA::ENCRYPTION_OAEP,
'ENCRYPTION_PKCS1' => RSA::ENCRYPTION_PKCS1,
default => RSA::ENCRYPTION_NONE,
};
$privateKey = $privateKey->withPadding($encryptPadding);
$decrypt = $privateKey->decrypt(base64_decode($data));
return json_decode($decrypt, true) ?? $decrypt;
}
Private Key Signing
/**
* 私钥加签.
* @param EncryptRequest $request 请求验证器
* @return array ['code' => '200', 'msg' => 'ok', 'status' => true, 'data' => []]
*/
#[PostMapping(path: 'rsa/private_key/sign')]
#[Scene(scene: 'sign')]
public function privateKeySign(EncryptRequest $request): array
{
[$key, $padding, $hash, $mgfHash, $data, $password] = [
$request->input('key'), // 私钥
$request->input('padding'), // 加密填充方式
$request->input('hash'), // 哈希算法
$request->input('mgf_hash', ''), // mgf哈希算法 当加密填充模式为OAEP或签名填充模式为PSS使用
$request->input('data'), // 待解密数据
$request->input('password', ''), // 证书密码
];
$sign = $this->service->privateKeySign($key, $padding, $hash, $data, $mgfHash, $password);
return $this->result->setData(['sign' => $sign])->getResult();
}
/**
* 私钥签名.
* @param string $key 私钥
* @param string $signPadding 签名填充方式
* @param string $hash 单向哈希
* @param array|string $data 待签名数据
* @param string $mgfHash 当加密填充模式为OAEP或签名填充模式为PSS使用
* @param string $password 证书密码
* @return string 签名
*/
public function privateKeySign(
string $key,
string $signPadding,
string $hash,
string|array $data,
string $mgfHash = 'sha256',
string $password = '',
): string {
/** @var PrivateKey|RSA $privateKey */
$privateKey = RSA::loadPrivateKey($key, $password);
// RSA::SIGNATURE_PKCS1, RSA::SIGNATURE_PSS
// 是否需要mgfHash
if ($signPadding === 'SIGNATURE_PSS') {
$privateKey = $privateKey->withHash($hash)->withMGFHash($mgfHash);
} else {
$privateKey = $privateKey->withHash($hash);
}
$signPadding = match ($signPadding) {
'SIGNATURE_PKCS1' => RSA::SIGNATURE_PKCS1,
'SIGNATURE_PSS' => RSA::SIGNATURE_PSS,
};
$privateKey = $privateKey->withPadding($signPadding);
$data = is_array($data) ? json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $data;
return base64_encode($privateKey->sign($data));
}
Public Key Verification
/**
* 公钥验签.
* @param EncryptRequest $request 请求验证器
* @return array ['code' => '200', 'msg' => 'ok', 'status' => true, 'data' => []]
*/
#[PostMapping(path: 'rsa/public_key/verify')]
#[Scene(scene: 'sign')]
public function publicKeyVerifySign(EncryptRequest $request): array
{
[$key, $padding, $hash, $mgfHash, $data, $sign] = [
$request->input('key'), // 公钥
$request->input('padding'), // 签名填充方式
$request->input('hash'), // 哈希算法
$request->input('mgf_hash', ''), // mgf哈希算法 当加密填充模式为OAEP或签名填充模式为PSS使用
$request->input('data'), // 加签源数据
$request->input('sign', ''), // 签名
];
$verifyResult = $this->service->publicKeyVerifySign($key, $padding, $hash, $data, $sign, $mgfHash);
return $this->result->setData(['verify_sign_result' => $verifyResult])->getResult();
}
/**
* 公钥验签.
* @param string $key 公钥
* @param string $signPadding 签名填充方式
* @param string $hash 单向哈希
* @param array|string $data 加签的原数据
* @param string $sign 签名
* @param string $mgfHash 当加密填充模式为OAEP或签名填充模式为PSS使用
* @return bool 验签结果
*/
public function publicKeyVerifySign(
string $key,
string $signPadding,
string $hash,
string|array $data,
string $sign,
string $mgfHash = 'sha256',
): bool {
/** @var PublicKey|RSA $publicKey */
$publicKey = RSA::loadPublicKey($key);
// 是否需要mgfHash
if ($signPadding === 'SIGNATURE_PSS') {
$publicKey = $publicKey->withHash($hash)->withMGFHash($mgfHash);
} else {
$publicKey = $publicKey->withHash($hash);
}
$signPadding = match ($signPadding) {
'SIGNATURE_PKCS1' => RSA::SIGNATURE_PKCS1,
'SIGNATURE_PSS' => RSA::SIGNATURE_PSS,
};
$publicKey = $publicKey->withPadding($signPadding);
$data = is_array($data) ? json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $data;
return $publicKey->verify($data, base64_decode($sign));
}