欢迎您的光临,本博所发布之文章皆为作者亲测通过,如有错误,欢迎通过各种方式指正。

教程  php进阶--PHP加密扩展之OpenSSL的使用

PHP工具扩展 本站 1268 0评论

引言


互联网的发展史上,安全性一直是开发者们相当重视的一个主题,为了实现数据传输安全,我们需要保证:数据来源(非伪造请求)、数据完整性(没有被人修改过)、数据私密性(密文,无法直接读取)等。虽然现在已经有SSL/TLS协议实现的HTTPS协议,但是因在客户端上依赖浏览器的正确实现,而且效率又很低,所以一般的敏感数据(如交易支付信息等)还是需要我们使用加密方法来手动加密。


虽然对于一般的WEB开发人员来说,大可不必深入了解一些安全相关的底层技术,但学习加密基础知识,使用现有加密相关工具却十分必要。由于工作需要,自己看了些加密相关文章,结合自己的使用经历,完成此文。


加密基础


学习如何使用加密之前,我们需要了解一些加密相关的基础知识。


加密算法一般分为两种:对称加密算法和非对称加密算法。


1.对称加密

对称加密算法是消息发送者和接收者使用同一个密匙,发送者使用密匙加密了文件,接收者使用同样的密匙解密,获取信息。常见的对称加密算法有:des/aes/3des.


对称加密算法的特点有:速度快,加密前后文件大小变化不大,但是密匙的保管是个大问题,因为消息发送方和接收方任意一方的密匙丢失,都会导致信息传输变得不安全。


2.非对称加密

与对称加密相对的是非对称加密,非对称加密的核心思想是使用一对相对的密匙,分为公匙和私匙,私匙自己安全保存,而将公匙公开。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。发送数据前只需要使用接收方的公匙加密就行了。常见的非对称加密算法有RSA/DSA:


非对称加密虽然没有密匙保存问题,但其计算量大,加密速度很慢,有时候我们还需要对大块数据进行分块加密。


3.数字签名

为了保证数据的完整性,还需要通过散列函数计算得到一个散列值,这个散列值被称为数字签名。其特点有:

· 无论原始数据是多大,结果的长度相同的;

· 输入一样,输出也相同;

· 对输入的微小改变,会使结果产生很大的变化;

· 加密过程不可逆,无法通过散列值得到原来的数据;

· 常见的数字签名算法有md5,hash1等算法。


4.公钥/私钥/签名/验证签名/加密/解密/对称加密/非对称加密

公钥与私钥是通过一种算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分。公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据。


当然,公钥和私钥都可以用来加密数据,用另一个解开。这里有两种情况,公钥加密私钥解密的情况被称为加密解密;私钥加密数据,公钥解密一般被称为签名和验证签名。


公钥和私钥是一一对应的,公钥加密的数据只有它相对应的私钥可以解开,当你想跟A通信时,和A交换公钥,你要发给A的数据就用A的公钥加密,他收到数据后用自己的私钥解密,同理A发送消息时也是这种操作,这样最大程度保证了安全性。

但是我们如何知道和我沟通的就真的是A吗?所以就要用上签名,用自己的私钥对数据进行签名发送给A,这个数据就只有配对的公钥可以解开,而这个私钥只有我有,所以如果配对的公钥解开了数据,就说明这数据是我发的,相反,则不是.这个被称为签名,A用我的公钥解密数据叫做验证签名。


加密时用一个密码加密文件,然后解密也用同样的密码,这个是对称加密。而有些加密时,加密用的一个密码,而解密用另外一组密码,这个叫非对称加密,意思就是加密解密的密码不一样。


PHP的openssl扩展


PHP 在进入7.x 时代后,默认就不再附带 mcrypt 扩展,mcrypt 将被 openssl_* 一族函数所替代。所以,对于 PHPer 来说,有必要学习一下 PHP 的 OpenSSL 扩展。


1.安装openssl扩展


安 装openssl组件,一般php安装目录中都有许多扩展组件的安装包,当然也包括openssl,例如我的php安装目录是/data/php- 5.4.32,那么openssl组件的安装目录则为/data/php-5.4.32/ext/openssl,然后执行以下操作

1) cd /data/php-5.4.32/ext/openssl

2) mv config0.m4 config.m4

3) /data/php/bin/phpize(phpize的路径根据各自安装有所不同,如果遇到Cannot find config.m4.

Make sure that you run /usr/local/bin/phpize in the top level source directory of the module,

到openssl目录里面mv config.0m4   config.m4  在执行phpize)

4) ./configure --with-openssl --with-php-config=/data/php/bin/php-config,如果此步报Cannot find OpenSSL's <evp.h> 的错误执行以下命令:

yum install openssl openssl-devel

ln -s /usr/lib64/libssl.so /usr/lib/

然后重复3)4)步骤

5)make

6)make install,安装成功会生成一个目录里面包含openssl.so

修 改php.ini文件,首先应设置一个扩展组件的存放目录,例如我的存放目录为/data/php/ext,那么修改php.ini中的 extension_dir为extension_dir = "/data/php/ext",然后在扩展组件最后添加extension=openssl.so,最后保存文件

7)重启php和nginx

phpinfo查看openssl是否安装成功

提示:

最后还需要把生成的bcmath.so文件加入到php.ini中 extesion=openssl.so(如果没有设定extesion_dir,可能要加上全路径,查看phpize执行后的生成的so文件的路径即可, 这里是:extension_dir=/www/wdlinux/php/lib/php/extensions/no-debug-zts- 20060613)

可能在make的时候,会遇到错误

错误:‘PHP_FE_END’未声明(不在函数内) -----{error: ‘PHP_FE_END’ undeclared here (not in a function)}

解决方法: 源代码有错误,进入php-5.3.18/ext/mcrypt目录

sed -i 's|PHP_FE_END|{NULL,NULL,NULL}|' ./*.c

sed -i 's|ZEND_MOD_END|{NULL,NULL,NULL}|' ./*.c

再重新make && make install

我编译后模块都在/usr/lib/php/modules,然后把php/ini文件的模块加载到/usr/lib/php/modules 这个下面。


2.openssl扩展的使用


本文就先从 OpenSSL 扩展中的对称加密说起。后面会陆续更多非对称加密、数字签名、数字证书等函数的讲解。

PHP 的 OpenSSL 扩展中,对称加密的相关函数有:

· openssl_encrypt()

· openssl_decrypt()

· openssl_random_pseudo_bytes()

· openssl_get_cipher_methods()

· openssl_cipher_iv_length()


光看PHP的官方文档还有点难理解。上一段代码,更清楚地看下这些函数怎么完成加密的:

// 加密算法 
$encryptMethod = 'aes-256-cbc';
// 明文数据
$data = 'Hello World';
// 生成IV
$ivLength = openssl_cipher_iv_length($encryptMethod);
$iv = openssl_random_pseudo_bytes($ivLength, $isStrong);
if (false === $iv && false === $isStrong) {
    die('IV generate failed');
   }
// 加密
$encrypted = openssl_encrypt($data, $encryptMethod, 'secret', 0, $iv);
// 解密
$decrypted = openssl_decrypt($encrypted, $encryptMethod, 'secret', 0, $iv);

详细解释一下:

第 1 行 指定了加密算法。比如这段代码使用 aes-256-cbc 算法加密。其实PHP的OpenSSL扩展支持很多种加密算法,想知道所有对称加密算法名称列表,可以调用 openssl_get_cipher_methods() 函数,这会返回一个数组:

array(  0 => 'AES-128-CBC',  1 => 'AES-128-CBC-HMAC-SHA1',
  ...  7 => 'AES-128-ECB',
  ...  31 => 'BF-CBC',  200 => 'seed-ofb',
)

你会发现函数返回将近200种加密算法,实际上没有这么多,许多只是因为大小写不同而重复了,比如 AES-128-CBC 和 aes-128-cbc 实际上是同一种加密算法。如果去掉重复项,那么 PHP 的 OpenSSL 扩展支持大概100多种不同的加密算法。

第 3 ~ 7 行 生成了 IV。为什么要生成 IV,这个 IV 有什么用?

回顾一下 openssl_get_cipher_methods() 返回的加密算法列表,有很多名字中间带有 “CBC” 字样,这些加密算法使用了同一种加密模式,也就是 密码分组链接模式(Cipher Block Chaining)。

在 CBC 模式的加密算法中,明文会被分成若干个组,以组为单位加密。每个组的加密过程,依赖他前一个组的数据:需要跟前一组的数据进行异或操作后生成本组的密文。那么最开头的那个组又要依赖谁呢?依赖的就是 IV,所以这就是为什么 IV 要叫初始化向量。IV 是 初始化向量(initialization vector)的缩写

IV 应该是随机生成的,所以代码用到了 openssl_random_pseudo_bytes() 生成 IV。该函数接收一个 int,代表需要生成的 IV 的长度。

IV 长度随加密算法不同而不同。一般人是记不住那么多算法需要的 IV 长度的。所以直接使用 openssl_cipher_iv_length() 函数,这个函数返回一个 int,表示加密算法需要的 IV 长度:

echo openssl_cipher_iv_length('AES-256-CBC'); // 16
echo openssl_cipher_iv_length('BC-CBC'); // 8
echo openssl_cipher_iv_length('AES-128-ECB'); // 0

比如 AES-256-CBC 需要16位的 IV、 BC-CBC 需要 8 位的 IV、而AES-128-ECB 不需要 IV,所以返回了 0。

第 8 ~ 9 行 是加密和解密。分别使用了 openssl_encrypt() 和 openssl_decrypt()。

· 第一个参数是输入,对 openssl_encrypt() 来说是明文串,对 openssl_decrypt() 来说是密文串

· 第二个参数是指定加密 / 解密 算法

· 第三个参数是加密 / 解密时需要用到的密码,是个字符串

· 第四个参数额外选项,没有特殊需要可以保持默认值:0,

· 第五个参数是 IV

这两个函数除了第一个参数不同,其余参数都要保证相同才能顺利解密。最后,在使用需要 IV 的加密算法时,需要注意:

· 必须传 $iv 参数,不传的话PHP将会抛出一个 Warning

· IV 应该是随机生成的(比如用 openssl_random_pseudo_bytes() ),不能人为设定

· 每次加密都应该重新生成一次 IV ,不可偷懒多次加密采用相同 IV

· IV 要随着密文一起保存(不然就没法解密了啦),可以直接附在密文串后面,也可以分开保存


(1)常用的函数


· 对称加密函数:

加密:string openssl_encrypt ( string $data , string $method , string $password) 

其中data为其要加密的数据, method是加密要使用的方法,password是要使用的密匙,函数返回加密后的数据,

其中$method列表可以使用openssl_get_cipher_methods()来获取,我们选取其中一个使用,$method列表形如:

Array(
    0 => aes-128-cbc,   // aes加密
    1 => des-ecb,       // des加密
    2 => des-ede3,      // 3des加密
    ...
    )

解密:string openssl_encrypt ( string $data , string $method , string $password)


· 非对称加密函数:

openssl_get_publickey();   别名openssl_pkey_get_public(); // 从证书导出公匙;

openssl_get_privatekey(); 别名openssl_pkey_get_private(); // 从证书导出私匙;

它们都只需要传入证书文件(一般是.pem文件);

openssl_public_encrypt(string $data , string &$crypted , mixed $key [, int $padding = OPENSSL\_PKCS1\_PADDING ] )

使用公匙加密数据,其中$data是要加密的数据;$crypted是一个引用变量,加密后的数据会被放入这个变量中;$key是要传入的公匙数据;由于被加密数据分组时,有可能不会正好为加密位数bit的整数倍,所以需要$padding(填充补齐),$padding的可选项有 OPENSSL_PKCS1_PADDING, OPENSSL_NO_PADDING,分别为PKCS1填充,或不使用填充;

与此方法相对的还有(传入参数一致):

openssl_private_encrypt(); // 使用私匙加密;

openssl_private_decrypt(); // 使用私匙解密;

openssl_public_decrypt(); // 使用公匙解密;


· 签名和验签函数:

bool openssl_sign ( string $data , string &$signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )

int openssl_verify ( string $data , string $signature , mixed $pub_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )

签名函数:$data为要签名的数据;$signature为签名结果的引用变量;$priv_key_id为签名所使用的私匙;$signature_alg为签名要使用的算法,其算法列表可以使用openssl_get_md_methods ()得到,形如:

array(
    0 => MD5,
    1 => SHA1,
    2 => SHA256,
    ...
)

验签函数:与签名函数相对,只不过它要传入与私匙对应的公匙;其结果为签名验证结果,1为成功,0为失败,-1则表示错误;


(2)加密实例


以下是一个非对称加密使用的小例子:

// 获取公匙
$pub_key = openssl_get_publickey('test.pem');
$encrypted = '';

// 对数据分块加密
for ($offset = 0, $length = strlen($raw_msg); $offset < $length; $offset += $key_size){    
    $encryptedBlock = '';
    $data = substr($raw_msg, $offset, $key_size)
    if (!openssl_public_encrypt($data, $encryptedBlock, $pub_key, OPENSSL_PKCS1_PADDING)){
       return '';
    } else {
        $encrypted .= $encryptedBlock;
 }
 return $encrypted;

而对称加密就非常简单了,直接使用ssl_encrypt()函数即可;


当然一些接口可能会对加密方法进行不同的要求,如不同的padding,加密块大小等等,这些就需要使用者自己调整了。


因为我们是在HTTP协议之上处理的数据,所以数据加密完成后,就可以直接发送了,不用再考虑底层的传输,使用cURL或SOAP扩展方法,就可以直接请求接口啦。


这里我针对上述常用函数封装了一个加密解密类,实现了基本的加密解密签名验签功能,代码如下:

class OpensslClass {
private $dn;
private $privkeypass = '111111'; //私钥密码 
private $numberofdays = 365; //有效时长 
private $cerpath = "./sign/test.cer"; //生成证书路径 
private $pfxpath = "./sign/test.pfx"; //密钥文件路径 
private $prikeypath = "./key/privkey.pem"; //私钥文件
private $pubkeypath = "./key/pubkey.key"; //公钥文件
public function __construct(array $dn) 
{
$this->dn = $dn;
}

/**
* 对称加密
* @param string 明文
* @param string key
* @param string 加密方式
* @return array [密文, 伪随机字节串]
*/
public function symmetric_encrypt($plaintext, $key, $cipher = 'aes-128-cbc')
{
if (in_array($cipher, openssl_get_cipher_methods()))//判断传递的加密算法是否在可用的加密算法的列表中
{
$ivlen = openssl_cipher_iv_length($cipher); //获取密码初始化向量(iv)长度
$iv = openssl_random_pseudo_bytes($ivlen); //生成一个伪随机字节串 string ,字节数由 length 参数指定
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv);
return [$ciphertext,$iv];
} else {
return '加密方式传递错误';
}
}

//对称解密
/**
* 对称解密
* @param string $ciphertext 密文
* @param string $key 密钥
* @param string $iv 伪随机字节串
* @param string $cipher 加密方式
* @return string 明文
*/
public function symmetric_decrypt($ciphertext, $key, $iv, $cipher = 'aes-128-cbc')
{
if (in_array($cipher, openssl_get_cipher_methods()))
{
$original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv);
return $original_plaintext;
} else {
return '加密方式传递错误';
}
}

/**
* 获取私钥公钥
* @param array $configargs 配置
* @return bool [description]
*/
public function getPrivateKeyAndPublicKey($configargs = array())
{
if (empty($configargs)) {
$configargs = array(
'private_key_bits' => 1024, // Size of Key.
'private_key_type' => OPENSSL_KEYTYPE_RSA
);
}
//$res返回false的时候,检查发现,是window系统缺少了openssl环境变量,解决方法如下:
$opensslConfigPath = "D:/phpstudy/PHPTutorial/Apache/conf/openssl.cnf"; //apache路径下的openssl.conf文件路径
$res = openssl_pkey_new($configargs);////生成一个新的私钥 openssl_pkey_new ([ array $configargs ] ) configargs参数微调密钥的生成(比如private_key_bits 指定应该使用多少位来生成私钥)
if(!$res) {
$configargs['config'] = $opensslConfigPath;
$res = openssl_pkey_new($configargs);
} 
openssl_pkey_export($res, $privKey, null, $configargs);//将一个密钥的可输出表示转换为字符串
$file = fopen($this->prikeypath, 'w');
fwrite($file, trim($privKey));
fclose($file);
$pubkey=openssl_pkey_get_details($res);
$pubkey=$pubkey["key"];
$file = fopen($this->pubkeypath, 'w');
fwrite($file, trim($pubkey));
fclose($file);
return true;
}

//非对称私钥加密
public function asymmetric_private_encrypt($plaintext)
{
$pkey = openssl_pkey_get_private(file_get_contents($this->prikeypath));
openssl_private_encrypt($plaintext,$crypttext,$pkey); 
$crypttext = base64_encode($crypttext);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
return $crypttext;
}

//非对称公钥解密
public function asymmetric_public_decrypt($crypttext)
{
$pubkey = file_get_contents($this->pubkeypath);
$res = openssl_pkey_get_public($pubkey);
openssl_public_decrypt(base64_decode($crypttext), $decrypttext, $res);
return $decrypttext;
}

//非对称公钥加密
public function asymmetric_public_encrypt($plaintext)
{
$pkey = openssl_pkey_get_public(file_get_contents($this->pubkeypath));
openssl_public_encrypt($plaintext,$crypttext,$pkey); 
$crypttext = base64_encode($crypttext);//加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
return $crypttext;
}

//非对称私钥解密
public function asymmetric_private_decrypt($crypttext)
{
$pubkey = file_get_contents($this->prikeypath);
$res = openssl_pkey_get_private($pubkey);
openssl_private_decrypt(base64_decode($crypttext), $decrypttext, $res);
return $decrypttext;
}

//证书生成
public function getCertificate()
{
$configargs = array(
'config' => "D:/phpstudy/PHPTutorial/Apache/conf/openssl.cnf",
'private_key_bits' => 1024, // Size of Key.
'private_key_type' => OPENSSL_KEYTYPE_RSA
);

//生成证书 
$privkey = openssl_pkey_new($configargs); //生成一个新的私钥 openssl_pkey_new ([ array $configargs ] ) configargs参数微调密钥的生成(比如private_key_bits 指定应该使用多少位来生成私钥)
$csr = openssl_csr_new($this->dn, $privkey,$configargs); //根据dn提供的信息生成新的CSR(证书签名请求) privkey 应该被设置为由openssl_pkey_new()函数预先生成(或者以其他方式从openssl_pkey函数集中获得)的私钥。该密钥的相应公共部分将用于签署CSR.
$sscert = openssl_csr_sign($csr, null, $privkey, $this->numberofdays,$configargs); //用另一个证书签署 CSR (或者本身) 并且生成一个证书 从给定的 CSR 生成一个x509证书资源
openssl_x509_export($sscert, $csrkey); //导出证书$csrkey 将 x509 以PEM编码的格式导出到名为 output 的字符串类型的变量中 公钥证书 只有公钥
openssl_pkcs12_export($sscert, $privatekey, $privkey, $this->privkeypass); //导出密钥$privatekey 

//生成证书文件 
$fp = fopen($this->cerpath, "w"); 
fwrite($fp, $csrkey); 
fclose($fp); 

//生成密钥文件 
$fp = fopen($this->pfxpath, "w"); 
fwrite($fp, $privatekey); 
fclose($fp); 
return true;
}

/**
* 签名
* @param string $data 明文
* @return string 加密信息
*/
public function sign($data)
{
$priv_key = file_get_contents($this->pfxpath); //获取密钥文件内容 
//私钥加密 
openssl_pkcs12_read($priv_key, $certs, $this->privkeypass); //读取公钥、私钥 
$prikeyid = $certs['pkey']; //私钥 
openssl_sign($data, $signMsg, $prikeyid,OPENSSL_ALGO_SHA1); //注册生成加密信息 
$signMsg = base64_encode($signMsg); //base64转码加密信息 加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的
return $signMsg;
}

/**
* 公钥验证签名
* @param string $data 明文
* @param string $signMsg 加密信息
* @return bool 是否验证通过
*/
public function verify($data,$signMsg)
{
$priv_key = file_get_contents($this->pfxpath); 
//公钥解密 
$unsignMsg=base64_decode($signMsg);//base64解码加密信息 
openssl_pkcs12_read($priv_key, $certs, $this->privkeypass); //读取公钥、私钥 
$pubkeyid = $certs['cert']; //公钥 
$res = openssl_verify($data, $unsignMsg, $pubkeyid); //验证 
return $res; //输出验证结果,1:验证成功,0:验证失败 
}
}
测试签名验签:
$dn = array( 
"countryName" => 'XX', //所在国家名称 
"stateOrProvinceName" => 'State', //所在省份名称 
"localityName" => 'SomewhereCity', //所在城市名称 
"organizationName" => 'MySelf', //注册人姓名 
"organizationalUnitName" => 'Whatever', //组织名称 
"commonName" => 'mySelf', //公共名称 
"emailAddress" => 'user@domain.com' //邮箱 
); 
$test = new OpensslClass($dn);
$str = '这是一个验证签名测试文本';
$test->init();
$signtext = $test->sign($str);
echo '加密信息 :'.$signtext;
echo '<br/>';
echo '验证结果 :'.$test->verify($str,$signtext);

结果:

111.jpg


测试加密解密:

$test = new OpensslClass($dn);
$str = '这是一个验证加密解密测试文本';
$test->getPrivateKeyAndPublicKey();
$entext = $test->asymmetric_public_encrypt($str);
$detext = $test->asymmetric_private_decrypt($entext);
echo $entext.'<br />';
echo $detext;

结果:

222.jpg


openssl还提供了很多函数,其中涉及了几个名词,pkcs12,pkcs7,x509,围绕这几个名词提供了许多函数,我在网上查阅了一些博客,这里介绍一下它们的含义作用以及区别:

x509,公钥证书,只有公钥。

pkcs7,签名或加密。可以往里面塞x509,同时没有签名或加密内容。

pkcs12,含有私钥,同时可以有公钥,有口令保护。

pkcs7的作用就是电子信封。

X509是基本规范

pkcs7和pkcs12是两个实现规范,pkcs7是数字信封,pkcs12是带有私钥的证书规范。

x509是数字证书的规范,pkcs7和pkcs12是两种封装形式。比如说同样的电影,有的是avi格式,有的是mpg,大概就这个意思。

pkcs7一般是把证书分成两个文件,一个公钥一个私钥,有PEM和DER两种编码方式。PEM比较多见,就是纯文本的,pkcs7一般是分发公钥用,看到的就是一串可见字符串,扩展名经常是.crt,.cer,.key等。DER是二进制编码。

pkcs12是把证书压成一个文件,.pfx 。主要是考虑分发证书,私钥是要绝对保密的,不能随便以文本方式散播。所以pkcs7格式不适合分发。.pfx中可以加密码保护,所以相对安全些。

在实践中要中,用户证书都是放在USB Key中分发,服务器证书经常还是以文件方式分发。服务器证书和用户证书,都是X509证书,就是里面的属性有区别。

X509 是证书规范

pkcs7 是消息语法 (常用于数字签名与加密)

pkcs12 个人消息交换与打包语法 (如.PFX .pkcs12)打包成带公钥与私钥


参考网址:

https://www.php.net/manual/zh/book.openssl.php

https://blog.csdn.net/kikajack/article/details/79273251

https://blog.csdn.net/qq_33722172/article/details/84872673 

https://www.cnblogs.com/zhenbianshu/p/5659328.html 

https://www.cnblogs.com/huyihao/p/6082765.html 


转载请注明: ITTXX.CN--分享互联网 » php进阶--PHP加密扩展之OpenSSL的使用

最后更新:2019-03-29 17:04:16

赞 (2) or 分享 ()
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽