SoFunction
Updated on 2025-04-04

Implementation of PHP SM4 encryption

sm4 base class

class Sm4
{
    private $ck = [
        0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
        0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
        0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
        0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
        0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
        0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
        0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
        0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
    ];

    private $Sbox = [
        0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
        0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
        0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
        0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
        0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
        0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
        0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
        0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
        0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
        0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
        0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
        0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
        0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
        0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
        0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
        0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48
    ];

    private $fk = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];

    private $rk = [];

    private $b = '';

    private $len = 16;


    /**
      * Sm4 constructor.
      * @param string $key key length 16 bits
      * @param string $b is not a multiple of 16 The required complement
      * @throws \Exception
      */
    public function __construct($key, $b = ' ')
    {
        $this->ck16($key);
        $this->crk($key);
    }

    private function dd(&$data)
    {
        $n    = strlen($data) % $this->len;
        $data = $data . str_repeat($this->b, $n);
    }

    private function ck16($str)
    {
        if (strlen($str) !== $this->len) {
            throw new \Exception('The key length is 16 bits');
        }
    }

    private function add($v)
    {
        $arr = unpack('N*', $v);
        $max = 0xffffffff;
        $j   = 1;
        for ($i = 4; $i > 0; $i--) {
            if ($arr[$i] > $max - $j) {
                $j       = 1;
                $arr[$i] = 0;
            } else {
                $arr[$i] += $j;
                break;
            }
        }
        return pack('N*', ...$arr);
    }

    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function deDataCtr($str, $iv)
    {
        return $this->enDataCtr($str, $iv);
    }

    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function enDataCtr($str, $iv)
    {
        $this->ck16($iv);
        $r = '';
        $this->dd($str);
        $l = strlen($str) / $this->len;
        for ($i = 0; $i < $l; $i++) {
            $s  = substr($str, $i * $this->len, $this->len);
            $tr = [];
            $this->encode(array_values(unpack('N*', $iv)), $tr);
            $s1 = pack('N*', ...$tr);
            $s1 = $s1 ^ $s;
            $iv = $this->add($iv);
            $r  .= $s1;
        }
        return $r;
    }


    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function enDataOfb($str, $iv)
    {
        $this->ck16($iv);
        $r = '';
        $this->dd($str);
        $l = strlen($str) / $this->len;
        for ($i = 0; $i < $l; $i++) {
            $s  = substr($str, $i * $this->len, $this->len);
            $tr = [];
            $this->encode(array_values(unpack('N*', $iv)), $tr);
            $iv = pack('N*', ...$tr);
            $s1 = $s ^ $iv;
            $r  .= $s1;
        }
        return $r;
    }

    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function deDataOfb($str, $iv)
    {
        return $this->enDataOfb($str, $iv);
    }

    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function deDataCfb($str, $iv)
    {
        $this->ck16($iv);
        $r = '';
        $this->dd($str);
        $l = strlen($str) / $this->len;
        for ($i = 0; $i < $l; $i++) {
            $s  = substr($str, $i * $this->len, $this->len);
            $tr = [];
            $this->encode(array_values(unpack('N*', $iv)), $tr);
            $s1 = pack('N*', ...$tr);
            $s1 = $s ^ $s1;
            $iv = $s;
            $r  .= $s1;
        }
        return $r;
    }

    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function enDataCfb($str, $iv)
    {
        $this->ck16($iv);
        $r = '';
        $this->dd($str);
        $l = strlen($str) / $this->len;
        for ($i = 0; $i < $l; $i++) {
            $s  = substr($str, $i * $this->len, $this->len);
            $tr = [];
            $this->encode(array_values(unpack('N*', $iv)), $tr);
            $s1 = pack('N*', ...$tr);
            $iv = $s ^ $s1;
            $r  .= $iv;
        }
        return $r;
    }


    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function enDataCbc($str, $iv)
    {
        $this->ck16($iv);
        $r = '';
        $this->dd($str);
        $l = strlen($str) / $this->len;
        for ($i = 0; $i < $l; $i++) {
            $s  = substr($str, $i * $this->len, $this->len);
            $s  = $iv ^ $s;
            $tr = [];
            $this->encode(array_values(unpack('N*', $s)), $tr);
            $iv = pack('N*', ...$tr);
            $r  .= $iv;
        }
        return $r;
    }

    /**
      * @param string $str Encrypted string
      * @param string $iv Initialize string 16 bits
      * @return string
      * @throws \Exception
      */
    public function deDataCbc($str, $iv)
    {
        $this->ck16($iv);
        $r = '';
        $this->dd($str);
        $l = strlen($str) / $this->len;
        for ($i = 0; $i < $l; $i++) {
            $s  = substr($str, $i * $this->len, $this->len);
            $tr = [];
            $this->decode(array_values(unpack('N*', $s)), $tr);
            $s1 = pack('N*', ...$tr);
            $s1 = $iv ^ $s1;
            $iv = $s;
            $r  .= $s1;
        }
        return $r;
    }


    /**
      * @param string $str Encrypted string
      * @return string
      */
    public function enDataEcb($str)
    {
        $r = [];
        $this->dd($str);
        $ar = unpack('N*', $str);
        do {
            $this->encode([current($ar), next($ar), next($ar), next($ar)], $r);
        } while (next($ar));
        return pack('N*', ...$r);
    }

    /**
      * @param string $str Decrypt string
      * @return string
      */
    public function deDataEcb($str)
    {
        $r = [];
        $this->dd($str);
        $ar = unpack('N*', $str);
        do {
            $this->decode([current($ar), next($ar), next($ar), next($ar)], $r);
        } while (next($ar));
        return pack('N*', ...$r);
    }

    private function encode($ar, &$r)
    {
        for ($i = 0; $i < 32; $i++) {
            $ar[$i + 4] = $this->f($ar[$i], $ar[$i + 1], $ar[$i + 2], $ar[$i + 3], $this->rk[$i]);
        }
        $r[] = $ar[35];
        $r[] = $ar[34];
        $r[] = $ar[33];
        $r[] = $ar[32];
    }

    private function decode($ar, &$r)
    {
        for ($i = 0; $i < 32; $i++) {
            $ar[$i + 4] = $this->f($ar[$i], $ar[$i + 1], $ar[$i + 2], $ar[$i + 3], $this->rk[31 - $i]);
        }
        $r[] = $ar[35];
        $r[] = $ar[34];
        $r[] = $ar[33];
        $r[] = $ar[32];
    }

    private function crk($key)
    {
        $keys = array_values(unpack('N*', $key));
        $keys = [
            $keys[0] ^ $this->fk[0],
            $keys[1] ^ $this->fk[1],
            $keys[2] ^ $this->fk[2],
            $keys[3] ^ $this->fk[3]
        ];
        for ($i = 0; $i < 32; $i++) {
            $this->rk[] = $keys[] = $keys[$i] ^ $this->t1($keys[$i + 1] ^ $keys[$i + 2] ^ $keys[$i + 3] ^ $this->ck[$i]);
        }
    }

    private function lm($a, $n)
    {
        return ($a >> (32 - $n) | (($a << $n) & 0xffffffff));
    }

    private function f($x0, $x1, $x2, $x3, $r)
    {
        return $x0 ^ $this->t($x1 ^ $x2 ^ $x3 ^ $r);
    }

    private function s($n)
    {
        return $this->Sbox[($n & 0xff)] | $this->Sbox[(($n >> 8) & 0xff)] << 8 | $this->Sbox[(($n >> 16) & 0xff)] << 16 | $this->Sbox[(($n >> 24) & 0xff)] << 24;
    }

    private function t($n)
    {
        $b = $this->s($n);
        return $b ^ $this->lm($b, 2) ^ $this->lm($b, 10) ^ $this->lm($b, 18) ^ $this->lm($b, 24);
    }

    private function t1($n)
    {
        $b = $this->s($n);
        return $b ^ $this->lm($b, 13) ^ $this->lm($b, 23);
    }


}

Calling class

class RtSm4
{
    protected $sm4;
    protected $keyLen = 16;
    protected $ivLen  = 16;

    function __construct($key)
    {
        $this->sm4 = new Sm4($key);
    }

    public function encrypt($data, $type = 'sm4', $iv = '', $formatOut = 'hex')
    {

        if ($type != 'sm4-ecb') {
            $this->check_iv($iv);
        }
        $ret = '';
        switch ($type) {
            case 'sm4':
            case 'sm4-cbc':
                $data = $this->mystr_pad($data, $this->keyLen); // Need to make up                $ret  = $this->sm4->enDataCbc($data, $iv);
                break;
            case 'sm4-ecb':
                $data = $this->mystr_pad($data, $this->keyLen); // Need to make up                $ret  = $this->sm4->enDataEcb($data);
                break;
            case 'sm4-ctr':
                $ret = $this->sm4->enDataCtr($data, $iv);
                break;
            case 'sm4-ofb':
                $ret = $this->sm4->enDataOfb($data, $iv);
                break;
            case 'sm4-cfb':
                $ret = $this->sm4->enDataCfb($data, $iv);
                break;
            default:
                throw new Exception('bad type');
        }
        if ($formatOut == 'hex') {
            return bin2hex($ret);
        } else if ($formatOut == 'base64') {
            return base64_encode($ret);
        }
        return $ret;
    }

    public function decrypt($data, $type = 'sm4', $iv = '', $formatInput = 'hex')
    {
        if ($type != 'sm4-ecb') {
            $this->check_iv($iv);
        }
        if ($formatInput == 'hex') {
            $data = hex2bin($data);
        } else if ($formatInput == 'base64') {
            $data = base64_decode($data);
        }
        //else  is raw
        switch ($type) {
            case 'sm4':
            case 'sm4-cbc':
                $ret = $this->sm4->deDataCbc($data, $iv);
                $ret = $this->mystr_unpad($ret);
                break;
            case 'sm4-ecb':
                $ret = $this->sm4->deDataEcb($data);
                $ret = $this->mystr_unpad($ret);
                break;
            case 'sm4-ctr':
                $ret = $this->sm4->deDataCtr($data, $iv);
                break;
            case 'sm4-ofb':
                $ret = $this->sm4->deDataOfb($data, $iv);
                break;
            case 'sm4-cfb':
                $ret = $this->sm4->deDataCfb($data, $iv);
                break;
            default:
                throw new Exception('bad type');
        }
        return $ret;
    }

    //Put it before encryption, specific suffixes do not apply to all docking parties    protected function mystr_pad($data, $len = 16)
    {
        $n         = $len - strlen($data) % $len;
        $tmpString = $this->strToHex($data) . "80";
        $tmpString .= str_repeat("00", $n - 1);
        return $this->hexToStr($tmpString);
    }

    // After decryption, remove and fill, the specific suffix does not apply to all docking parties    protected function mystr_unpad($data)
    {
        $tmp       = $this->strToHex($data);
        $lastIndex = strrpos($tmp, '80');
        if ($lastIndex !== false) {
            $tmp = substr($tmp, 0, $lastIndex);
        }
        return $this->hexToStr($tmp);
    }

    //Complete before encryption, common code, no specific requirements available    protected function mystr_pad_bak($data, $len = 16)
    {
        $n = $len - strlen($data) % $len;
        return $data . str_repeat(chr($n), $n);
    }
    // Remove and fill up after decryption. Common code, this is available for no specific requirements.    protected function mystr_unpad_bak($data)
    {
        $n = ord(substr($data, -1));
        return substr($data, 0, -$n);
    }
    protected function check_iv($iv)
    {
        if (strlen($iv) != $this->ivLen) {
            throw new Exception('bad iv');
        }
    }

    protected function strToHex($string)
    {
        $hex = unpack('H*', $string);
        return $hex[1];
    }

    protected function hexToStr($hex)
    {
        $string = pack('H*', $hex);
        return $string;
    }
}

Call

$key        = hex2bin('660e5d5xxxxxxx714f346e');
$iv         = hex2bin('abcd0863xxxxxf67');
$sm4        = new RtSm4($key);
$string     = 'xxxx';
$encodeText = $sm4->encrypt($string, 'sm4-cbc', $iv);
echo $encodeText . PHP_EOL;
echo $sm4->decrypt($encodeText, 'sm4-cbc', $iv). PHP_EOL;

This is the end of this article about the implementation of PHP SM4 encryption. For more related PHP SM4 encryption content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!