php使用SM(国密)算法

2021-09-11

SM2(国密2)算法暂时不可用,SM4(国密4)可以使用(有多种模式)

/**
 * @description: SM(国密)算法
 */
class SMAlgorithm
{
    /**
     * @description: 
     * @var array 加解密所需数据
     */
    protected $data = [];

    /**
     * @description: 
     * @var string 加解密方法名
     */
    protected $func;

    /**
     * @description: 秘钥长度
     * @var array
     */
    protected $keyLength = [
        'sm4' => 16,
    ];

    /**
     * @description: 加密模式对应iv长度
     * @var array
     */
    protected $ivLength = [
        'ecb' => 0,
        'cbc' => 16,
        'cfb' => 16,
        'ofb' => 16,
        'ctr' => 16,
    ];

    /**
     * @description: SM加解密
     * @param {string} $data 需要加解密的数据
     * @param {string} $algotithm 加密算法
     * @param {string} $key 加密key
     * @param {int} $options 加密填充 OPENSSL_RAW_DATA/OPENSSL_ZERO_PADDING 不填充/以0填充
     * @param {string} $iv 初始化向量,ecb模式不需要,其他模式需要,且长度不一
     * @param {bool} $isDe 加密/解密
     * @return {*}
     */
    public function encryptDecrypt(string $data, string $algotithm, string $key, int $options = OPENSSL_RAW_DATA, string $iv = '', bool $isDe = false):string
    {
        list($sm, $mode) = explode('-', $algotithm, 2);
        $keylength = $this->keyLength[strtolower($sm)] ?? false;
        if (!$keylength) {
            throw new \Exception('SM加密长度错误');
        }

        $this->func = 'openssl_' . ($isDe ? 'decrypt' : 'encrypt');
        // 获取算法对应的iv长度
        $ivLength   = openssl_cipher_iv_length($algotithm);
        $this->data = [
            'data'       => $data,
            'key'        => substr(str_pad($key, $keylength, 0), 0, $keylength),
            'options'    => $options,
            'algotithm'  => $algotithm,
            'iv'         => substr(str_pad($iv, $ivLength, 0), 0, $ivLength),
            'isDe'       => $$isDe,
        ];
        $modeFunc = '_' . $mode;
        if (!method_exists($this, $modeFunc)) {
            throw new \Exception('SM加密模式错误');
        }
        $result = $this->$modeFunc();
        return $isDe ? base64_encode($result) : trim($result);
    }

    /**
     * @description: cbc模式,不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
     *                          但不利于并行计算,误差会传递,需要初始化向量IV
     * @return {*}
     */
    private function _cbc():string
    {
        return $this->_callute(true);
    }

    /**
     * @description: ecb模式,简单,有利于并行计算,误差不会被传送;
     *                          但不能隐藏明文的模式,可能对明文进行主动攻击
     * @return {*}
     */
    private function _ecb():string
    {
        return $this->_callute(false);
    }
    
    /**
     * @description: cfb模式,隐藏了明文模式,分组密码转化为流模式,可以及时加密传送小于分组的数据;
     *                          但是不利于并行计算;误差传送:一个明文单元损坏影响多个单元;唯一的IV;
     * @return {*}
     */
    private function _cfb()
    {
        return $this->_callute(true);
    }
    
    /**
     * @description: ofb模式,隐藏了明文模式;分组密码转化为流模式;可以及时加密传送小于分组的数据;
     *                           但不利于并行计算;对明文的主动攻击是可能的;误差传送:一个明文单元损坏影响多个单元;
     * @return {*}
     */
    private function _ofb()
    {
        return $this->_callute(true);
    }

    /**
     * @description: ctr模式,Counter
     * 计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。
     * 这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次
     * @return {*}
     */
    private function _ctr()
    {
        return $this->_callute(true);
    }

    /**
     * @description: 加解密计算
     * @param bool $iv 是否需要定义iv
     * @return {*}
     */
    private function _callute(bool $iv = false):string
    {
        if ($iv) {
            return $this->$func($this->data['data'], $this->data['algotithm'], $this->data['key'], $this->data['options'], $this->data['iv']);
        }
        return $this->$func($this->data['data'], $this->data['algotithm'], $this->data['key'], $this->data['options']);
    }

    /**
     * @description: 获取openSSL库的错误,错误消息已被队列化,可以查询到多条错误信息,最后一个是最近的一个错误。
     * @param 
     * @return array
     */
    public function errors()
    {
        $result = [];
        while ($data = openssl_error_string() !== false) {
            $result[] = $data;
        }
        return $result;
    }
}

 

{/if}