接口签名与数据加密
前言
公司业务主要是和第三方机构合作,遇到过各种各样的加密,今天就来简单讲解一下常见的加密方式
简单签名
有些机构的加密方式简单一些,常见遇到会用md5和sha1.
$sign_key = '5eb63bbbe01eeed093cb22bb8f5acdc3'; // 机构提供
$post_data = [
'order_no' => 'A2018040323437434',
'time' => time(),
'status' => 'hello world',
];
$post_data['sign'] = md5($post_data['order_no'] . $post_data['time'] . $sign_key); // 防止一个签名重复使用,且可以在接口处验证时间,时间与当前时间差大则说明请求伪造
上面的$post_data就是一些机构的简单签名方式,一般由机构提供key和签名规则,常见和key和时间戳识别符联合加密,也有将整个请求参数用递归方式拼接加密.sha1大致与上方相同.这种签名方式仅仅是对接口做了加密,可是请求数据和返回数据还是十分清晰的,一旦让别人捕获,可以很简单的识别出其中的关键信息.于是就有了数据可逆加密
数据可逆加密
这里就介绍两种常见的可逆加密
discuz提供的一个方案
/**
* 加解密函数
* @param $string
* @param string $operation
* @param string $key
* @param int $expiry
* @return bool|string
*/
function encrypt($string, $operation = 'DECODE', $key = 'dhyuerwbcytwbzghn', $expiry = 0)
{
// 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
$ckey_length = 4;
// 密匙
$key = md5($key);
// 密匙a会参与加解密
$keya = md5(substr($key, 0, 16));
// 密匙b会用来做数据完整性验证
$keyb = md5(substr($key, 16, 16));
// 密匙c用于变化生成的密文
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length):
substr(md5(microtime()), -$ckey_length)) : '';
// 参与运算的密匙
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
// 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),
//解密时会通过这个密匙验证数据完整性
// 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) :
sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
// 产生密匙簿
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
// 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
// 核心加解密部分
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
// 从密匙簿得出密匙进行异或,再转成字符
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
// 验证数据有效性,请看未加密明文的格式
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&
substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
// 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因
// 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码
return $keyc.str_replace('=', '', base64_encode($result));
}
}
$encode_str = encrypt('hello world!', 'ENCODE');
echo $encode_str . PHP_EOL;
$decode_str = encrypt($encode_str);
echo $decode_str . PHP_EOL;
输出
fab9fhs559JrIfE97/H5dbLn/oztFxJEltuUG3rQiSf8Q5r1yeAORV4
hello world!
这个加密方案简单,常用于站内加密,比如订单号用户号之类的信息需要作为参数,可是又不想让用户在前端看到则可以用这个加密方案
DES对称加密
以des-cbc/pksc5填充为例结果base64编码
<?php
class CryptDes
{
private $key;
public function __construct($key)
{
$this->key = $key;
}
/**
* des加密
* @desc des加密CBC模式,PKCS5Padding填充
* @param $str
* @return string
*/
public function encrypt($str)
{
$str = $this->pkcs5Pad($str, 8);
if (strlen($str) % 8) {
$str = str_pad($str,
strlen($str) + 8 - strlen($str) % 8, "\0");
}
return base64_encode(openssl_encrypt($str, 'DES-ECB', substr($this->key, 0, 8), OPENSSL_RAW_DATA | OPENSSL_NO_PADDING));
}
/**
* des解密
* @param $str
* @return string
*/
public function decrypt($str)
{
$decode_str = openssl_decrypt(base64_decode($str), 'DES-ECB', substr($this->key, 0, 8), OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
return $this->pkcs5Unpad($decode_str);
}
/**
* PKCS5Padding填充
* @param $text
* @param $blocksize
* @return string
*/
private function pkcs5Pad($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
/**
* PKCS5Padding填充逆向
* @param $text
* @return bool|string
*/
private function pkcs5Unpad($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);
}
}
$des = new CryptDes('9#HL&sk8s5Fw#q&8');
$str = 'hello world!';
$encode = $des->encrypt($str);
$decode = $des->decrypt($encode);
print_r([
'str' => $str,
'encode' => $encode,
'decode' => $decode,
]);
输出
Array
(
[str] => hello world!
[encode] => y1pB2YaGeaBkNui7Wu5ReA==
[decode] => hello world!
)
RSA非对称加密
RSA非对称加密需要生成一个公钥和一个私钥.用法也是众说纷纭,有人说保留公钥,将私钥提供给机构,有人说保留私钥,将公钥提供给机构,本人倾向于后者.
用法,将公钥提供给机构,像机构发起请求时用自己私钥加密,这是解决了证明我是我的问题(请求确实由自己发起)机构返回内容或发起请求同理,将他们的公钥给我们.代码示例如下
<?php
/**
* Created by PhpStorm.
* User: mc
* Date: 18/4/3
* Time: 下午6:01
*/
class RSA
{
public $encrypt_len;
public $public_key;
public $private_key;
public function __construct($encrypt_len, $public_key, $private_key)
{
$this->encrypt_len = $encrypt_len;
$this->public_key = $public_key;
$this->private_key = $private_key;
}
/**
* 私钥加密
* @param $data_content
* @return string
*/
public function encryptedByPrivateKey($data_content)
{
$data_content = base64_encode($data_content);
$encrypted = "";
$totalLen = strlen($data_content);
$encrypt_pos = 0;
while ($encrypt_pos < $totalLen) {
openssl_private_encrypt(substr($data_content, $encrypt_pos, $this->encrypt_len), $encrypt_data, $this->private_key);
$encrypted .= bin2hex($encrypt_data);
$encrypt_pos += $this->encrypt_len;
}
return $encrypted;
}
/**
* 公钥解密
* @param $encrypted
* @return bool|string
*/
public function decryptByPublicKey($encrypted)
{
$decrypt = "";
$totalLen = strlen($encrypted);
$decryptPos = 0;
while ($decryptPos < $totalLen) {
openssl_public_decrypt(hex2bin(substr($encrypted, $decryptPos, $this->encrypt_len * 8)), $decryptData, $this->public_key);
$decrypt .= $decryptData;
$decryptPos += $this->encrypt_len * 8;
}
//openssl_public_decrypt($encrypted, $decryptData, $this->public_key);
$decrypt = base64_decode($decrypt);
return $decrypt;
}
}
$public_key = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6NILnfGpOGTSJztKAZ8fGLLV7
Ad6PUPIeCwHz9qQ87fbEp0/eHTm2e+LgJRseRerTYLeLplqxDSqJgDToPBQOdtIQ
EqBw/C7abBskscTss+PCEjI+IHdxT1BDMoH45ofPfasizLV4wZ3WWJJhmt/gxJH3
benGi5ZJ4ksBPpJXowIDAQAB
-----END PUBLIC KEY-----'
;
$private_key = '-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALo0gud8ak4ZNInO
0oBnx8YstXsB3o9Q8h4LAfP2pDzt9sSnT94dObZ74uAlGx5F6tNgt4umWrENKomA
NOg8FA520hASoHD8LtpsGySxxOyz48ISMj4gd3FPUEMygfjmh899qyLMtXjBndZY
kmGa3+DEkfdt6caLlkniSwE+klejAgMBAAECgYEAjEhPbtKezCPVHxWAJVkKetTo
DLoF0HctUVD9sazZY0XsKY/bbf0ao86FyFRsL8yA86rj3QQBQ24l492A/o10lX21
R4u4Dc3EdtNnoY0FmAcoFRU2tHHMgknkI1tFsvKbWiCUnGiWlv98Db+OcVjQkH28
eHYZK6UPEeUagydmmKECQQD2STIWfyYSSqwpo1FVfOUgrFIyYSbB2sFCKe7CluIa
oAxYxXi8HA6Eu3eDOUUqD/yRIdNTFuGKXWaDGFbB6sQ5AkEAwYyqljpRSBmPv0GY
yc00MUsuD4TimvL8J0oz9kEFzyOfnhCUdVD2j95Z1k++EZYdQ7lcg4JX0X8eOulp
gpESuwJAdOr6pENoR3a7lGi7y+GmxIQJ4XDNfWnkJQzTE/2dCRbBxcK5NlP7cHeu
nNUrSHSeaiesst1B5PXCHKoJRbW1wQJAHPK3COUMByabk1VyTqx8Y+sEppmPcvFo
uU+l2ez7u3FujCuaqLlFR1tQQHeIzASRt/FfXuP90n2aveDvQPIFxQJAA1Aa0ZFh
Jyqj6hMGSPBivL3dVzV3XMEumoAyDfYsxJ4ub30F+UmvXri6Zhu3FAW+akuoHAhO
l7WbvdsSSeVKTA==
-----END PRIVATE KEY-----';
$encrypt_len = 32;
$rsa = new RSA($encrypt_len, $public_key, $private_key);
$test_content = 'Hello World';
$encode_content = $rsa->encryptedByPrivateKey($test_content);
$decode_content = $rsa->decryptByPublicKey($encode_content);
echo $decode_content;
输出
cafbs7cVaaE+YZd0gMmq2Ys4fV0m6MMrdQcxw1wmSXDMCMiEOrRHKA4
hello world!
结束语
上面是对接机构常用到的加密方案,当然这只是简单距离实际应用当中还有许多的变通之处.常见的如ip限制进一步确保数据安全.