如何从密钥文件判断是PKCS8还是PKCS1格式
不同的签名验签代码,对密钥格式有不同的要求:
比如JAVA一般使用PKCS8格式的密钥,其他语言一般使用PKCS1格式的密钥。
区分小技巧:
开头为"-----BEGIN PRIVATE KEY-----"的密钥格式为PKCS8,
开头为"-----BEGIN RSA PRIVATE KEY-----"的密钥格式为PKCS1
不同的签名验签代码,对密钥格式有不同的要求:
比如JAVA一般使用PKCS8格式的密钥,其他语言一般使用PKCS1格式的密钥。
开头为"-----BEGIN PRIVATE KEY-----"的密钥格式为PKCS8,
开头为"-----BEGIN RSA PRIVATE KEY-----"的密钥格式为PKCS1
$certificateCAcerContent = file_get_contents($filePath);
$certificateCApemContent = '-----BEGIN CERTIFICATE-----' . PHP_EOL
. chunk_split(base64_encode($certificateCAcerContent), 64, PHP_EOL)
. '-----END CERTIFICATE-----' . PHP_EOL;
$pkcs12 = file_get_contents($filePath);
$res = openssl_pkcs12_read($pkcs12, $certs, '密码');
一般客户会提供两个文件.pfx
结尾的(公钥加私钥文件).cer
结尾的文件 (包含公钥文件)
私钥一般都是pfx
格式(私钥用来加密生成签名发送报文)
公钥是cer
格式(公钥用来验证返回报文里的签名)。
但是php
里openssl
只能用pem
格式,需要把这两个文件转化成pem
的:
//filePath为pfx文件路径
function signfrompfx($strData, $filePath, $keyPass)
{
if (!file_exists($filePath)) {
return false;
}
$pkcs12 = file_get_contents($filePath);
if (openssl_pkcs12_read($pkcs12, $certs, $keyPass)) {
$privateKey = $certs['pkey'];
$publicKey = $certs['cert'];
$signedMsg = "";
if (openssl_sign($strData, $signedMsg, $privateKey)) {
$signedMsg = bin2hex($signedMsg);//这个看情况。有些不需要转换成16进制,有些需要base64编码。看各个接口
return $signedMsg;
} else {
return '';
}
} else {
return '0';
}
}
function verifyReturn($data,$signature,$filePath){
/**
* filePath为crt,cert文件路径。x.509证书
* cer to dem, Convert .cer to .pem, cURL uses .pem
*/
$certificateCAcerContent = file_get_contents($filePath);
$certificateCApemContent =
'-----BEGIN CERTIFICATE-----'.PHP_EOL
.chunk_split(base64_encode($certificateCAcerContent), 64, PHP_EOL)
.'-----END CERTIFICATE-----'.PHP_EOL;
$pubkeyid = openssl_get_publickey($certificateCApemContent);
$len = strlen($signature);
$signature= pack("H" . $len, $signature); //Php-16进制转换为2进制,看情况。有些接口不需要,有些需要base64解码
$data=str_replace('<?xml version=\"1.0\" encoding=\"GBK\"?>', '<?xml version="1.0" encoding="GBK"?>', $data);//这个看情况。
// state whether signature is okay or not
return openssl_verify($data, $signature, $pubkeyid);
}
openssl x509 -inform der -in pub.cer -out pub.pem
转自:https://blog.csdn.net/weixin_38626872/article/details/107784610
[root@xmg-hk ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
[root@xmg-hk ~]# openssl version
OpenSSL 1.0.2k-fips 26 Jan 2017
其他版本可参考下载: https://www.openssl.org/source/openssl-1.1.1k.tar.gz
cd /usr/local/
wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz
tar -zcvf openssl-1.1.1k.tar.gz
mv /usr/bin/openssl /usr/bin/openssl.bak
mv /usr/include/openssl /usr/include/openssl.bak
cd /usr/local/openssl-1.1.1k
./config --prefix=/usr/local/openssl
make && make install
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/openssl/include/openssl /usr/include/openssl
echo “/usr/local/openssl/lib” >> /etc/ld.so.conf
ldconfig -v
[root@xmg-hk ~]# openssl version
OpenSSL 1.1.1k 25 Mar 2021
说明:需要先进行备份,备份需要在"建立链接"操作以前完成。
生成RSA密钥key.pem(也称私钥,密钥对)。
从key.pem中导出公钥pubkey.pem。
使用公钥pubkey.pem对文件test.txt进行加密,得到密文test.enc。
使用私钥key.pem对test.enc进行解密,得到译文test.dec。
对比test.txt和test.dec,应该是一样的。
使用key.pem对test.txt进行签名,得到test.sig。
使用pubkey.pem对test.txt的签名test.sig进行验签。
key.pem ====导出=====> pubkey.pem
pubkey.pem + text.txt ===公钥加密===> test.enc
key.pem + test.enc ===私钥解密===> test.dec
key.pem + test.txt ===私钥签名===> test.sig
pubkey.pem + test.txt + test.sig ==> 验证签名
# 生成密钥对
openssl genrsa -out key.pem
# 导出公钥
openssl rsa -in key.pem -pubout -out pubkey.pem
参数说明:
-out: 指定输出的文件
-in: 指定输入的文件
-pubout: 指定输出公钥。如果不加该参数,默认输出的为私钥
# 生成一个待加密的测试文件
echo "hello, world" > test.txt
# 公钥加密
openssl rsautl -encrypt -pubin -inkey pubkey.pem -in test.txt -out test.enc
openssl rsautl -decrypt -inkey key.pem -in test.enc -out test.dec
参数说明:
-encrypt: 加密操作
-decrypt: 解密操作
-pubin: 指定输入公钥。如果不加该参数,则认为输入的为私钥
-inkey: 密钥文件
# 如果没有任何输出,则文件相同
cmp test.txt test.dec
# 签名
openssl dgst -sign key.pem -sha256 -out test.sig test.txt
# 验签
openssl dgst -verify pubkey.pem -sha256 -signature test.sig test.txt
参数说明:
-sign: 使用私钥签名
-verify: 使用公钥验签
-sha256: 摘要算法,也可以为md5/sha1/sha384/sha512等,签名验签使用的摘要算法应相同
-signature: 待验证的签名文件
# 查看私钥信息
openssl rsa -in key.pem -noout -text
# 查看公钥信息
openssl rsa -pubin -in pubkey.pem -noout -text
参数说明:
-noout: 不打印密钥数据
-text: 以文本方式打印密钥信息
强大的安全套接字层密码库
OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。在OpenSSL被曝出现严重安全漏洞后,发现多数通过SSL协议加密的网站使用名为OpenSSL的开源软件包。由于这是互联网应用最广泛的安全传输方法,被网银、在线支付、电商网站、门户网站、电子邮件等重要网站广泛使用,所以该漏洞影响范围广大。
OpenSSL有两种运行模式:交互模式和批处理模式。
直接输入openssl回车进入交互模式,输入带命令选项的openssl进入批处理模式。
OpenSSL整个软件包大概可以分成三个主要的功能部分:密码算法库、SSL协议库以及应用程序。OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。
OpenSSL一共提供了8种对称加密算法,其中7种是分组加密算法,仅有的一种流加密算法是RC4。这7种分组加密算法分别是AES、DES、Blowfish、CAST、IDEA、RC2、RC5,都支持电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)和输出反馈模式(OFB)四种常用的分组密码加密模式。其中,AES使用的加密反馈模式(CFB)和输出反馈模式(OFB)分组长度是128位,其它算法使用的则是64位。事实上,DES算法里面不仅仅是常用的DES算法,还支持三个密钥和两个密钥3DES算法。
OpenSSL一共实现了4种非对称加密算法,包括DH算法、RSA算法、DSA算法和椭圆曲线算法(EC)。DH算法一般用于密钥交换。RSA算法既可以用于密钥交换,也可以用于数字签名,当然,如果你能够忍受其缓慢的速度,那么也可以用于数据加密。DSA算法则一般只用于数字签名。
OpenSSL实现了5种信息摘要算法,分别是MD2、MD5、MDC2、SHA(SHA1)和RIPEMD。SHA算法事实上包括了SHA和SHA1两种信息摘要算法,此外,OpenSSL还实现了DSS标准中规定的两种信息摘要算法DSS和DSS1。
密钥和证书管理是PKI的一个重要组成部分,OpenSSL为之提供了丰富的功能,支持多种标准。
首先,OpenSSL实现了ASN.1的证书和密钥相关标准,提供了对证书、公钥、私钥、证书请求以及CRL等数据对象的DER、PEM和BASE64的编解码功能。OpenSSL提供了产生各种公开密钥对和对称密钥的方法、函数和应用程序,同时提供了对公钥和私钥的DER编解码功能。并实现了私钥的PKCS#12和PKCS#8的编解码功能。OpenSSL在标准中提供了对私钥的加密保护功能,使得密钥可以安全地进行存储和分发。
在此基础上,OpenSSL实现了对证书的X.509标准编解码、PKCS#12格式的编解码以及PKCS#7的编解码功能。并提供了一种文本数据库,支持证书的管理功能,包括证书密钥产生、请求产生、证书签发、吊销和验证等功能。
事实上,OpenSSL提供的CA应用程序就是一个小型的证书管理中心(CA),实现了证书签发的整个流程和证书管理的大部分机制。
几乎所有 Linux 发行版都包含 openssl。我们可以利用它的随机功能来生成可以用作密码的随机字母字符串。
openssl rand -base64 10
# nU9LlHO5nsuUvw==
nU9LlHO5nsuUvw==
用SHA1算法计算文件file.txt的哈西值,输出到stdout:
# openssl dgst -sha1 file.txt
用SHA1算法计算文件file.txt的哈西值,输出到文件digest.txt:
# openssl sha1 -out digest.txt file.txt
用DSS1(SHA1)算法为文件file.txt签名,输出到文件dsasign.bin。签名的private key必须为DSA算法产生的,保存在文件dsakey.pem中。
# openssl dgst -dss1 -sign dsakey.pem -out dsasign.bin file.txt
用dss1算法验证file.txt的数字签名dsasign.bin,验证的private key为DSA算法产生的文件dsakey.pem。
# openssl dgst -dss1 -prverify dsakey.pem -signature dsasign.bin file.txt
用sha1算法为文件file.txt签名,输出到文件rsasign.bin,签名的private key为RSA算法产生的文件rsaprivate.pem。
# openssl sha1 -sign rsaprivate.pem -out rsasign.bin file.txt
用sha1算法验证file.txt的数字签名rsasign.bin,验证的public key为RSA算法生成的rsapublic.pem。
# openssl sha1 -verify rsapublic.pem -signature rsasign.bin file.txt
对称加密应用例子,用DES3算法的CBC模式加密文件plaintext.doc,加密结果输出到文件ciphertext.bin。
# openssl enc -des3 -salt -in plaintext.doc -out ciphertext.bin
用DES3算法的OFB模式解密文件ciphertext.bin,提供的口令为trousers,输出到文件plaintext.doc。注意:因为模式不同,该命令不能对以上的文件进行解密。
# openssl enc -des-ede3-ofb -d -in ciphertext.bin -out plaintext.doc -pass pass:trousers
用Blowfish的CFB模式加密plaintext.doc,口令从环境变量PASSWORD中取,输出到文件ciphertext.bin。
# openssl bf-cfb -salt -in plaintext.doc -out ciphertext.bin -pass env:PASSWORD
给文件ciphertext.bin用base64编码,输出到文件base64.txt。
# openssl base64 -in ciphertext.bin -out base64.txt
用RC5算法的CBC模式加密文件plaintext.doc,输出到文件ciphertext.bin,salt、key和初始化向量(iv)在命令行指定。
# openssl rc5 -in plaintext.doc -out ciphertext.bin -S C62CB1D49F158ADC -iv E9EDACA1BD7090C6 -K 89D4B1678D604FAA3DBFFD030A314B29
使用生成因子2和随机的1024-bit的素数产生D0ffie-Hellman参数,输出保存到文件dhparam.pem
# openssl dhparam -out dhparam.pem -2 1024
从dhparam.pem中读取Diffie-Hell参数,以C代码的形式,输出到stdout。
# openssl dhparam -in dhparam.pem -noout -C
生成1024位DSA参数集,并输出到文件dsaparam.pem。
# openssl dsaparam -out dsaparam.pem 1024
使用参数文件dsaparam.pem生成DSA私钥匙,采用3DES加密后输出到文件dsaprivatekey.pem
# openssl gendsa -out dsaprivatekey.pem -des3 dsaparam.pem
使用私钥匙dsaprivatekey.pem生成公钥匙,输出到dsapublickey.pem
# openssl dsa -in dsaprivatekey.pem -pubout -out dsapublickey.pem
从dsaprivatekey.pem中读取私钥匙,解密并输入新口令进行加密,然后写回文件dsaprivatekey.pem
# openssl dsa -in dsaprivatekey.pem -out dsaprivatekey.pem -des3 -passin
产生1024位RSA私匙,用3DES加密它,口令为trousers,输出到文件rsaprivatekey.pem
# openssl genrsa -out rsaprivatekey.pem -passout pass:trousers -des3 1024
从文件rsaprivatekey.pem读取私匙,用口令trousers解密,生成的公钥匙输出到文件rsapublickey.pem
# openssl rsa -in rsaprivatekey.pem -passin pass:trousers -pubout -out rsapubckey.pem
用公钥匙rsapublickey.pem加密文件plain.txt,输出到文件cipher.txt
# openssl rsautl -encrypt -pubin -inkey rsapublickey.pem -in plain.txt -out cipher.txt
使用私钥匙rsaprivatekey.pem解密密文cipher.txt,输出到文件plain.txt
# openssl rsautl -decrypt -inkey rsaprivatekey.pem -in cipher.txt -out plain.txt
用私钥匙rsaprivatekey.pem给文件plain.txt签名,输出到文件signature.bin
# openssl rsautl -sign -inkey rsaprivatekey.pem -in plain.txt -out signature.bin
用公钥匙rsapublickey.pem验证签名signature.bin,输出到文件plain.txt
# openssl rsautl -verify -pubin -inkey rsapublickey.pem -in signature.bin -out plain
从X.509证书文件cert.pem中获取公钥匙,用3DES加密mail.txt,输出到文件mail.enc
# openssl smime -encrypt -in mail.txt -des3 -out mail.enc cert.pem
从X.509证书文件cert.pem中获取接收人的公钥匙,用私钥匙key.pem解密S/MIME消息mail.enc,结果输出到文件mail.txt
# openssl smime -decrypt -in mail.enc -recip cert.pem -inkey key.pem -out mail.txt
cert.pem为X.509证书文件,用私匙key,pem为mail.txt签名,证书被包含在S/MIME消息中,输出到文件mail.sgn
# openssl smime -sign -in mail.txt -signer cert.pem -inkey key.pem -out mail.sgn
验证S/MIME消息mail.sgn,输出到文件mail.txt,签名者的证书应该作为S/MIME消息的一部分包含在mail.sgn中
# openssl smime -verify -in mail.sgn -out mail.txt
openssl version -a
openssl help
openssl genrsa -aes128 -out fd.key 2048 # pem format
openssl rsa -text -in fd.key
对称加密: 在加密和解密过程中使用相同的密钥, 或是两个可以简单地相互推算的密钥的加密算法.
非对称加密: 也称为公开加密, 它需要一个密钥对, 一个是公钥, 一个是私钥, 一个负责加密, 一个负责解密.
对称加密在性能上要优于非对称加密, 但是安全性低于非对称加密.
PHP 7.1 之后的对称加密和非对称加密都需要借助 openssl 扩展实现. mcrypt 库已经被移除.
openssl_get_cipher_methods() : 返回 openssl 支持的所有加密方式.
openssl_encrypt($data, $method, $key, $options = 0, $iv = '') : 以指定方式 method 和密钥 key 加密 data, 返回 false 或加密后的数据.
data : 明文
method : 加密算法
key : 密钥
options :
0 : 自动对明文进行 padding, 返回的数据经过 base64 编码.
1 : OPENSSL_RAW_DATA,自动对明文进行 padding, 但返回的结果未经过 base64 编码.
2 : OPENSSL_ZERO_PADDING,对明文进行 0 填充, 返回的结果经过 base64 编码. 但是, openssl 不推荐 0 填充的方式,即使选择此项也不会自动进行 padding, 仍需手动 padding.
iv : 非空的初始化向量, 不使用此项会抛出一个警告. 如果未进行手动填充, 则返回加密失败.
openssl_decrypt($data, $method, $key, $options = 0, $iv = '') : 解密数据.
openssl_cipher_iv_length($method) : 获取 method 要求的初始化向量的长度.
openssl_random_pseudo_bytes($length) : 生成指定长度的伪随机字符串.
hash_mac($method, $data, $key, $raw_out) : 生成带有密钥的哈希值.
method : 加密算法
data : 明文
key : 密钥
raw_output :
TRUE : 输出原始二进制数据
FALSE : 输出长度固定的小写 16 进制字符串
DES 加密的密钥长度为 64 bit, 实际应用中有效使用的是 56 位, 剩余 8 位作为奇偶校验位. 如果密钥长度不足 8 个字节, 将会使用 \0 补充到 8 个字节. 如密钥为 "12345", 其加密后的密文与密钥 "12345\0\0\0" 加密后的密文相同. 明文按 64 bit ( UTF-8 下为 8 个字节长度 ) 进行分组, 每 64 位分成一组 ( 最后一组不足 64 位的需要填充数据 ), 分组后的明文组和密钥按位替代或交换的方法形成密文组。
<?php
class DES {
private $method = 'DES-CBC';
private $key = 'www.phpernote.com';
public function __construct($key = '') {
// 密钥长度不能超过64bit(UTF-8下为8个字符长度),超过64bit不会影响程序运行,但有效使用的部分只有64bit,多余部分无效,可通过openssl_error_string() 查看错误提示
$key && $this->key = $key;
}
public function encrypt($plaintext) {
// 生成加密所需的初始化向量, 加密时缺失iv会抛出一个警告
$ivlen = openssl_cipher_iv_length($this->method);
$iv = openssl_random_pseudo_bytes($ivlen);
// 按64bit一组填充明文
//$plaintext = $this->padding($plaintext);
// 加密数据. 如果options参数为0, 则不再需要上述的填充操作. 如果options参数为1, 也不需要上述的填充操作, 但是返回的密文未经过base64编码. 如果options参数为2, 虽然PHP说明是自动0填充, 但实际未进行填充, 必须需要上述的填充操作进行手动填充. 上述手动填充的结果和options为0和1是自动填充的结果相同.
$ciphertext = openssl_encrypt($plaintext, $this->method, $this->key, 1, $iv);
// 生成hash
$hash = hash_hmac('sha256', $ciphertext, $this->key, false);
return base64_encode($iv . $hash . $ciphertext);
}
public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
// 从密文中获取iv
$ivlen = openssl_cipher_iv_length($this->method);
$iv = substr($ciphertext, 0, $ivlen);
// 从密文中获取hash
$hash = substr($ciphertext, $ivlen, 64);
// 获取原始密文
$ciphertext = substr($ciphertext, $ivlen + 64);
// hash校验
if (hash_equals($hash, hash_hmac('sha256', $ciphertext, $this->key, false))) {
// 解密数据
$plaintext = openssl_decrypt($ciphertext, $this->method, $this->key, 1, $iv) ?? false;
// 去除填充数据. 加密时进行了填充才需要去填充
//$plaintext = $plaintext ? $this->unpadding($plaintext) : false;
return $plaintext;
}
return '解密失败';
}
// 按64bit一组填充数据
private function padding($plaintext) {
$padding = 8 - (strlen($plaintext) % 8);
$chr = chr($padding);
return $plaintext . str_repeat($chr, $padding);
}
private function unpadding($ciphertext) {
$chr = substr($ciphertext, -1);
$padding = ord($chr);
if ($padding > strlen($ciphertext)) {
return false;
}
if (strspn($ciphertext, $chr, -1 * $padding, $padding) !== $padding) {
return false;
}
return substr($ciphertext, 0, -1 * $padding);
}
}
$aes = new DES('yhm');
$string = $aes->encrypt('www.phpernote.com');
echo '明文:www.phpernote.com', '<br />';
echo '密文:', $string, '<br />';
//密文:9I5V7XirG0ZiMWQ0MmJlZWY0MWI5ZjNkNzE3YmIwZDRiYTlkOWI3NTc2ZjIyMzc5MmEzOTkxYzIyNzY1MzU3NjljMWFlZjIztd1jrvlOS6tDUaMphQBqusOVjBnhgUdV
//注意这里的密文每次输出都不一样
echo '解密:', $aes->decrypt($string);
//解密:www.phpernote.com
AES 加密的分组长度是 128 位, 即每个分组为 16 个字节 ( 每个字节 8 位 ). 密钥的长度根据加密方式的不同可以是 128 位, 192 位, 256 位. 与 DES 加密一样. 密钥长度超过指定长度时, 超出部分无效. 密钥长度不足时, 会自动以\0
补充到指定长度.
AES 密钥长度 ( 位 ) 分组长度 ( 位 )
AES-128 128 128
AES-192 192 128
AES-256 256 128
<?php
class AES {
private $key = 'www.phpernote.com';
private $method = 'aes-128-cbc';
public function __construct($key = '') {
extension_loaded('openssl') or die('未启用 OPENSSL 扩展');
$key && $this->key = $key;
}
public function encrypt($plaintext) {
if (!in_array($this->method, openssl_get_cipher_methods())) {
die('不支持该加密算法!');
}
// options为1, 不需要手动填充
//$plaintext = $this->padding($plaintext);
// 获取加密算法要求的初始化向量的长度
$ivlen = openssl_cipher_iv_length($this->method);
// 生成对应长度的初始化向量. aes-128模式下iv长度是16个字节, 也可以自由指定.
$iv = openssl_random_pseudo_bytes($ivlen);
// 加密数据
$ciphertext = openssl_encrypt($plaintext, $this->method, $this->key, 1, $iv);
$hmac = hash_hmac('sha256', $ciphertext, $this->key, false);
return base64_encode($iv . $hmac . $ciphertext);
}
public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($this->method);
$iv = substr($ciphertext, 0, $ivlen);
$hmac = substr($ciphertext, $ivlen, 64);
$ciphertext = substr($ciphertext, $ivlen + 64);
$verifyHmac = hash_hmac('sha256', $ciphertext, $this->key, false);
if (hash_equals($hmac, $verifyHmac)) {
$plaintext = openssl_decrypt($ciphertext, $this->method, $this->key, 1, $iv) ?? false;
return $plaintext;
} else {
die('数据被修改!');
}
}
}
$aes = new AES('yhm');
$string = $aes->encrypt('www.phpernote.com');
echo '明文:www.phpernote.com', '<br />';
echo '密文:', $string, '<br />';
//密文:XJmLnBp3L8tOUYswd7+s3jEzNDZlMjMxNTkwMjNiMDI0MmU2ZWMzNTE4MDk5ZTZkYzc4ODlhMWQ1NWM1YjUzNDNlNjcxMjk1MzI0M2M1ODe5M9dAO4mNXj1rHsypG+u9G8OQBMMJMg8fkmkflvC43g==
//注意这里的密文每次输出都不一样
echo '解密:', $aes->decrypt($string);
//解密:www.phpernote.com
$res = openssl_pkey_new([array $config]) : 生成一个新的私钥和公钥对. 通过配置数组, 可以微调密钥的生成.
digest_alg : 摘要或签名哈希算法.
private_key_bits : 指定生成的私钥的长度.
private_key_type : 指定生成私钥的算法. 默认 OPENSSL_KEYTYPE_RSA, 可指定 OPENSSL_KEYTYPE_DSA, OPENSSL_KEYTYPE_DH, OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC.
config : 自定义 openssl.conf 文件的路径.
。。。
openssl_pkey_free($res) : 释放有 openssl_pkey_new() 创建的私钥.
openssl_get_md_methods() : 获取可用的摘要算法.
openssl_pkey_export_to_file($res, $outfilename) : 将 ASCII 格式 ( PEM 编码 ) 的密钥导出到文件中. 使用相对路径时, 是相对服务器目录, 而非当前所在目录.
openssl_pkey_export($res, &$out) : 提取 PEM 格式私钥字符串.
openssl_pkey_get_details($res) : 返回包含密钥详情的数组.
openssl_get_privatekey($key) : 获取私钥. key 是一个 PEM 格式的文件或一个 PEM 格式的私钥.
openssl_get_publickey($certificate) : 获取公钥. certificate 是一个 X.509 证书资源或一个 PEM 格式的文件或一个 PEM 格式的公钥.
openssl_private_encrypt($data, &$crypted, $privKey [, $padding = OPENSSL_PKCS1_PADDING]) : 使用私钥加密数据, 并保存到 crypted . 其中填充模式为 OPENSSL_PKCS1_PADDING 时, 如果明文长度不够, 加密时会在明文中随机填充数据. 为 OPENSSL_NO_PADDING 时, 如果明文长度不够, 会在明文的头部填充 0 .
openssl_public_decrypt($crypted, &$decrypted, $pubKey [, $padding]) : 使用公钥解密数据, 并保存到 decrypted .
openssl_public_encrypt($data, &$crypted, $pubKey [, $padding]) : 使用公钥加密数据, 并保存到 crypted .
openssl_private_decrypt($crypted, &$decrypted, $privKey [, $padding]) : 使用私钥解密数据, 并保存到 decrypted .
RSA 也是一种分组加密方式, 但明文的分组长度根据选择的填充方式的不同而不同。
<?php
class RSA {
private $private_key; // 私钥
private $public_key; // 公钥
private $private_res; // 私钥资源
private $public_res; // 公钥资源
public function __construct() {
extension_loaded('openssl') or die('未加载 openssl');
// 生成新的公钥和私钥对资源
$config = [
'digest_alg' => 'sha256',
'private_key_bits' => 1204,
'private_key_type' => OPENSSL_KEYTYPE_RSA
];
$res = openssl_pkey_new($config);
if (!$res) {
die('生成密钥对失败');
}
// 获取公钥, 生成公钥资源
$this->public_key = openssl_pkey_get_details($res)['key'];
$this->public_res = openssl_pkey_get_public($this->public_key);
// 获取私钥, 生成私钥资源
openssl_pkey_export($res, $this->private_key);
$this->private_res = openssl_pkey_get_private($this->private_key);
openssl_free_key($res);
}
// 加密
public function encrypt($plaintext) {
$ciphertext = null;
openssl_public_encrypt($plaintext, $ciphertext, $this->public_res);
return $ciphertext;
}
// 解密
public function decrypt($ciphertext) {
$plaintext = null;
openssl_private_decrypt($ciphertext, $plaintext, $this->private_res);
return $plaintext;
}
}
$aes = new RSA('yhm');
$string = $aes->encrypt('www.phpernote.com');
echo '明文:www.phpernote.com', '<br />';
echo '密文:', $string, '<br />';
//密文:密文:O�rZ�v��5 �j��]��9�S��p��(};�ۢf���UejL]��d,� ��v�{ �4'Nq��zr!�iQ��I鷺�T�7 '�����8�$��� �u��������AT����K�U|��� V����܄I���0°���)
//注意这里的密文每次输出都不一样,而且还都是乱码,O(∩_∩)O
echo '解密:', $aes->decrypt($string);
//解密:www.phpernote.com
为了学习方便,转进来的,原文地址:https://www.phpernote.com/php-function/1538.html
用一个密钥,对明文进行加密,同理,同这把密钥,也可以对密文进行解密。
有一对密钥对,使用公钥和私钥进行加密解密可以使用私钥加密,公钥进行解密,同理,也可以使用公钥加密,私钥进行解密。
用md5(或者其它单向加密算法),对内容进行加密出来的字符串,就叫做摘要。
用私钥对摘要进行加密,加密出来签字串,就叫做签名。
用非对称密钥对中的公钥,对签名进行解密操作,解密出来的摘要和原来的对比,就叫做验签。
含有公钥、摘要算法、指纹、签名算法、唯一序列号的特殊文件,就是数字证书。
公钥:网站(服务器)的公钥
颁发者:CA(证书认证机构)
有效期:证书的使用期限
摘要算法:指定的摘要算法,用来计算证书的摘要
指纹:也就是证书的摘要,保证证书的完整性
签名算法:用于生成签名,确保证书是由CA签发
序列号:证书的唯一标识
将网站的公钥,颁发者,有效期,摘要算法 ,哈希算法写入证书。CA 根据证书中的指定的哈希算法,计算出整个证书的摘要,即digest。CA根据签名算法以及上一步计算出来的摘要,CA用自己的私钥对摘要进行加密,生成CA的签名,即signature最后把摘要,签名以及证书的基本信息,一起发布,就得到了网站的证书。
下面以小明与小花的安全通信为例,进行问答模拟,如下:
答:通过加密
答:对称加密因为,单向加密,没办法解密,不行非对称加密,太慢,也不行只有对称加密,速度快
答:小花使用小明的公钥,对密钥S进行加密,传给小明小明用自己的私钥解密
答:使用数字证书具体就是 小明向CA申请一个自己的数字证书,把自己的公钥放在证书中小明将数字证书发送给小花。
答:小花用操作系统内置的CA的数字证书,拿到CA的公钥,用CA的公钥,对数字证书进行验签
验签通过,说明数字证书是真的。
本文摘自:https://www.helloworld.net/p/0011879971 感谢作者的辛苦付出!
引用自Wiki:
ASN.1 is a standard interface description language for defining data structures that can be serialized and deserialized in a cross-platform way.
也就是说ASN.1是一种用来定义数据结构的接口描述语言,它不是二进制,也不是文件格式,看下面的例子你就会明白了:
FooQuestion ::= SEQUENCE {
trackingNumber INTEGER,
question IA5String
}
这段代码定义了FooQuestion的数据结构,下面是FooQuestion这个数据接口的某个具体的数据:
myQuestion FooQuestion ::= SEQUENCE {
trackingNumber 5,
question "Anybody there?"
}
ASN.1用在很多地方比如下面要讲的X.509和PKCS group of cryptography standards。
引用自Wiki:
ASN.1 is closely associated with a set of encoding rules that specify how to represent a data structure as a series of bytes
意思是ASN.1有一套关联的编码规则,这些编码规则用来规定如何用二进制来表示数据结构,DER是其中一种。
把上面的FooQuestion的例子用DER编码则是(16进制):
30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f
翻译过来就是:
30 — type tag indicating SEQUENCE
13 — length in octets of value that follows
02 — type tag indicating INTEGER
01 — length in octets of value that follows
05 — value (5)
16 — type tag indicating IA5String
(IA5 means the full 7-bit ISO 646 set, including variants,
but is generally US-ASCII)
0e — length in octets of value that follows
41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f — value ("Anybody there?")
看到这里你应该对DER编码格式有一个比较好的认识了。
引用自Wiki:
Privacy-Enhanced Mail (PEM) is a de facto file format for storing and sending cryptographic keys, certificates, and other data, based on a set of 1993 IETF standards defining “privacy-enhanced mail.”
PEM是一个用来存储和发送密码学key、证书和其他数据的文件格式的事实标准。许多使用ASN.1的密码学标准(比如X.509和PKCS)都使用DER编码,而DER编码的内容是二进制的,不适合与邮件传输(早期Email不能发送附件),因此使用PEM把二进制内容转换成ASCII码。文件内容的格式像下面这样:
-----BEGIN label-----
BASE64Encoded
-----END label-----
label用来区分内容到底是什么类型,下面会讲。
和PEM相关的RFC有很多,与本文内容相关的则是RFC7468,这里面规定了很多label,不过要注意不是所有label都会有对应的RFC或Specification,这些label只是一种约定俗成。
PEM实际上就是把DER编码的文件的二进制内容用base64编码一下,然后加上-----BEGIN label-----
这样的头和-----END label-----
这样的尾,中间则是DER文件的Base64编码。
我们可以通过下面的方法验证这个结论,先生成一个RSA Private Key,编码格式是PEM格式:
openssl genrsa -out key.pem
查看一下文件内容,可以看到label是RSA PRIVATE KEY
:
----BEGIN RSA PRIVATE KEY-----
BASE64Encoded
-----END RSA PRIVATE KEY-----
然后我们把PEM格式转换成DER格式:
openssl rsa -in key.pem -outform der -out key.der
如果你这个时候看一下文件内容会发现都是二进制。然后我们把DER文件的内容Base64一下,会看到内容和PEM文件一样(忽略头尾和换行):
base64 -i key.der -o key.der.base64
上面讲到的PEM是对证书、密码学Key文件的一种编码方式,下面举例这些证书、密码学Key文件格式:
引用自Wiki :
In cryptography, X.509 is a standard defining the format of public key certificates. X.509 certificates are used in many Internet protocols, including TLS/SSL, which is the basis for HTTPS, the secure protocol for browsing the web.
X.509是一个Public Key Certificates的格式标准,TLS/SSL使用它,TLS/SSL是HTTPS的基础所以HTTPS也使用它。而所谓Public Key Certificates又被称为Digital Certificate 或 Identity Certificate。
An X.509 certificate contains a public key and an identity (a hostname, or an organization, or an individual), and is either signed by a certificate authority or self-signed.
一个X.509 Certificate包含一个Public Key和一个身份信息,它要么是被CA签发的要么是自签发的。
下面这种张图就是一个X.509 Certificate:
事实上X.509 Certificate这个名词通常指代的是IETF的PKIX Certificate和CRL Profile,见RFC5280。所以当你看到PKIX Certificate字样的时候可以认为就是X.509 Certificate。
引用自Wiki:
In cryptography, PKCS stands for “Public Key Cryptography Standards”
前面提到的X.509是定义Public Key Certificates的格式的标准,看上去和PKCS有点像,但实际上不同,PKCS是Public Key密码学标准。此外Public-Key Cryptography虽然名字看上去只涉及Public Key,实际上也涉及Priviate Key,因此PKCS也涉及Private Key。
PKCS一共有15个标准编号从1到15,这里只挑讲PKCS #1、PKCS #8、PKCS #12。
PKCS #1,RSA Cryptography Standard,定义了RSA Public Key和Private Key数学属性和格式,详见RFC8017。
PKCS #8,Private-Key Information Syntax Standard,用于加密或非加密地存储Private Certificate Keypairs(不限于RSA),详见RFC5858。
PKCS #12定义了通常用来存储Private Keys和Public Key Certificates(例如前面提到的X.509)的文件格式,使用基于密码的对称密钥进行保护。注意上述Private Keys和Public Key Certificates是复数形式,这意味着PKCS #12文件实际上是一个Keystore,PKCS #12文件可以被用做Java Key Store(JKS),详见RFC7292。
openssl pkcs12 -export \
-in <cert> \
-inkey <private-key> \
-name my-cert \
-caname my-ca-root \
-CAfile <ca-cert> \
-chain
-out <pkcs-file>
PKCS #12一般不导出PEM编码格式。
当你不知道你的PEM文件内容是什么格式的可以根据下面查询。
RFC7468 - Textual Encoding of Certificates
-----BEGIN CERTIFICATE-----
BASE64Encoded
-----END CERTIFICATE-----
RFC7468 - Textual Encoding of Subject Public Key Info
-----BEGIN PUBLIC KEY-----
BASE64Encoded
-----END PUBLIC KEY-----
没有RFC或权威Specification,该格式有时候被称为traditional format、SSLeay format(见SO)
-----BEGIN RSA PRIVATE KEY-----
BASE64Encoded
-----END RSA PRIVATE KEY-----
同上没有RFC或权威Specification
-----BEGIN RSA PUBLIC KEY-----
BASE64Encoded
-----END RSA PUBLIC KEY-----
RFC7468 - One Asymmetric Key and the Textual Encoding of PKCS #8 Private Key Info
-----BEGIN PRIVATE KEY-----
BASE64Encoded
-----END PRIVATE KEY-----
RFC7468 - Textual Encoding of PKCS #8 Encrypted Private Key Info
-----BEGIN ENCRYPTED PRIVATE KEY-----
BASE64Encoded
-----END ENCRYPTED PRIVATE KEY-----
生成PKCS #1格式的RSA Private Key
openssl genrsa -out private-key.p1.pem 2048
PKCS #1 -> Unencrypted PKCS #8
openssl pkcs8 -topk8 -in private-key.p1.pem -out private-key.p8.pem -nocrypt
PKCS #1 -> Encrypted PKCS #8
openssl pkcs8 -topk8 -in private-key.p1.pem -out private-key.p8.pem
过程中会让你输入密码,你至少得输入4位,所以PKCS #8相比PKCS #1更安全。
PKCS #8 -> PKCS #1
openssl rsa -in private-key.p8.pem -out private-key.p1.pem
如果这个PKCS #8是加密的,那么你得输入密码。
PKCS #8 Unencrypted -> PKCS #8 Encrypted
openssl pkcs8 -topk8 -in private-key.p8.nocrypt.pem -out private-key.p8.crypt.pem
过程中会让你输入密码,你至少得输入4位。
PKCS #8 Encrypted -> PKCS #8 Unencrypted
openssl pkcs8 -topk8 -in private-key.p8.crypt.pem -out private-key.p8.nocrypt.pem -nocrypt
过程中会要求你输入Private Key密码。
提取指的是从Private Key中提取Public Key,openssl rsa
同时支持PKCS #1和PKCS #8的RSA Private Key,唯一的区别是如果PKCS #8是加密的,会要求你输入密码。
提取X.509格式RSA Public Key
openssl rsa -in private-key.pem -pubout -out public-key.x509.pem
提取PKCS #1格式RSA Public Key
openssl rsa -in private-key.pem -out public-key.p1.pem -RSAPublicKey_out
openssl x509 -in cert.pem -pubkey -noout > public-key.x509.pem
X.509 RSA Public Key -> PKCS #1 RSA Public Key
openssl rsa -pubin -in public-key.x509.pem -RSAPublicKey_out -out public-key.p1.pem
PKCS #1 RSA Public Key -> X.509 RSA Public Key
openssl rsa -RSAPublicKey_in -in public-key.p1.pem -pubout -out public-key.x509.pem
<?php
/**
* Describe:
* Author: liziyu
* Date: 2022/10/31 11:10 AM
*/
namespace App\encrypt;
class MD5WithRSA
{
/**
* 利用约定数据和私钥生成数字签名
* @param $data 待签数据
* @return String 返回签名
*/
public function sign($data='')
{
if (empty($data))
{
return false;
}
$private_key="MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgE==";
$private_key = chunk_split($private_key, 64, "\n");
$private_key = "-----BEGIN PRIVATE KEY-----\n$private_key-----END PRIVATE KEY-----";
if (empty($private_key))
{
echo "私钥为空";
return false;
}
// 生成密钥资源id
$private_key_resource_id = openssl_get_privatekey($private_key);
if (empty($private_key_resource_id))
{
echo "私钥资源id为空";
return false;
}
$verify = openssl_sign($data, $signature, $private_key_resource_id, OPENSSL_ALGO_MD5);
openssl_free_key($private_key_resource_id);
// 二次Base64编码
return base64_encode(base64_encode($signature));
}
/**
* 利用公钥和数字签名以及约定数据验证合法性
* @param string $data 待验证数据
* @param string $signature 数字签名
* @return -1 验证错误;0 验证失败;1 验证成功
*/
public function isValid($data='', $signature='')
{
if (empty($data) || empty($signature))
{
return false;
}
$public_key ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB";
$public_key = chunk_split($public_key, 64, "\n");
$public_key = "-----BEGIN PUBLIC KEY-----\n$public_key-----END PUBLIC KEY-----";
if (empty($public_key))
{
echo "公钥为空";
return false;
}
// 生成密钥资源id
$public_key_resource_id = openssl_get_publickey($public_key);
if (empty($public_key_resource_id))
{
echo "公钥资源id为空";
return false;
}
// 对签名进行二次Base64解码
$signature=base64_decode(base64_decode($signature));
$ret = openssl_verify($data, $signature, $public_key_resource_id, OPENSSL_ALGO_MD5);
openssl_pkey_free($public_key_resource_id);
return $ret;
}
}