key = $this->preProcess($key); $this->setSkey(); } /** * 计算每轮加密需要的秘钥 */ private function setSkey(): void { $skey = []; for ($i = 0; $i < 4; $i++) { $skey[] = self::SM4_FK[$i] ^ ($this->key[4 * $i] << 24 | $this->key[4 * $i + 1] << 16 | $this->key[4 * $i + 2] << 8 | $this->key[4 * $i + 3]); } for ($k = 0; $k < 32; $k++) { $tmp = $skey[$k + 1] ^ $skey[$k + 2] ^ $skey[$k + 3] ^ self::SM4_CK[$k]; //非线性化操作 $buf = (self::SM4_Sbox[($tmp >> 24) & 0xff]) << 24 | (self::SM4_Sbox[($tmp >> 16) & 0xff]) << 16 | (self::SM4_Sbox[($tmp >> 8) & 0xff]) << 8 | (self::SM4_Sbox[$tmp & 0xff]); //线性化操作 $skey[] = $skey[$k] ^ ($buf ^ $this->sm4Rotl32($buf, 13) ^ $this->sm4Rotl32($buf, 23)); $this->skey[] = $skey[$k + 4]; } } /** * 32比特的buffer中循环左移n位 * @param $buf int 可以传递进10进制 也可以是0b开头的二进制 * @param $n int 向左偏移n位 * @return int * reference http://blog.csdn.net/w845695652/article/details/6522285 */ private function sm4Rotl32(int $buf, int $n): int { return (($buf << $n) & 0xffffffff) | ($buf >> (32 - $n)); } /** * 对字符串加密 * @param string $plain_text * @return string * @throws Exception */ public function encrypt(string $plain_text): string { $bytes = bin2hex($plain_text); $need_pad_length = $this->block_size - strlen($bytes) % $this->block_size; $pad_bytes = str_pad( $bytes, strlen($bytes) + $need_pad_length, sprintf("%02x", $need_pad_length / 2), STR_PAD_RIGHT ); $chunks = str_split($pad_bytes, $this->block_size); return strtolower(implode('', array_map(function ($chunk) { return $this->encryptBinary($chunk); }, $chunks))); } /** * SM4加密单个片段(128bit) * @param $text string 32个十六进制字符串 * @return string * @throws Exception */ private function encryptBinary(string $text): string { $x = $re = []; $t = $this->preProcess($text); for ($i = 0; $i < 4; $i++) { $x[] = $t[$i * 4] << 24 | $t[$i * 4 + 1] << 16 | $t[$i * 4 + 2] << 8 | $t[$i * 4 + 3]; } for ($k = 0; $k < 32; $k++) { $tmp = $x[$k + 1] ^ $x[$k + 2] ^ $x[$k + 3] ^ $this->skey[$k]; $buf = self::SM4_Sbox[($tmp >> 24) & 0xff] << 24 | self::SM4_Sbox[($tmp >> 16) & 0xff] << 16 | self::SM4_Sbox[($tmp >> 8) & 0xff] << 8 | self::SM4_Sbox[$tmp & 0xff]; $x[$k + 4] = $x[$k] ^ $buf ^ $this->sm4Rotl32($buf, 2) ^ $this->sm4Rotl32($buf, 10) ^ $this->sm4Rotl32($buf, 18) ^ $this->sm4Rotl32($buf, 24); } for ($i = 0; $i < 4; $i++) { $re[] = ($x[35 - $i] >> 24) & 0xff; $re[] = ($x[35 - $i] >> 16) & 0xff; $re[] = ($x[35 - $i] >> 8) & 0xff; $re[] = $x[35 - $i] & 0xff; } return $this->wrapResult($re); } /** * 预处理16字节长度的16进制字符串 返回10进制的数组 数组大小为16 * @param string $text * @return array * @throws Exception */ private function preProcess(string $text): array { preg_match('/[0-9a-f]{32}/', strtolower($text), $re); if (empty($re)) { throw new Exception('error input format!'); } $key = $re[0]; for ($i = 0; $i < 16; $i++) { $result[] = hexdec($key[2 * $i] . $key[2 * $i + 1]); } return $result; } /** * 将十进制结果包装成16进制字符串输出 * @param array $result * @return string */ private function wrapResult(array $result): string { $hex_str = ''; foreach ($result as $v) { $tmp = dechex($v); $len = strlen($tmp); if ($len == 1) //不足两位十六进制的数 在前面补一个0,保证输出也是32个16进制字符 { $hex_str .= '0'; } $hex_str .= $tmp; } return strtoupper($hex_str); } /** * SM4解密单个片段(128bits) * @param string $text 32个16进制字符串 * @return string * @throws Exception */ private function decrypt_decrypt(string $text): string { $x = $re = []; $t = $this->preProcess($text); for ($i = 0; $i < 4; $i++) { $x[] = $t[4 * $i] << 24 | $t[4 * $i + 1] << 16 | $t[4 * $i + 2] << 8 | $t[4 * $i + 3]; } for ($k = 0; $k < 32; $k++) { $tmp = $x[$k + 1] ^ $x[$k + 2] ^ $x[$k + 3] ^ $this->skey[31 - $k]; $buf = (self::SM4_Sbox[($tmp >> 24) & 0xff]) << 24 | (self::SM4_Sbox[($tmp >> 16) & 0xff]) << 16 | (self::SM4_Sbox[($tmp >> 8) & 0xff]) << 8 | (self::SM4_Sbox[$tmp & 0xff]); $x[$k + 4] = $x[$k] ^ $buf ^ $this->sm4Rotl32($buf, 2) ^ $this->sm4Rotl32($buf, 10) ^ $this->sm4Rotl32($buf, 18) ^ $this->sm4Rotl32($buf, 24); } for ($i = 0; $i < 4; $i++) { $re[] = ($x[35 - $i] >> 24) & 0xff; $re[] = ($x[35 - $i] >> 16) & 0xff; $re[] = ($x[35 - $i] >> 8) & 0xff; $re[] = $x[35 - $i] & 0xff; } return $this->wrapResult($re); } /** * 字符串解密 * @param string $cipher_text * @return string * @throws Exception */ public function decrypt(string $cipher_text): string { $chunks = str_split($cipher_text, $this->block_size); $decrypt_text_data = implode('', array_map(function ($chunk) { return $this->decrypt_decrypt($chunk); }, $chunks)); $pad_length = hexdec(substr($decrypt_text_data, -2)); return hex2bin(preg_replace( sprintf("/%s$/", str_repeat(sprintf("%02x", $pad_length), $pad_length)), '', $decrypt_text_data )); } }