2022年11月

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

一般客户会提供两个文件
.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

PHP

<?php
ini_set("error_reporting","E_ALL & ~E_NOTICE");
header('Content-Type:application/json; charset=utf-8');
if (!function_exists('openssl_encrypt')){
    exit('请开启php.ini的openssl扩展');
}
$input="123456";   //要加密的字符串
$deskey='QDHPJKOS';//算法密钥 
$des = new Des();
$encode=$des->encrypt($input,$deskey);
echo "DES加密结果:".$encode;
$decode=$des->decrypt($encode,$deskey);
echo "\r\nDES解密结果:".$decode;

/**
 * DES加解密类
 */
class Des{
   
    /**
     *
     * 加密函数
     * 算法:des
     * 加密模式:ecb
     * 补齐方法:PKCS5
     *
     * @param unknown_type $input
     */
    public function encrypt($input, $key)
    {
        //由于php7.1废弃了mcrypt_* 一系列函数,所以采用openssl版本
        $str = $this->pkcsPadding($input, 8);
        $key = str_pad($key, 8, '0'); //3DES加密将8改为24
        $sign = @openssl_encrypt($str, 'DES-ECB', $key,OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
        //转为base64,可以有效解决乱码等问题
        $sign = base64_encode($sign);
        return $sign;
    }
    
    /**
     * 解密函数
     * 算法:des
     * 加密模式:ecb
     * 补齐方法:PKCS5
     * @param unknown_type $input
     */
    public function decrypt($input, $key)
    {
        //由于php7.1废弃了mcrypt_* 一系列函数 所以采用openssl版本
        $encrypted = base64_decode($input);
        $key = str_pad($key, 8, '0'); //3DES加密将8改为24
        $sign = @openssl_decrypt($encrypted, 'DES-ECB', $key,OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
        $sign = $this->unPkcsPadding($sign);
        $sign = rtrim($sign);
        return $sign;
    }
    /**
     * 填充
     *
     * @param $text
     * @param $blocksize
     * @return string
     */
    private function pkcsPadding($text, $blocksize)
    {
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }
 
     /**
     * 去填充
     * 
     * @param $text
     * @return string
     */
    private function unPkcsPadding($text)
    {
        $pad = ord($text {strlen($text) - 1});
        if ($pad > strlen($text))
            return false;
        if (strspn($text, chr($pad), strlen($text) - $pad) != $pad)
            return false;
        return substr($text, 0, - 1 * $pad);
    }
    
}

JAVA

package demoSys;

import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String input = "123456";
        String deskey = "QDHPJKOS";
        String encode = encrypt(input, deskey);
        System.out.println("DES加密结果:" + encode);
        String decode = decrypt(encode, deskey);
        System.out.println("DES解密结果:" + decode);
    }

    public static String encrypt(String souce, String key) {
        try {
            // DES算法要求有一个可信任的随机数源
            SecureRandom sr = new SecureRandom();
            // 从原始密匙数据创建DESKeySpec对象
            DESKeySpec dks = new DESKeySpec(key.getBytes("UTF-8"));
            // 创建一个密匙工厂,然后用它把DESKeySpec转换成 一个SecretKey对象
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretkey = keyFactory.generateSecret(dks);
            // Cipher对象实际完成加密操作
            Cipher cipher = Cipher.getInstance("DES");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.ENCRYPT_MODE, secretkey, sr);
            // 获取数据并加密
            byte encryptedData[] = cipher.doFinal(souce.getBytes("UTF-8"));
            // JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder
            // Android平台可以使用android.util.Base64
            return new String(Base64.getEncoder().encode(encryptedData));
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return souce;
        }

    }

    public static String decrypt(String souce, String key) {
        try {
            // DES算法要求有一个可信任的随机数源
            SecureRandom sr = new SecureRandom();
            // 从原始密匙数据创建DESKeySpec对象
            DESKeySpec dks = new DESKeySpec(key.getBytes());
            // 创建一个密匙工厂,然后用它把DESKeySpec转换成 一个SecretKey对象
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretkey = keyFactory.generateSecret(dks);
            // Cipher对象实际完成加密操作
            Cipher cipher = Cipher.getInstance("DES");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.DECRYPT_MODE, secretkey, sr);
            // 将加密报文用BASE64算法转化为字节数组
            byte[] encryptedData = Base64.getDecoder().decode(souce);
            // 用DES算法解密报文
            byte decryptedData[] = cipher.doFinal(encryptedData);
            return new String(decryptedData, "UTF-8");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return souce;
        }

    }

}


系统环境介绍以及准备

查看系统版本

[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

一、浏览器四种常见的post请求方式

enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码。默认地,表单数据会编码为"application/x-www-form-urlencoded"。
a.png

(1)、HTTP 协议是以 ASCII 码 传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体。
(2)、协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须 使用什么编码方式 。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。
(3)、数据发送出去,还要服务端解析成功才有意义。一般服务端语言如 php、python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。

(1)application/x-www-form-urlencoded

这应该是最常见的 POST 提交数据的方式了。浏览器的原生

表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。

<form action="form_action.asp" enctype="text/plain">
  <p>First name: <input type="text" name="fname" /></p>
  <p>Last name: <input type="text" name="lname" /></p>
  <input type="submit" value="Submit" />
</form>

此时可以看到,

Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

首先,Content-Type 被指定为 application/x-www-form-urlencoded;其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。大部分服务端语言都对这种方式很好的支持,常用的如jQuery中的ajax请求,Content-Type 默认值都是「application/x-www-form-urlencoded;charset=utf-8

(2)multipart/form-data

这也是常见的post请求方式,一般用来上传文件,各大服务器的支持也比较好。所以我们使用表单 上传文件 时,必须让表单的enctype属性值为 multipart/form-data.

注意:以上两种方式:application/x-www-form-urlencoded和multipart/form-data都是浏览器原生支持的。

(3)application/json

application/json作为响应头并不陌生,实际上,现在很多时候也把它作为请求头,用来告诉服务端消息主体是序列化的JSON字符串,除了低版本的IE,基本都支持。除了低版本的IE都支持JSON.stringify()的方法,服务端也有处理JSON的函数,使用json不会有任何麻烦。例如:

//请求数据
var data = {name:'jack',sex:'man'};
//请求数据序列化处理
JSON.stingify(data);

//结果:{'name':'jack','sex':'man'};

二、postman的几种参数格式

form-data、x-www-form-urlencoded、raw、binary的区别

1.form-data对应的是页以form表单提交传值的情形

等价于http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息;
由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件

POST  HTTP/1.1
Host: test.app.com
Cache-Control: no-cache
Postman-Token: 59227787-c438-361d-fbe1-75feeb78047e
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="filekey"; filename=""
Content-Type: 


------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="textkey"

tttttt
------WebKitFormBoundary7MA4YWxkTrZu0gW--

请求体中的boundary参数指定的就是分隔体,可以看到请求内容被分为了两段,第一段对应filekey,第二段对应textkey。

2. x-www-form-urlencoded

即application/x-www-from-urlencoded,将表单内的数据转换为Key-Value。

POST  HTTP/1.1
Host: test.app.com
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: e00dbaf5-15e8-3667-6fc5-48ee3cc89758

key1=value1&key2=value2
  • form-data与x-www-form-urlencoded的区别
  • multipart/form-data:可以上传文件或者键值对,最后都会转化为一条消息
  • x-www-form-urlencoded:只能上传键值对,而且键值对都是通过&间隔分开的

3.raw对应的是入参是任意格式的可以上传任意格式的【文本】,可以上传text、json、xml、html等

b.png

(4)binary

相当于Content-Type:application/octet-stream,只可以上传二进制数据,通常用来上传文件,但是一次只能上传一个文件

c.png

响应

1.点击 Send 即可发送请求
在下面的response模块显示返回信息
d.png

转自:https://www.cnblogs.com/h-c-g/p/11002380.html

操作流程为:

生成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 感谢作者的辛苦付出!