接口签名与数据加密

接口签名与数据加密

前言

公司业务主要是和第三方机构合作,遇到过各种各样的加密,今天就来简单讲解一下常见的加密方式

简单签名

有些机构的加密方式简单一些,常见遇到会用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编码

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非对称加密需要生成一个公钥和一个私钥.用法也是众说纷纭,有人说保留公钥,将私钥提供给机构,有人说保留私钥,将公钥提供给机构,本人倾向于后者.
用法,将公钥提供给机构,像机构发起请求时用自己私钥加密,这是解决了证明我是我的问题(请求确实由自己发起)机构返回内容或发起请求同理,将他们的公钥给我们.代码示例如下

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限制进一步确保数据安全.

你可能感兴趣的:(接口签名与数据加密)