commit c5e7c84dea851fbffc2ed12564146ddf8184517f Author: chenc <1458513@qq.com> Date: Tue May 16 16:57:10 2023 +0800 PHP OpenSSL 工具包 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c401de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/composer.lock +.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..af7aeca --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 ionCube Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4be7b9d --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +### PHP OpenSSL 工具包 +#### 功能 +- 生成证书 createCert +- 指定字符串加密 encryptString 解密 decryptString +- 使用证书加密 encryptData 解密 decryptData +- 使用自定义证书 setPublicKey & setPrivateKey 参数 isfile = false +- 国密类 Sm4Helper \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4ece6d1 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "chenc/php-openssl-cryptor", + "description": "creating/encrypting/decrypting for php open_ssl library", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "0x49", + "email": "1458513@qq.com" + } + ], + "autoload": { + "psr-4": { + "Chenc\\PhpOpensslCryptor\\": "src/" + } + }, + "require": { + "php": "7.3", + "ext-openssl": "*" + } +} diff --git a/src/Cryptor.php b/src/Cryptor.php new file mode 100644 index 0000000..b7d89b0 --- /dev/null +++ b/src/Cryptor.php @@ -0,0 +1,250 @@ +setCipherAlgo($cipher_algo); + $this->setHashAlgo($hash_algo); + $this->format = $fmt; + $this->private_key_default_path = $_SERVER['PWD'] . DIRECTORY_SEPARATOR . 'private.key'; + $this->public_key_default_path = $_SERVER['PWD'] . DIRECTORY_SEPARATOR . 'public.pem'; + } + + public function setCipherAlgo($value) + { + if (!in_array($value, openssl_get_cipher_methods(true))) { + throw new \Exception("Cryptor:: - unknown cipher algo {$value}"); + } + $this->iv_num_bytes = openssl_cipher_iv_length($value); + $this->cipher_algo = $value; + } + + public function setHashAlgo($value) + { + if (!in_array($value, openssl_get_md_methods(true))) { + throw new \Exception("Cryptor:: - unknown hash algo {$value}"); + } + $this->hash_algo = $value; + } + + public function encryptString($in, $key, $fmt = null) + { + if ($fmt === null) { + $fmt = $this->format; + } + $iv = openssl_random_pseudo_bytes($this->iv_num_bytes, $isStrongCrypto); + if (!$isStrongCrypto) { + throw new \Exception("Cryptor::encryptString() - Not a strong key"); + } + $keyhash = openssl_digest($key, $this->hash_algo, true); + $opts = OPENSSL_RAW_DATA; + $encrypted = openssl_encrypt($in, $this->cipher_algo, $keyhash, $opts, $iv); + if ($encrypted === false) { + throw new \Exception('Cryptor::encryptString() - Encryption failed: ' . openssl_error_string()); + } + $res = $iv . $encrypted; + if ($fmt == Cryptor::FORMAT_B64) { + $res = base64_encode($res); + } else if ($fmt == Cryptor::FORMAT_HEX) { + $res = unpack('H*', $res)[1]; + } + return $res; + } + + public function decryptString($in, $key, $fmt = null) + { + if ($fmt === null) { + $fmt = $this->format; + } + $raw = $in; + if ($fmt == Cryptor::FORMAT_B64) { + $raw = base64_decode($in); + } else if ($fmt == Cryptor::FORMAT_HEX) { + $raw = pack('H*', $in); + } + if (strlen($raw) < $this->iv_num_bytes) { + throw new \Exception('Cryptor::decryptString() - ' . + 'data length ' . strlen($raw) . " is less than iv length {$this->iv_num_bytes}"); + } + $iv = substr($raw, 0, $this->iv_num_bytes); + $raw = substr($raw, $this->iv_num_bytes); + $keyhash = openssl_digest($key, $this->hash_algo, true); + $opts = OPENSSL_RAW_DATA; + $res = openssl_decrypt($raw, $this->cipher_algo, $keyhash, $opts, $iv); + if ($res === false) { + throw new \Exception('Cryptor::decryptString - decryption failed: ' . openssl_error_string()); + } + return $res; + } + + /** + * 使用证书加密 + * @param $data string 使用证书加密 + * @param $public_key string 公钥证书路径 + * @return mixed|string + */ + public function encryptData($data, $public_key) + { + $public_key = openssl_pkey_get_public($this->getPublicKey($public_key)); + $encryptedData = ''; + $res = ''; + openssl_public_encrypt($data, $encryptedData, $public_key); + if ($this->format === Cryptor::FORMAT_B64) { + $res = base64_encode($encryptedData); + } else if ($this->format === Cryptor::FORMAT_HEX) { + $res = unpack('H*', $encryptedData)[1]; + } else { + $res = $encryptedData; + } + return $res; + } + + /** + * 使用证书解密 + * @param $data string 解密字符 + * @param $private_key string 私钥证书路径 + * @param $passphrase string 私钥密码 + * @return string + */ + function decryptData($data, $passphrase = null, $private_key = '') + { + // 加载私钥 + $private_key = openssl_pkey_get_private($this->getPrivateKey($private_key), $passphrase); + $sensitiveData = ''; + if ($this->format == Cryptor::FORMAT_B64) { + $raw = base64_decode($data); + } else if ($this->format == Cryptor::FORMAT_HEX) { + $raw = pack('H*', $data); + } else { + $raw = $data; + } + openssl_private_decrypt($raw, $sensitiveData, $private_key); + return $sensitiveData; + } + + /** + * @param $save_path string 证书保存路径 + * @param $key_bits string 使用多少位来生成私钥 + * @param $key_type int 创建CSR时应该使用哪些扩展(默认OPENSSL_KEYTYPE_RSA) + * @param $passphrase string 证书密码(默认NULL) + * @param $config_path string 自定义 openssl.conf 文件的路径 + * @return boolean + * @throws \Exception + */ + public function createCert($passphrase = null, $force = false, $save_path = '', $key_bits = '2048', $key_type = OPENSSL_KEYTYPE_RSA, $config_path = '') + { + try { + if (empty($save_path)) { + $save_path = $_SERVER['PWD']; + } + $config = array( + 'digest_alg' => $this->hash_algo, //可以用openssl_get_md_methods() 查看支持的加密方法 + 'private_key_bits' => $key_bits, + 'private_key_type' => $key_type, + ); + if (!empty($config_path)) { + $config['config'] = $config_path; + } + $res = openssl_pkey_new($config); + openssl_pkey_export($res, $private_key_pem, $passphrase, $config); + $details = openssl_pkey_get_details($res); + $public_key_pem = $details['key']; + $this->setPublicKey($public_key_pem, empty($save_path) ? $this->public_key_default_path : $save_path . DIRECTORY_SEPARATOR . 'public.pem', true, $force); + $this->setPrivateKey($private_key_pem, empty($save_path) ? $this->private_key_default_path : $save_path . DIRECTORY_SEPARATOR . 'private.key', true, $force); + return true; + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + public function setPublicKey($data, $save_path = '', $is_file = true, $force = false) + { + if ($is_file) { + if (empty($save_path)) { + $save_path = $this->public_key_default_path; + } + if ($force) { + if (file_exists($save_path)) { + $_old = file_get_contents($save_path); + file_put_contents($save_path . '_' . time() . '.bak', $_old); + } + $this->public_key = $data; + file_put_contents($save_path, $data); + } else { + if (file_exists($save_path)) { + return false; + } else { + $this->public_key = $data; + file_put_contents($save_path, $data); + } + } + + } else { + $this->public_key = $data; + } + } + + public function getPublicKey($save_path = '') + { + if (empty($this->public_key)) { + if (empty($save_path)) { + $save_path = $this->public_key_default_path; + } + $this->public_key = file_get_contents($save_path); + } + return $this->public_key; + } + + public function setPrivateKey($data, $save_path = '', $is_file = true, $force = false) + { + if ($is_file) { + if (empty($save_path)) { + $save_path = $this->private_key_default_path; + } + if ($force) { + if (file_exists($save_path)) { + $_old = file_get_contents($save_path); + file_put_contents($save_path . '_' . time() . '.bak', $_old); + } + $this->private_key = $data; + file_put_contents($save_path, $data); + } else { + if (file_exists($save_path)) { + return false; + } else { + $this->private_key = $data; + file_put_contents($save_path, $data); + } + } + } else { + $this->private_key = $data; + } + } + + public function getPrivateKey($save_path) + { + if (empty($this->private_key)) { + if (empty($save_path)) { + $save_path = $this->private_key_default_path; + } + $this->private_key = file_get_contents($save_path); + } + return $this->private_key; + } +} \ No newline at end of file diff --git a/src/Sm4Helper.php b/src/Sm4Helper.php new file mode 100644 index 0000000..9011fdb --- /dev/null +++ b/src/Sm4Helper.php @@ -0,0 +1,196 @@ +sm4KeySchedule($key); + + $bytes = $this->pad($data, $this->_block_size); + $chunks = array_chunk($bytes, $this->_block_size); + + $ciphertext = ""; + foreach ($chunks as $chunk) { + $ciphertext .= $this->sm4Encrypt($chunk); + } + + return base64_encode($ciphertext); + } + + /** + * sm4解密 + * @param $key + * @param $data + * @return bool|string + */ + public function decrypt($key, $data) + { + $data = base64_decode($data); + if (strlen($data) < 0 || strlen($data) % $this->_block_size != 0) { + return false; + } + + $this->sm4KeySchedule($key); + $bytes = unpack("C*", $data); + $chunks = array_chunk($bytes, $this->_block_size); + + $plaintext = ""; + foreach ($chunks as $chunk) { + $plaintext .= substr($this->sm4Decrypt($chunk), 0, 16); + } + $plaintext = $this->unPad($plaintext); + + return $plaintext; + } + + private function sm4Decrypt($cipherText) + { + $x = []; + for ($j = 0; $j < 4; $j++) { + $x[$j] = ($cipherText[$j * 4] << 24) | ($cipherText[$j * 4 + 1] << 16) | ($cipherText[$j * 4 + 2] << 8) | ($cipherText[$j * 4 + 3]); + } + + for ($i = 0; $i < 32; $i++) { + $tmp = $x[$i + 1] ^ $x[$i + 2] ^ $x[$i + 3] ^ $this->_rk[31 - $i]; + $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[$i + 4] = $x[$i] ^ ($buf ^ $this->sm4Rotl32(($buf), 2) ^ $this->sm4Rotl32(($buf), 10) ^ $this->sm4Rotl32(($buf), 18) ^ $this->sm4Rotl32(($buf), 24)); + } + + $plainText = []; + for ($k = 0; $k < 4; $k++) { + $plainText[4 * $k] = ($x[35 - $k] >> 24) & 0xFF; + $plainText[4 * $k + 1] = ($x[35 - $k] >> 16) & 0xFF; + $plainText[4 * $k + 2] = ($x[35 - $k] >> 8) & 0xFF; + $plainText[4 * $k + 3] = ($x[35 - $k]) & 0xFF; + } + + return $this->bytesToString($plainText); + } + + private function sm4Encrypt($plainText) + { + $x = []; + for ($j = 0; $j < 4; $j++) { + $x[$j] = ($plainText[$j * 4] << 24) | ($plainText[$j * 4 + 1] << 16) | ($plainText[$j * 4 + 2] << 8) | ($plainText[$j * 4 + 3]); + } + + for ($i = 0; $i < 32; $i++) { + $tmp = $x[$i + 1] ^ $x[$i + 2] ^ $x[$i + 3] ^ $this->_rk[$i]; + $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[$i + 4] = $x[$i] ^ ($buf ^ $this->sm4Rotl32(($buf), 2) ^ $this->sm4Rotl32(($buf), 10) ^ $this->sm4Rotl32(($buf), 18) ^ $this->sm4Rotl32(($buf), 24)); + } + + $cipherText = []; + for ($k = 0; $k < 4; $k++) { + $cipherText[4 * $k] = ($x[35 - $k] >> 24) & 0xFF; + $cipherText[4 * $k + 1] = ($x[35 - $k] >> 16) & 0xFF; + $cipherText[4 * $k + 2] = ($x[35 - $k] >> 8) & 0xFF; + $cipherText[4 * $k + 3] = ($x[35 - $k]) & 0xFF; + } + + return $this->bytesToString($cipherText); + } + + private function stringToBytes($string) + { + return unpack('C*', $string); + } + + private function bytesToString($bytes) + { + return vsprintf(str_repeat('%c', count($bytes)), $bytes); + } + + private function pad($data) + { + $bytes = $this->stringToBytes($data); + $rem = $this->_block_size - count($bytes) % $this->_block_size; + for ($i = 0; $i < $rem; $i++) { + array_push($bytes, $rem); + } + return $bytes; + } + + private function unPad($data) + { + $bytes = $this->stringToBytes($data); + $rem = $bytes[count($bytes)]; + $bytes = array_slice($bytes, 0, count($bytes) - $rem); + return $this->bytesToString($bytes); + } + + private function sm4Rotl32($buf, $n) + { + return (($buf << $n) & 0xffffffff) | ($buf >> (32 - $n)); + } + + private function sm4KeySchedule($key) + { + $this->_rk = []; + $key = array_values(unpack("C*", $key)); + + $k = []; + for ($i = 0; $i < 4; $i++) { + $k[$i] = self::SM4_FK[$i] ^ (($key[4 * $i] << 24) | ($key[4 * $i + 1] << 16) | ($key[4 * $i + 2] << 8) | ($key[4 * $i + 3])); + } + + for ($j = 0; $j < 32; $j++) { + $tmp = $k[$j + 1] ^ $k[$j + 2] ^ $k[$j + 3] ^ self::SM4_CK[$j]; + $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]); + + $k[$j + 4] = $k[$j] ^ (($buf) ^ ($this->sm4Rotl32(($buf), 13)) ^ ($this->sm4Rotl32(($buf), 23))); + $this->_rk[$j] = $k[$j + 4]; + } + } +} diff --git a/tests/BaseTestCryptor.php b/tests/BaseTestCryptor.php new file mode 100644 index 0000000..304af6d --- /dev/null +++ b/tests/BaseTestCryptor.php @@ -0,0 +1,10 @@ +createCert('123456',true); +var_dump($x->encryptString('123456','0987654321')); +var_dump($x->decryptString('8saj9YFKzbyc7Af6Mv93iyPybW7dAw==','0987654321')); +var_dump($x->encryptData('123456','')); +var_dump($x->decryptData('R1i4pURMUT6jXrJBhsclzPGh1e+ga9ivcE0Uh+OUJCvM5c5Av4HUh25c2fxSikSdQlFG0lXFC0UTI3p9Ag+jG2halnbRm//Cv9fwFMzZYtBB+1A/Q6TnAA8NRNgG5GA44p69iNG49dCOqwrN0aS6A1qgrFUfnE0g99sfRFMaohgicsK1pi1UQPIWQtCY5pzVe1FzmMUE4zCuQtKMnrUdxa+t9Gf8IVAXlwy8BWDshwx7aGzyXBKKErb/nt/6RdwnBEAEUrh0hueNJJRFVWusrI0DMMJhaa95HoF31kLx7Ziev7t2sgTOCtLYAAcXXVDvoFkSCBVJc0USNizcoSiuJg==','123456'));