adapter = RtEccFactory::getAdapter(); $this->generator = RtEccFactory::getSmCurves()->generatorSm2(); if (in_array($formatSign, $this->arrFormat)) { $this->formatSign = $formatSign; } else { $this->formatSign = 'hex'; } if (!$randFixed) { $this->useDerandomizedSignatures = false; $this->useDerandomizedEncrypt = false; } } /** * 随机生成一对16进制明文公私钥 */ public function generatekey() { // $adapter = $this->adapter; $generator = $this->generator; //随机生成一个私钥类 $private = $generator->createPrivateKey(); //取出私钥16进制表示出来 $privateKey = $this->decHex($private->getSecret()); //取出公钥的椭圆点 $pubPoint = $private->getPublicKey()->getPoint(); //公钥上的点x, y $pubX = $this->decHex($pubPoint->getX()); $pubY = $this->decHex($pubPoint->getY()); $publicKey = '04' . $pubX . $pubY; return [$privateKey, $publicKey]; } /** * 随机生成一对PEM编码公私钥 */ public function generatePemkey() { $adapter = $this->adapter; $generator = $this->generator; $private = $generator->createPrivateKey(); $derSerializer = new DerPrivateKeySerializer($adapter); // der包 ans1编码 1 版本号 2私钥 3 oid 4 公钥 四组数据 // $der = $derSerializer->serialize( $private ); $pemSerializer = new PemPrivateKeySerializer($derSerializer); $privateKeyPem = $pemSerializer->serialize($private); $derPubSerializer = new DerPublicKeySerializer($adapter); $pemPubSerializer = new PemPublicKeySerializer($derPubSerializer); $publicKeyPem = $pemPubSerializer->serialize($private->getPublicKey()); return [$privateKeyPem, $publicKeyPem]; } /** * SM2 公钥加密算法 * * @param string $document * @param string $publicKey 如提供的base64的,可使用 bin2hex(base64_decode($publicKey)) * @return string */ public function doEncrypt($document, $publicKey, $model = C1C3C2) { $adapter = $this->adapter; $generator = $this->generator; $this->cipher = new \Rtgm\smecc\SM2\Cipher(); $arrMsg = Hex2ByteBuf::HexStringToByteArray2(bin2hex($document)); list($pubKeyX, $pubKeyY) = $this->_getKeyXY($publicKey); // $key = $this->_getPubKeyObject( $pubKeyX, $pubKeyY ); $point = new Point($adapter, $generator->getCurve(), gmp_init($pubKeyX, 16), gmp_init($pubKeyY, 16)); // 是否使用固定的中间椭圆加密, if ($this->useDerandomizedEncrypt) { $c1 = $this->cipher->initEncipher($point, $this->foreignKey); } else { $c1 = $this->cipher->initEncipher($point, null); } // print_r($c1); $arrMsg = $this->cipher->encryptBlock($arrMsg); $c2 = strtolower(Hex2ByteBuf::ByteArrayToHexString($arrMsg)); // print_R($c2);echo "\n"; $c3 = strtolower(Hex2ByteBuf::ByteArrayToHexString($this->cipher->Dofinal())); // print_r($c1.$c3.$c2); if ($model == C1C3C2) { return $c1 . $c3 . $c2; } else { return $c1 . $c2 . $c3; } } /** * SM2 私钥解密算法, * * @param string $document * @param string $privateKey 如提供的base64的,可使用 bin2hex(base64_decode($privateKey)) * @param bool $trim 是否做04开头的去除,看业务返回 * @return string */ public function doDecrypt($encryptData, $privateKey, $trim = true, $model = C1C3C2) { // $encryptData = $c1.$c3.$c2 if (substr($encryptData, 0, 2) == '04' && $trim) { $encryptData = substr($encryptData, 2); } if (strlen($privateKey) == 66 && substr($privateKey, 0, 2) == '00') { $privateKey = substr($privateKey, 2); // 个别的key 前面带着00 } $adapter = $this->adapter; $generator = $this->generator; $this->cipher = new \Rtgm\smecc\SM2\Cipher(); $c1X = substr($encryptData, 0, 64); $c1Y = substr($encryptData, strlen($c1X), 64); $c1Length = strlen($c1X) + strlen($c1Y); if ($model == C1C3C2) { $c3 = substr($encryptData, $c1Length, 64); $c2 = substr($encryptData, $c1Length + strlen($c3)); } else { $c3 = substr($encryptData, -64); $c2 = substr($encryptData, $c1Length, strlen($encryptData) - $c1Length - 64); } $p1 = new Point($adapter, $generator->getCurve(), gmp_init($c1X, 16), gmp_init($c1Y, 16)); $this->cipher->initDecipher($p1, $privateKey); $arrMsg = Hex2ByteBuf::HexStringToByteArray2($c2); $arrMsg = $this->cipher->decryptBlock($arrMsg); $document = hex2bin(Hex2ByteBuf::ByteArrayToHexString($arrMsg)); $c3_ = strtolower(Hex2ByteBuf::ByteArrayToHexString($this->cipher->Dofinal())); $c3 = strtolower($c3); if ($c3 == $c3_) { //hash签名相同, return $document; } else { return ''; } } /** * SM2 签名明文16进制密码, 如提供的base64的,可使用 bin2hex(base64_decode($privateKey)) * */ public function doSign($document, $privateKey, $userId = null) { if (empty($userId)) { $userId = $this->userId; } $adapter = $this->adapter; $generator = $this->generator; $algorithm = 'sha256'; $secret = gmp_init($privateKey, 16); $key = new PrivateKey($adapter, $generator, $secret); return $this->_dosign($document, $key, $adapter, $generator, $userId, $algorithm); } /** * SM2 签名pem密码 * */ public function doSignOutKey($document, $privateKeyFile, $userId = null) { if (empty($userId)) { $userId = $this->userId; } if (!file_exists($privateKeyFile)) { throw new \Exception('privatekey file not exists'); } $adapter = $this->adapter; $generator = $this->generator; //这个sha256 只是生成随机数时用到,和主体算法无关 $algorithm = 'sha256'; $pemSerializer = new PemPrivateKeySerializer(new DerPrivateKeySerializer($adapter)); $keyData = file_get_contents($privateKeyFile); $key = $pemSerializer->parse($keyData); return $this->_dosign($document, $key, $adapter, $generator, $userId, $algorithm); } protected function _dosign($document, $key, $adapter, $generator, $userId, $algorithm = 'sha256') { // $publickey = $key->getPublicKey(); $obPoint = $key->getPublicKey()->getPoint(); $pubKeyX = $adapter->decHex($obPoint->getX()); $pubKeyY = $adapter->decHex($obPoint->getY()); $hash = $this->_doS3Hash($document, $pubKeyX, $pubKeyY, $generator, $userId); # Derandomized signatures are not necessary, but is avoids # the risk of a low entropy RNG, causing accidental reuse # of a k value for a different message, which leaks the # private key. if ($this->useDerandomizedSignatures) { $random = \Mdanter\Ecc\Random\RandomGeneratorFactory::getHmacRandomGenerator($key, $hash, $algorithm); } else { $random = \Mdanter\Ecc\Random\RandomGeneratorFactory::getRandomGenerator(); } $randomK = $random->generate($generator->getOrder()); $signer = new Sm2Signer($adapter); $signature = $signer->sign($key, $hash, $randomK); $serializer = new DerSignatureSerializer(); $serializedSig = $serializer->serialize($signature); if ($this->formatSign == 'hex') { return bin2hex($serializedSig); } else if ($this->formatSign == 'base64') { return base64_encode($serializedSig) . PHP_EOL; } //缺省 hex return bin2hex($serializedSig); } public function verifySign($document, $sign, $publicKey, $userId = null) { $adapter = $this->adapter; $generator = $this->generator; if (empty($userId)) { $userId = $this->userId; } if ($this->formatSign == 'hex') { $sigData = hex2bin($sign); } else if ($this->formatSign == 'base64') { $sigData = base64_decode($sign); } else { $sigData = hex2bin($sign); } // Parse signature $sigSerializer = new DerSignatureSerializer(); $sig = $sigSerializer->parse($sigData); // get hash list($pubKeyX, $pubKeyY) = $this->_getKeyXY($publicKey); $hash = $this->_doS3Hash($document, $pubKeyX, $pubKeyY, $generator, $userId); // get pubkey parse $key = $this->_getPubKeyObject($pubKeyX, $pubKeyY); $signer = new Sm2Signer($adapter); return $signer->verify($key, $sig, $hash); } public function verifySignOutKey($document, $sign, $publickeyFile, $userId = null) { if (empty($userId)) { $userId = $this->userId; } if (!file_exists($publickeyFile)) { throw new \Exception('publickey file not exists'); } $adapter = $this->adapter; $generator = $this->generator; if ($this->formatSign == 'hex') { $sigData = hex2bin($sign); } else if ($this->formatSign == 'base64') { $sigData = base64_decode($sign); } else { $sigData = hex2bin($sign); } // Parse signature $sigSerializer = new DerSignatureSerializer(); $sig = $sigSerializer->parse($sigData); // Parse public key $keyData = file_get_contents($publickeyFile); $derSerializer = new DerPublicKeySerializer($adapter); $pemSerializer = new PemPublicKeySerializer($derSerializer); $key = $pemSerializer->parse($keyData); $pubKeyX = $this->decHex($key->getPoint()->getX()); $pubKeyY = $this->decHex($key->getPoint()->getY()); $hash = $this->_doS3Hash($document, $pubKeyX, $pubKeyY, $generator, $userId); $signer = new Sm2Signer($adapter); return $signer->verify($key, $sig, $hash); } /** * */ protected function _doS3Hash($document, $pubKeyX, $pubKeyY, $generator, $userId) { $hasher = new Sm2WithSm3(); $hash = $hasher->getSm3Hash($document, $pubKeyX, $pubKeyY, $generator, $userId); return gmp_init(Hex2ByteBuf::ByteArrayToHexString($hash), 16); } protected function _getKeyXY($publicKey) { if (strlen($publicKey) == 128) { $pubKeyX = substr($publicKey, 0, 64); $pubKeyY = substr($publicKey, -64); } else if (strlen($publicKey) == 130 && substr($publicKey, 0, 2) == '04') { $pubKeyX = substr($publicKey, 2, 64); $pubKeyY = substr($publicKey, -64); } else { throw new \Exception('publickey format error'); } return [$pubKeyX, $pubKeyY]; } protected function _getPubKeyObject($pubKeyX, $pubKeyY) { $generator = $this->generator; // __construct( GmpMathInterface $adapter, CurveFpInterface $curve, \GMP $x, \GMP $y, \GMP $order = null, bool $infinity = false ) $x = gmp_init($pubKeyX, 16); $y = gmp_init($pubKeyY, 16); $point = new Point($this->adapter, $generator->getCurve(), $x, $y); // __construct( GmpMathInterface $adapter, GeneratorPoint $generator, PointInterface $point ) return new PublicKey($this->adapter, $this->generator, $point); } protected function _str2hex($str) { $res = array(); for ($i = 0; $i < strlen($str); $i++) { $res[$i] = sprintf("%02x", ord($str[$i])); } return implode("", $res); } private function decHex($dec, $len = 64): string { if (gettype($dec) == 'string') { $dec = gmp_init($dec, 10); } if (gmp_cmp($dec, 0) < 0) { throw new \InvalidArgumentException('Unable to convert negative integer to string'); } $hex = gmp_strval($dec, 16); /* if (strlen($hex) % 2 != 0) { $hex = '0'.$hex; } */ $left = $len - strlen($hex); if ($left > 0) { $hex = str_repeat('0', $left) . $hex; } return $hex; } }