.github/workflows/sm.yml000064400000001663146412215520011313 0ustar00name: sm on: push: branches: [ master ] pull_request: branches: [ master ] jobs: run: runs-on: ubuntu-latest strategy: matrix: php-versions: [ '5.6','7.0','7.1', '7.2', '7.4', '8.0' ] name: PHP ${{ matrix.php-versions }} steps: - name: Checkout uses: actions/checkout@v2 - name: Install PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} coverage: none ini-values: opcache.jit_buffer_size=256M, opcache.jit=1205, pcre.jit=1, opcache.enable=1, opcache.enable_cli=1 - name: Check PHP Version run: php -v && php -i | grep opcache - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Execute tests sm3 run: php tests/sm3.php - name: Execute tests sm4 run: php tests/sm4.php .gitignore000064400000000072146412215520006535 0ustar00/vendor .idea .vscode .svn .DS_Store composer.lock cache/ .php_cs.dist000064400000005666146412215520007002 0ustar00setRiskyAllowed(true) ->setIndent(' ') ->setRules([ '@PSR2' => true, '@PhpCsFixer' => true, '@Symfony:risky' => true, 'concat_space' => ['spacing' => 'one'], 'array_syntax' => ['syntax' => 'short'], 'array_indentation' => true, 'combine_consecutive_unsets' => true, 'method_separation' => true, 'single_quote' => true, 'declare_equal_normalize' => true, 'function_typehint_space' => true, 'hash_to_slash_comment' => true, 'include' => true, 'lowercase_cast' => true, 'no_multiline_whitespace_before_semicolons' => true, 'no_leading_import_slash' => true, 'no_multiline_whitespace_around_double_arrow' => true, 'no_spaces_around_offset' => true, 'no_unneeded_control_parentheses' => true, 'no_unused_imports' => true, 'no_whitespace_before_comma_in_array' => true, 'no_whitespace_in_blank_line' => true, 'object_operator_without_whitespace' => true, 'single_blank_line_before_namespace' => true, 'single_class_element_per_statement' => true, 'space_after_semicolon' => true, 'standardize_not_equals' => true, 'ternary_operator_spaces' => true, 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'whitespace_after_comma_in_array' => true, 'no_extra_consecutive_blank_lines' => [ 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'throw', 'use', ], 'binary_operator_spaces' => [ 'align_double_arrow' => true, 'align_equals' => true, ], 'braces' => [ 'allow_single_line_closure' => true, ], ]) ->setFinder( PhpCsFixer\Finder::create() ->exclude('vendor') ->exclude('tests') ->in(__DIR__) ); README.md000064400000003357146412215520006035 0ustar00## php国密算法 - sm3 - 字符串签名 - 文件签名 - sm4 - ecb - ebc ## 安装 ```shell composer require lizhichao/one-sm ``` ## SM3签名 ```php sign('abc') . PHP_EOL; echo $sm3->sign(str_repeat("adfas哈哈哈", 100)) . PHP_EOL; // 文件签名 echo $sm3->signFile(__FILE__) . PHP_EOL; ``` ### 性能测试 和 [openssl](https://github.com/openssl/openssl) , [SM3-PHP](https://github.com/DongyunLee/SM3-PHP) 性能测试 ```shell php bench.php ``` 结果 ``` openssl:4901d7181a1024b8c0f59b8d3c5c6d96b4b707ad10e8ebc8ece5dc49364a3067 one-sm3:4901d7181a1024b8c0f59b8d3c5c6d96b4b707ad10e8ebc8ece5dc49364a3067 SM3-PHP:4901d7181a1024b8c0f59b8d3c5c6d96b4b707ad10e8ebc8ece5dc49364a3067 openssl time:6.3741207122803ms one-sm3 time:8.1770420074463ms SM3-PHP time:1738.5928630829ms ``` [测试代码bench.php](https://github.com/lizhichao/sm/blob/master/bench.php) ## SM4加密 ```php enDataEcb($data); // 加密后的长度和原数据长度一致 var_dump(strlen($d) === $str_len); // ECB解密 $d = $sm4->deDataEcb($d); // 解密后和原数据相等 var_dump(md5($d) === $sign); // 初始化向量16位 $iv = hex2bin(md5(2)); // CBC加密 $d = $sm4->enDataCbc($data, $iv); // 加密后的长度和原数据长度一致 var_dump(strlen($d)===$str_len); // CBC解密 $d = $sm4->deDataCbc($d, $iv); // 解密后和原数据相等 var_dump(md5($d)===$sign); ``` bench.php000064400000017673146412215520006354 0ustar00sign($str) . PHP_EOL; $t2 = microtime(true); echo 'SM3-PHP:' . sm3($str) . PHP_EOL; $t3 = microtime(true); echo 'openssl time:'.($t1 - $t) * 1000 . 'ms' . PHP_EOL; echo 'one-sm3 time:'.($t2 - $t1) * 1000 . 'ms' . PHP_EOL; echo 'SM3-PHP time:'.($t3 - $t2) * 1000 . 'ms' . PHP_EOL; // 文件签名 //echo \OneSm\Sm3::signFile(__FILE__) . PHP_EOL;composer.json000064400000000536146412215520007274 0ustar00{ "name": "lizhichao/one-sm", "type": "library", "description": "国密sm3", "keywords": [ "php", "sm3" ], "license": "Apache-2.0", "authors": [ { "name": "tanszhe", "email": "1018595261@qq.com" } ], "require": { "php": ">=5.6" }, "autoload": { "psr-4": { "OneSm\\": "src/" } } } src/Sm3.php000064400000007236146412215520006520 0ustar00getK($l); $bt = $this->getB($k); $str = $str . $bt . pack('J', $l); $count = strlen($str); $l = $count / $this->STR_LEN; $vr = hex2bin($this->IV); for ($i = 0; $i < $l; $i++) { $vr = $this->cf($vr, substr($str, $i * $this->STR_LEN, $this->STR_LEN)); } return bin2hex($vr); } private function getK($l) { $v = $l % $this->LEN; return $v + $this->STR_LEN < $this->LEN ? $this->LEN - $this->STR_LEN - $v - 1 : ($this->LEN * 2) - $this->STR_LEN - $v - 1; } private function getB($k) { $arg = [128]; $arg = array_merge($arg, array_fill(0, intval($k / 8), 0)); return pack('C*', ...$arg); } public function signFile($file) { $l = filesize($file) * 8; $k = $this->getK($l); $bt = $this->getB($k) . pack('J', $l); $hd = fopen($file, 'r'); $vr = hex2bin($this->IV); $str = fread($hd, $this->STR_LEN); if ($l > $this->LEN - $this->STR_LEN - 1) { do { $vr = $this->cf($vr, $str); $str = fread($hd, $this->STR_LEN); } while (!feof($hd)); } $str = $str . $bt; $count = strlen($str) * 8; $l = $count / $this->LEN; for ($i = 0; $i < $l; $i++) { $vr = $this->cf($vr, substr($str, $i * $this->STR_LEN, $this->STR_LEN)); } return bin2hex($vr); } private function t($i) { return $i < 16 ? 0x79cc4519 : 0x7a879d8a; } private function cf($ai, $bi) { $wr = array_values(unpack('N*', $bi)); for ($i = 16; $i < 68; $i++) { $wr[$i] = $this->p1($wr[$i - 16] ^ $wr[$i - 9] ^ $this->lm($wr[$i - 3], 15)) ^ $this->lm($wr[$i - 13], 7) ^ $wr[$i - 6]; } $wr1 = []; for ($i = 0; $i < 64; $i++) { $wr1[] = $wr[$i] ^ $wr[$i + 4]; } list($a, $b, $c, $d, $e, $f, $g, $h) = array_values(unpack('N*', $ai)); for ($i = 0; $i < 64; $i++) { $ss1 = $this->lm( ($this->lm($a, 12) + $e + $this->lm($this->t($i), $i % 32) & 0xffffffff), 7); $ss2 = $ss1 ^ $this->lm($a, 12); $tt1 = ($this->ff($i, $a, $b, $c) + $d + $ss2 + $wr1[$i]) & 0xffffffff; $tt2 = ($this->gg($i, $e, $f, $g) + $h + $ss1 + $wr[$i]) & 0xffffffff; $d = $c; $c = $this->lm($b, 9); $b = $a; $a = $tt1; $h = $g; $g = $this->lm($f, 19); $f = $e; $e = $this->p0($tt2); } return pack('N*', $a, $b, $c, $d, $e, $f, $g, $h) ^ $ai; } private function ff($j, $x, $y, $z) { return $j < 16 ? $x ^ $y ^ $z : ($x & $y) | ($x & $z) | ($y & $z); } private function gg($j, $x, $y, $z) { return $j < 16 ? $x ^ $y ^ $z : ($x & $y) | (~$x & $z); } private function lm($a, $n) { return ($a >> (32 - $n) | (($a << $n) & 0xffffffff)); } private function p0($x) { return $x ^ $this->lm($x, 9) ^ $this->lm($x, 17); } private function p1($x) { return $x ^ $this->lm($x, 15) ^ $this->lm($x, 23); } }src/Sm4.php000064400000015004146412215520006511 0ustar00ck16($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('秘钥长度为16位'); } } /** * @param string $str 加密字符串 * @param string $iv 初始化字符串16位 * @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 加密字符串 * @param string $iv 初始化字符串16位 * @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 加密字符串 * @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 解密字符串 * @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); } }tests/sm3.php000064400000002470146412215520007126 0ustar00sign('test')); $pack = 'H' . (string)$size; if (\strlen($secret) > $size) { $secret = pack($pack, $sm3->sign($secret)); } $key = str_pad($secret, $size, \chr(0x00)); $ipad = $key ^ str_repeat(\chr(0x36), $size); $opad = $key ^ str_repeat(\chr(0x5C), $size); $hmac = $sm3->sign($opad . pack($pack, $sm3->sign($ipad . $data))); return $raw_output ? pack($pack, $hmac) : $hmac; } $sm3 = new \OneSm\Sm3(); eq('66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0', $sm3->sign('abc')); eq('1294da78431a20991584c68a669f2c59618e08bf0d7989f35f6ae1d7d570e143', $sm3->sign(str_repeat("adfas哈哈哈", 100))); eq('1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b', $sm3->sign('')); eq( '8e4bd77d8a10526fae772bb6014dfaed0335491e1cdfa92d3aca1481ae5d9a83', hmac(str_repeat('abc', 1000), 'secret') ); eq( hex2bin('8e4bd77d8a10526fae772bb6014dfaed0335491e1cdfa92d3aca1481ae5d9a83'), hmac(str_repeat('abc', 1000), 'secret', true) ); tests/sm4.php000064400000001503146412215520007123 0ustar00enDataEcb($data); // 加密后的长度和原数据长度一致 eq(strlen($d), $str_len); // 解密 $d = $sm4->deDataEcb($d); // 解密后和原数据相等 eq(md5($d), $sign); $iv = hex2bin(md5(2)); // 加密 $d = $sm4->enDataCbc($data, $iv); // 加密后的长度和原数据长度一致 eq(strlen($d), $str_len); // 解密 $d = $sm4->deDataCbc($d, $iv); // 解密后和原数据相等 eq(md5($d), $sign);