分类 OpenSSL 下的文章

不同的签名验签代码,对密钥格式有不同的要求:

比如JAVA一般使用PKCS8格式的密钥,其他语言一般使用PKCS1格式的密钥。

区分小技巧:

开头为"-----BEGIN PRIVATE KEY-----"的密钥格式为PKCS8,
开头为"-----BEGIN RSA PRIVATE KEY-----"的密钥格式为PKCS1

php读取.cer文件

$certificateCAcerContent = file_get_contents($filePath);
$certificateCApemContent = '-----BEGIN CERTIFICATE-----' . PHP_EOL
. chunk_split(base64_encode($certificateCAcerContent), 64, PHP_EOL)
. '-----END CERTIFICATE-----' . PHP_EOL;

php读取.fpx文件

$pkcs12 = file_get_contents($filePath);
$res = openssl_pkcs12_read($pkcs12, $certs, '密码');

开发银行或海关的项目经常遇到加密解密的问题

一般客户会提供两个文件
.pfx 结尾的(公钥加私钥文件)
.cer 结尾的文件 (包含公钥文件)

私钥一般都是pfx格式(私钥用来加密生成签名发送报文)
公钥是cer格式(公钥用来验证返回报文里的签名)。
但是phpopenssl只能用pem格式,需要把这两个文件转化成pem的:

一、PHP的方式转化

//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';
    }
}

公钥cer转pem(即x.509证书dem格式转换为pem)

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命令转化

openssl x509 -inform der -in pub.cer -out pub.pem


三、附上截图

20200804133845380.png

转自:https://blog.csdn.net/weixin_38626872/article/details/107784610

系统环境介绍以及准备

查看系统版本

[root@xmg-hk ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

查看openssl版本

[root@xmg-hk ~]# openssl version 
OpenSSL 1.0.2k-fips  26 Jan 2017

官网下载openssl-1.1.1k

其他版本可参考下载: 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
说明:需要先进行备份,备份需要在"建立链接"操作以前完成。

转自:https://cloud.tencent.com/developer/article/1907392

操作流程为:

生成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 ==> 验证签名

命令如下:

1. 生成密钥对并导出公钥

# 生成密钥对
openssl genrsa -out key.pem

# 导出公钥
openssl rsa -in key.pem -pubout -out pubkey.pem

参数说明:

-out: 指定输出的文件
-in: 指定输入的文件
-pubout: 指定输出公钥。如果不加该参数,默认输出的为私钥

2. 加解密操作

# 生成一个待加密的测试文件
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

3. 签名、验签

# 签名
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: 待验证的签名文件

4. 查看密钥信息

# 查看私钥信息
openssl rsa -in key.pem -noout -text

# 查看公钥信息
openssl rsa -pubin -in pubkey.pem -noout -text

参数说明:

-noout: 不打印密钥数据
-text: 以文本方式打印密钥信息

转自:https://segmentfault.com/a/1190000018084284

openssl

强大的安全套接字层密码库

补充说明

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),实现了证书签发的整个流程和证书管理的大部分机制。

实例

1、使用 openssl 生成密码

几乎所有 Linux 发行版都包含 openssl。我们可以利用它的随机功能来生成可以用作密码的随机字母字符串。

openssl rand -base64 10
# nU9LlHO5nsuUvw==

nU9LlHO5nsuUvw==

2、消息摘要算法应用例子

用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

3、对称加密应用例子

对称加密应用例子,用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

4、Diffie-Hellman应用例子

使用生成因子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

5、DSA应用例子应用例子

生成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

6、RSA应用例子

产生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, AES. 这两种加密方式都属于分组加密, 先将明文分成多个等长的模块 ( block ), 然后进行加密。

DES 加密

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 加密

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]) : 生成一个新的私钥和公钥对. 通过配置数组, 可以微调密钥的生成.

$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


在传输重要信息时, 一般会采用对称加密和非对称加密相结合的方式, 而非使用单一加密方式. 一般先通过 AES 加密数据, 然后通过 RSA 加密 AES 密钥, 然后将加密后的密钥和数据一起发送. 接收方接收到数据后, 先解密 AES 密钥, 然后使用解密后的密钥解密数据。

为了学习方便,转进来的,原文地址:https://www.phpernote.com/php-function/1538.html

什么是对称加密?

用一个密钥,对明文进行加密,同理,同这把密钥,也可以对密文进行解密。

什么是非对称加密?

有一对密钥对,使用公钥和私钥进行加密解密可以使用私钥加密,公钥进行解密,同理,也可以使用公钥加密,私钥进行解密。

什么是摘要?

用md5(或者其它单向加密算法),对内容进行加密出来的字符串,就叫做摘要。

什么是签名?

用私钥对摘要进行加密,加密出来签字串,就叫做签名。

什么是验签?

用非对称密钥对中的公钥,对签名进行解密操作,解密出来的摘要和原来的对比,就叫做验签。

什么是数字证书?

含有公钥、摘要算法、指纹、签名算法、唯一序列号的特殊文件,就是数字证书。

公钥:网站(服务器)的公钥
颁发者:CA(证书认证机构)
有效期:证书的使用期限
摘要算法:指定的摘要算法,用来计算证书的摘要
指纹:也就是证书的摘要,保证证书的完整性
签名算法:用于生成签名,确保证书是由CA签发
序列号:证书的唯一标识

数字证书是如何产生的?

将网站的公钥,颁发者,有效期,摘要算法 ,哈希算法写入证书。CA 根据证书中的指定的哈希算法,计算出整个证书的摘要,即digest。CA根据签名算法以及上一步计算出来的摘要,CA用自己的私钥对摘要进行加密,生成CA的签名,即signature最后把摘要,签名以及证书的基本信息,一起发布,就得到了网站的证书。

下面以小明与小花的安全通信为例,进行问答模拟,如下:

问题一:小明和小花安全的通信,怎么做?

答:通过加密

问题二:通过哪种加密方式通信,更高效?

答:对称加密因为,单向加密,没办法解密,不行非对称加密,太慢,也不行只有对称加密,速度快

问题三:采用对称加密,密钥S 怎么安全传输?

答:小花使用小明的公钥,对密钥S进行加密,传给小明小明用自己的私钥解密

问题四:小明如何安全的把自己的公钥传输给小花?

答:使用数字证书具体就是 小明向CA申请一个自己的数字证书,把自己的公钥放在证书中小明将数字证书发送给小花。

问题五:小花如何验证数字证书的真实性?

答:小花用操作系统内置的CA的数字证书,拿到CA的公钥,用CA的公钥,对数字证书进行验签

验签通过,说明数字证书是真的。

本文摘自:https://www.helloworld.net/p/0011879971 感谢作者的辛苦付出!

ASN.1 - 数据结构描述语言

引用自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.509PKCS group of cryptography standards

文件编码格式

DER编码格式

引用自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编码格式有一个比较好的认识了。

PEM编码格式

引用自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.509PKCS)都使用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

证书、密码学Key格式

上面讲到的PEM是对证书、密码学Key文件的一种编码方式,下面举例这些证书、密码学Key文件格式:

X.509证书

引用自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 CertificateIdentity 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。

PKCS系列

引用自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

PKCS #1,RSA Cryptography Standard,定义了RSA Public Key和Private Key数学属性和格式,详见RFC8017

PKCS #8

PKCS #8,Private-Key Information Syntax Standard,用于加密或非加密地存储Private Certificate Keypairs(不限于RSA),详见RFC5858

PKCS #12

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格式速查

当你不知道你的PEM文件内容是什么格式的可以根据下面查询。

X.509 Certificate

RFC7468 - Textual Encoding of Certificates

-----BEGIN CERTIFICATE-----
BASE64Encoded
-----END CERTIFICATE-----

X.509 Certificate Subject Public Key Info

RFC7468 - Textual Encoding of Subject Public Key Info

-----BEGIN PUBLIC KEY-----
BASE64Encoded
-----END PUBLIC KEY-----

PKCS #1 Private Key

没有RFC或权威Specification,该格式有时候被称为traditional format、SSLeay format(见SO

-----BEGIN RSA PRIVATE KEY-----
BASE64Encoded
-----END RSA PRIVATE KEY-----

PKCS #1 Public Key

同上没有RFC或权威Specification

-----BEGIN RSA PUBLIC KEY-----
BASE64Encoded
-----END RSA PUBLIC KEY-----

PKCS #8 Unencrypted Private Key

RFC7468 - One Asymmetric Key and the Textual Encoding of PKCS #8 Private Key Info

-----BEGIN PRIVATE KEY-----
BASE64Encoded
-----END PRIVATE KEY-----

PKCS #8 Encrypted Private Key

RFC7468 - Textual Encoding of PKCS #8 Encrypted Private Key Info

-----BEGIN ENCRYPTED PRIVATE KEY-----
BASE64Encoded
-----END ENCRYPTED PRIVATE KEY-----

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密码。

Public Key操作命令

从PKCS #1/#8提取

提取指的是从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

从X.509证书提取

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

参考资料

https://chanjarster.github.io/post/x509-pkcs-file-formats/

与G银行对接时用到的

<?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;
    }
}