appid = '38dc55300f454e28b6204226fbc3033d'; // $this->secret_key = '3dd02385af49beb9f2227f619e76bdc4'; // //测试替换url 测试码 // $this->check_url = 'https://wlc.nppa.gov.cn/test/authentication/check'; // $this->query_url = 'https://wlc.nppa.gov.cn/test/authentication/query'; // $this->loginout = 'https://wlc.nppa.gov.cn/test/collection/loginout'; // $this->isTest = true; $config = config('zxsm'); $this->appid = $config['app_id']; $this->secret_key = $config['secret_key']; $this->check_url = 'https://api.wlc.nppa.gov.cn/idcard/authentication/check'; $this->query_url = 'http://api2.wlc.nppa.gov.cn/idcard/authentication/query'; $this->loginout = 'http://api2.wlc.nppa.gov.cn/behavior/collection/loginout'; $this->isTest = false; //时间戳 $this->time = $this->getMillisecond(); } //入口在这里 //直接运行这里 //1.需要配置$this->check() 函数中的配置 query loginout 同上 public function index() { //testcase01-实名认证接口 //testcase02-实名认证接口 //testcase03-实名认证接口 $check_res = $this->check(); echo '
';
var_dump($check_res);
die();
/*
//testcase04-实名认证结果查询接口
//testcase05-实名认证结果查询接口
//testcase06-实名认证结果查询接口
$query_res = $this->query();
var_dump($query_res);die();
//testcase07-游戏用户行为数据上报接口
//testcase08-游戏用户行为数据上报接口
$loginout_res = $this->loginout();
var_dump($loginout_res);
*/
}
/**
* [check 实名认证接口]
* @Author shyn0121@qq.com
* @DateTime 2021-03-05
* @route [route]
* @return [type] [description] ($url, $data, $headers, $is_post = true, $is_ssl = false)
*/
public function check($bizid = '', $ai = '', $realname = '', $idcard = '')
{
$this->initConfig();
if($this->isTest){
$ai = '100000000000000001';
}
//测试数据替换数据 用预置数据测试 https://wlc.nppa.gov.cn/fcm_company/%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E9%98%B2%E6%B2%89%E8%BF%B7%E5%AE%9E%E5%90%8D%E8%AE%A4%E8%AF%81%E7%B3%BB%E7%BB%9F%E6%B5%8B%E8%AF%95%E7%B3%BB%E7%BB%9F%E8%AF%B4%E6%98%8E.pdf
//下面的数据测出为失败 一定要用预置数据哦 query 同理 loginout 也要注意配置
if (!trim($bizid) || !trim($ai) || !trim($realname) || !trim($idcard)) {
$retData = array();
$retData['errcode'] = 9999;
$retData['errmsg'] = '验证参数不完整';
return $retData;
}
if (trim($bizid)) {
$this->bizid = trim($bizid);
}
if (trim($ai)) {
$this->ai = trim($ai);
}
if (trim($realname)) {
$this->name = trim($realname);
}
if (trim($idcard)) {
$this->id_num = trim($idcard);
}
/*
if (empty($this->ai) || empty($this->name) || empty($this->id_num)) {
die("body请对配置参数赋值");
}
*/
//业务参数
$body_params = [
'ai' => $this->ai,
'name' => $this->name,
'idNum' => $this->id_num,
];
log_message($body_params, 'log', LOG_PATH . 'authentication/');
//string
$this->body = $this->getBody($body_params);
// echo $this->body."----body-----
";
$this->sign = $this->getSign($this->body);
$this->header = $this->makeHeader($this->getHeaders($this->sign));
$return_data = $this->getJson($this->check_url, $this->body, $this->header, true);
$errmsg = $return_data['errmsg'];
if ($return_data['errcode'] > 1000 & $return_data['errcode'] < 2000) {
$errmsg = '系统异常,请联系开发者!';
curlDD("中宣实名认证接口异常:" . $return_data['errcode'] . ' - ' . $return_data['errmsg'], Env::get('dingtalk.warning_url'));
}
if ($return_data['errcode'] > 2000) {
$errmsg = '实名认证业务异常!';
if ($return_data['errcode'] == 2001) {
$errmsg = '身份证号校验不通过!';
} else if ($return_data['errcode'] == 2005) {
$errmsg = '姓名合法性校验不通过!';
} else {
curlDD("中宣实名认证(check)接口失败:" . $return_data['errcode'] . ' - ' . $return_data['errmsg'], Env::get('dingtalk.notic_url'));
}
}
$return_data['errmsg'] = $errmsg;
return $return_data;
}
public function checkV2($bizid = '', $ai = '', $realname = '', $idcard = '')
{
$this->initConfig();
if($this->isTest){
$ai = '100000000000000001';
}
//下面的数据测出为失败 一定要用预置数据哦 query 同理 loginout 也要注意配置
if (!trim($bizid) || !trim($ai) || !trim($realname) || !trim($idcard)) {
return ['code' => -110, 'data' => [], 'msg' => '验证参数不完整'];
}
if (trim($bizid)) {
$this->bizid = trim($bizid);
}
if (trim($ai)) {
$this->ai = trim($ai);
}
if (trim($realname)) {
$this->name = trim($realname);
}
if (trim($idcard)) {
$this->id_num = trim($idcard);
}
//业务参数
$body_params = [
'ai' => $this->ai,
'name' => $this->name,
'idNum' => $this->id_num,
];
log_message($body_params, 'log', LOG_PATH . 'authentication/');
$this->body = $this->getBody($body_params);
$this->sign = $this->getSign($this->body);
$this->header = $this->makeHeader($this->getHeaders($this->sign));
$return_data = $this->getJson($this->check_url, $this->body, $this->header, true);
/**
* 返回成功示例:
*
*array(3) {
* ["errcode"] => int(0)
* ["errmsg"] => string(2) "OK"
* ["data"] => array(1) {
* ["result"] => array(2) {
* ["status"] => int(0)
* ["pi"] => string(38) "1hhi7599umwfpjhy9sdbk8qn3sids5jfw86ez2"
* }
* }
* }
*/
if($return_data['errcode'] === 0){
return ['code' => 200, 'data' => $return_data['data']['result'], 'msg' => 'success'];
}
$errmsg = $return_data['errmsg'];
if ($return_data['errcode'] > 1000 & $return_data['errcode'] < 2000) {
$errmsg = '认证系统异常,请联系商务反馈!';
curlDD("中宣实名认证接口异常:" . $return_data['errcode'] . '; ' . $return_data['errmsg']."; ai={$this->ai}", Env::get('dingtalk.warning_url'));
}
if ($return_data['errcode'] > 2000) {
if ($return_data['errcode'] == 2001) {
$errmsg = '认证失败,身份证号校验不通过!';
} else if ($return_data['errcode'] == 2004) {
$errmsg = '认证失败,认证提交频繁,请稍后!';
} else if ($return_data['errcode'] == 2005) {
$errmsg = '认证失败,姓名合法性校验不通过!';
} else {
$errmsg = '认证失败,实名认证业务异常!';
curlDD("中宣实名认证(check)接口失败:" . $return_data['errcode'] . '; ' . $return_data['errmsg']."; ai={$this->ai}", Env::get('dingtalk.notic_url'));
}
}
return ['code' => -100, 'data' => [], 'msg' => $errmsg];
}
/**
* [query 实名认证结果查询接口]
* @Author shyn0121@qq.com
* @DateTime 2021-03-05
* @route [route]
* @return [type] [description]
*/
public function query($bizid = '', $ai = '')
{
//需要用预置数据https://wlc.nppa.gov.cn/fcm_company/%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E9%98%B2%E6%B2%89%E8%BF%B7%E5%AE%9E%E5%90%8D%E8%AE%A4%E8%AF%81%E7%B3%BB%E7%BB%9F%E6%B5%8B%E8%AF%95%E7%B3%BB%E7%BB%9F%E8%AF%B4%E6%98%8E.pdf
/*
$this->ai = '300000000000000005'; //游戏内部对应的唯一标识建议32位
$this->bizid = "1101999999";
*/
$this->initConfig();
if($this->isTest){
$ai = '100000000000000001';
}
if (!trim($bizid) || !trim($ai)) {
$retData = array();
$retData['errcode'] = 9999;
$retData['errmsg'] = '查询参数不完整';
return $retData;
}
if (trim($bizid)) {
$this->bizid = trim($bizid);
}
if (trim($ai)) {
$this->ai = trim($ai);
}
/*
if (empty($this->ai)) {
die("body请对配置参数赋值");
}
*/
//业务参数
$body_params = [
'ai' => $this->ai,
];
//string
$this->sign = $this->getSign($body_params);
$this->header = $this->makeHeader($this->getHeaders($this->sign));
$return_data = $this->getJson($this->query_url . '?ai=' . $this->ai, '', $this->header, false);
return $return_data;
}
/**
* [loginout 游戏用户行为数据上报接口]
* @param $bizid
* @param $sid 一个会话标识只能对应唯一的实名用户,一个实名用户可以拥有多个会话标识;同一用户单次游戏会话中,上下线动作必须使用同一会话标识上报备注:会话标识仅标识一次用户会话,生命周期仅为一次上线和与之匹配的一次下线,不会对生命周期之外的任何业务有任何影响
* @param $bt 游戏用户行为类 0:下线1:上线
* @param $ct 用户行为数据上报类型 0:已认证通过用户2:游客用户
* @param $di 游客模式设备标识,由游戏运营单位生成,游客用户下必填
* @param $pi 已通过实名认证用户的唯一标识,已认证通过用户必填
* @return array|int|mixed
*/
public function loginout($bizid = '', $sid = '', $bt = 1, $ct = 0, $di = '', $pi = '')
{
//$this->bizid = "1101999999";
if (!trim($bizid)) {
$retData = array();
$retData['errcode'] = 9999;
$retData['errmsg'] = '查询参数不完整';
return $retData;
}
if (trim($bizid)) {
$this->bizid = trim($bizid);
}
$this->initConfig();
/*
$cdata = [
//可行 游客
'no' => 1,
'si' => 'w7ligxjw355ftctm94yqt9dcew4zd723',
'bt' =>0,
'ot' => time(),
'ct' => 2,
'di'=>'uyiv6clpf7cu296pd4ppv11le820dhkw',
'pi' => '1fffbjzos82bs9cnyj1dna7d6d29zg4esnh99u',
//可行 已认证
'no' => 1,
'si' => 'w7ligxjw355ftctm94yqt9dcew4zd723',
'bt' =>0,
'ot' => time(),
'ct' => 0,
'pi' => '1fffbjzos82bs9cnyj1dna7d6d29zg4esnh99u',
];
*/
$cdata = array();
$cdata['no'] = 1;
$cdata['si'] = md5($sid);
$cdata['bt'] = $bt;
$cdata['ot'] = time() - 10;
if ($ct == 0) {
$cdata['ct'] = $ct;
$cdata['pi'] = trim($pi);
} else {
$cdata['ct'] = $ct;
$cdata['di'] = trim($di);
$cdata['pi'] = trim($pi);
}
// var_dump($cdata);
//业务参数
$body_params = [
'collections' => [$cdata],
];
//string
$this->body = $this->getBody($body_params);
$this->sign = $this->getSign($this->body);
$this->header = $this->makeHeader($this->getHeaders($this->sign));
$return_data = $this->getJson($this->loginout, $this->body, $this->header, true);
// var_dump($return_data);
return $return_data;
}
/**
* [接口调用签名]
* @Author shyn0121@qq.com
* @DateTime 2021-03-05
* @data [请求体 json]
* @return [type] [description]
*/
public function getSign($rawBody)
{
$data = $this->getHeaders();
if (is_array($rawBody)) {
$data += $rawBody;
}
ksort($data);
$source = [$this->secret_key];
foreach ($data as $key => $value) {
if ($key !== 'sign' && $key != 'Content-Type') {
$source[] = "{$key}{$value}";
}
}
if (!is_array($rawBody)) {
$source[] = $rawBody;
}
$presign = implode("", $source);
return hash("sha256", $presign);
}
//报文体
public function getBody($data)
{
// $string = Aes::encrypt(json_encode($data), $this->secret_key);
$string = $this->aesGcmEncrypt($data);
return json_encode(['data' => $string]);
}
public function aesGcmEncrypt($string)
{
$cipher = strtolower('AES-128-GCM');
if (is_array($string)) $string = json_encode($string);
//二进制key
$skey = hex2bin($this->secret_key);
//二进制iv
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
list($content, $tag) = AESGCM::encrypt($skey, $iv, $string);
//如果环境是php7.1+,直接使用下面的方式
// $tag = NULL;
// $content = openssl_encrypt($string, $cipher, $skey,OPENSSL_RAW_DATA,$iv,$tag);
$str = bin2hex($iv) . bin2hex($content) . bin2hex($tag);
return base64_encode(hex2bin($str));
}
public function getHeaders($sign = null)
{
return [
'Content-Type' => "application/json;charset=utf-8",
// "Content-type:multipart/form-data",
// "Content-type:application/x-www-form-urlencoded",
"appId" => $this->appid,
"bizId" => $this->bizid,
"timestamps" => $this->time,
"sign" => $sign,
];
}
public function makeHeader($params)
{
$header = [];
foreach ($params as $k => $v) {
$header[] = $k . ': ' . $v;
}
return $header;
}
/**
* [getJson description]
* @Author shyn0121@qq.com
* @DateTime 2021-03-08
* @route [route]
* @param [string] $url [url]
* @param string $post_body [请求数据]
* @param array $headers [请求头]
* @param boolean $is_post [请求方式 post:true get:false]
* @param boolean $is_ssl [description]
* @return [array] [返回值]
*/
public function getJson($url, $post_body = '', $headers = [], $is_post = true, $is_ssl = false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $is_ssl);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, $is_post);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); //设置超时
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //$headers array
if ($post_body) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); //$post_body = json串
}
$output = curl_exec($ch);
$errorno = curl_errno($ch);
if (!$output || $errorno) {
return $errorno;
}
curl_close($ch);
// log_message($url . $output, 'log', LOG_PATH . 'authentication/');
trace('auth_zxsm:'.$url .' - '. $output, 'Authentication@getJson:result');
return json_decode($output, true);
}
public function getMillisecond()
{
list($msec, $sec) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
}
}
/**
* Class Aes 对称加密
* version : (PHP 7.1+)
*/
class Aes
{
/**
* php7.1++
* [encrypt 加密]
* @Author shyn0121@qq.com
* @DateTime 2021-03-05
* @route [route]
* @param string $data [加密数据] json串
* @param string $key [key]
* @param string $type [解密类型]
* @param [type] $options [description]
* @param string $tag [引用]
* @return [type] [description]
*/
public static function encrypt($data, $key, $cipher = 'aes-128-gcm', $options = OPENSSL_RAW_DATA, $tag = null)
{
$encrypt_data = false;
//必须将key 转成ascii
$key = hex2bin($key);
// $options = in_array($options, [OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING]) ? $options : OPENSSL_RAW_DATA;
if (in_array($cipher, openssl_get_cipher_methods())) {
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$encrypt_data = openssl_encrypt($data, $cipher, $key, $options, $iv, $tag);
$encrypt_data = base64_encode($iv . $encrypt_data . $tag);
}
return $encrypt_data;
//下面也可以
// $key = hex2bin($key);
// $ivlen = openssl_cipher_iv_length($cipher);
// $iv = openssl_random_pseudo_bytes($ivlen);
// $encrypt = openssl_encrypt($data, $cipher, $key, $options, $iv, $tag);
// $encrypt = bin2hex($iv) . bin2hex($encrypt) . bin2hex($tag);
// return base64_encode(hex2bin($encrypt));
}
/**
* [decrypt 解密]
* @Author shyn0121@qq.com
* @DateTime 2021-03-05
* @route [route]
* @param string $data [解密数据]
* @param string $key [key]
* @param string $type [解密类型]
* @param [type] $options [description]
* @param string $tag [引用]
* @return [type] [json]
*/
public static function decrypt($data, $key, $cipher = 'aes-128-gcm', $options = OPENSSL_RAW_DATA, $tag = "")
{
$r = base64_decode($data);
$ivlen = openssl_cipher_iv_length($cipher);
$key = hex2bin($key);
$iv = substr($r, 0, 12);
$tag = substr($r, -16);
$ciphertext = substr($r, $ivlen, -16);
$original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options, $iv, $tag);
if ($original_plaintext == false) {
$err = openssl_error_string();
return $err;
}
return $original_plaintext;
}
}
class AESGCM
{
/**
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return array
*/
public static function encrypt($K, $IV, $P = null, $A = null, $tag_length = 128)
{
/*
Assertion::string($K, 'The key encryption key must be a binary string.');
*/
$key_length = mb_strlen($K, '8bit') * 8;
/*
Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
Assertion::string($IV, 'The Initialization Vector must be a binary string.');
Assertion::nullOrString($P, 'The data to encrypt must be null or a binary string.');
Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
*/
if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $P) {
return self::encryptWithPHP71($K, $key_length, $IV, $P, $A, $tag_length);
} elseif (class_exists('\Crypto\Cipher')) {
return self::encryptWithCryptoExtension($K, $key_length, $IV, $P, $A, $tag_length);
}
return self::encryptWithPHP($K, $key_length, $IV, $P, $A, $tag_length);
}
/**
* This method will append the tag at the end of the ciphertext.
*
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return string
*/
public static function encryptAndAppendTag($K, $IV, $P = null, $A = null, $tag_length = 128)
{
return implode(self::encrypt($K, $IV, $P, $A, $tag_length));
}
/**
* @param string $K Key encryption key
* @param string $key_length Key length
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return array
*/
private static function encryptWithPHP71($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
{
$mode = 'aes-' . ($key_length) . '-gcm';
$T = null;
$C = openssl_encrypt($P, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, $A, $tag_length / 8);
// Assertion::true(false !== $C, 'Unable to encrypt the data.');
return [$C, $T];
}
/**
* @param string $K Key encryption key
* @param string $key_length Key length
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return array
*/
private static function encryptWithPHP($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
{
list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);
$C = self::getGCTR($K, $key_length, self::getInc(32, $J0), $P);
$u = self::calcVector($C);
$c_len_padding = self::addPadding($C);
$S = self::getHash($H, $A . str_pad('', $v / 8, "\0") . $C . str_pad('', $u / 8, "\0") . $a_len_padding . $c_len_padding);
$T = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
return [$C, $T];
}
/**
* @param string $K Key encryption key
* @param string $key_length Key length
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return array
*/
private static function encryptWithCryptoExtension($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
{
$cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
$cipher->setAAD($A);
$cipher->setTagLength($tag_length / 8);
$C = $cipher->encrypt($P, $K, $IV);
$T = $cipher->getTag();
return [$C, $T];
}
/**
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param string|null $C Data to encrypt (null for authentication)
* @param string|null $A Additional Authentication Data
* @param string $T Tag
*
* @return string
*/
public static function decrypt($K, $IV, $C, $A, $T)
{
/*
Assertion::string($K, 'The key encryption key must be a binary string.');
$key_length = mb_strlen($K, '8bit') * 8;
Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
Assertion::string($IV, 'The Initialization Vector must be a binary string.');
Assertion::nullOrString($C, 'The data to encrypt must be null or a binary string.');
Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
$tag_length = self::getLength($T);
Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
*/
if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $C) {
return self::decryptWithPHP71($K, $key_length, $IV, $C, $A, $T);
} elseif (class_exists('\Crypto\Cipher')) {
return self::decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length);
}
return self::decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length);
}
/**
* This method should be used if the tag is appended at the end of the ciphertext.
* It is used by some AES GCM implementations such as the Java one.
*
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param string|null $Ciphertext Data to encrypt (null for authentication)
* @param string|null $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return string
*
* @see self::encryptAndAppendTag
*/
public static function decryptWithAppendedTag($K, $IV, $Ciphertext = null, $A = null, $tag_length = 128)
{
$tag_length_in_bits = $tag_length / 8;
$C = mb_substr($Ciphertext, 0, -$tag_length_in_bits, '8bit');
$T = mb_substr($Ciphertext, -$tag_length_in_bits, null, '8bit');
return self::decrypt($K, $IV, $C, $A, $T);
}
/**
* @param string $K Key encryption key
* @param string $key_length Key length
* @param string $IV Initialization vector
* @param string|null $C Data to encrypt (null for authentication)
* @param string|null $A Additional Authentication Data
* @param string $T Tag
*
* @return string
*/
private static function decryptWithPHP71($K, $key_length, $IV, $C, $A, $T)
{
$mode = 'aes-' . ($key_length) . '-gcm';
$P = openssl_decrypt(null === $C ? '' : $C, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, null === $A ? '' : $A);
// Assertion::true(false !== $P, 'Unable to decrypt or to verify the tag.');
return $P;
}
/**
* @param string $K Key encryption key
* @param string $key_length Key length
* @param string $IV Initialization vector
* @param string|null $C Data to encrypt (null for authentication)
* @param string|null $A Additional Authentication Data
* @param string $T Tag
* @param int $tag_length Tag length
*
* @return string
*/
private static function decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
{
list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);
$P = self::getGCTR($K, $key_length, self::getInc(32, $J0), $C);
$u = self::calcVector($C);
$c_len_padding = self::addPadding($C);
$S = self::getHash($H, $A . str_pad('', $v / 8, "\0") . $C . str_pad('', $u / 8, "\0") . $a_len_padding . $c_len_padding);
$T1 = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
// Assertion::eq($T1, $T, 'Unable to decrypt or to verify the tag.');
return $P;
}
/**
* @param string $K Key encryption key
* @param string $key_length Key length
* @param string $IV Initialization vector
* @param string|null $C Data to encrypt (null for authentication)
* @param string|null $A Additional Authentication Data
* @param string $T Tag
* @param int $tag_length Tag length
*
* @return string
*/
private static function decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
{
$cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
$cipher->setTag($T);
$cipher->setAAD($A);
$cipher->setTagLength($tag_length / 8);
return $cipher->decrypt($C, $K, $IV);
}
/**
* @param $K
* @param $key_length
* @param $IV
* @param $A
*
* @return array
*/
private static function common($K, $key_length, $IV, $A)
{
$H = openssl_encrypt(str_repeat("\0", 16), 'aes-' . ($key_length), $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA); //---
$iv_len = self::getLength($IV);
if (96 === $iv_len) {
$J0 = $IV . pack('H*', '00000001');
} else {
$s = self::calcVector($IV);
// Assertion::eq(($s + 64) % 8, 0, 'Unable to decrypt or to verify the tag.');
$packed_iv_len = pack('N', $iv_len);
$iv_len_padding = str_pad($packed_iv_len, 8, "\0", STR_PAD_LEFT);
$hash_X = $IV . str_pad('', ($s + 64) / 8, "\0") . $iv_len_padding;
$J0 = self::getHash($H, $hash_X);
}
$v = self::calcVector($A);
$a_len_padding = self::addPadding($A);
return [$J0, $v, $a_len_padding, $H];
}
/**
* @param string $value
*
* @return int
*/
private static function calcVector($value)
{
return (128 * ceil(self::getLength($value) / 128)) - self::getLength($value);
}
/**
* @param string $value
*
* @return string
*/
private static function addPadding($value)
{
return str_pad(pack('N', self::getLength($value)), 8, "\0", STR_PAD_LEFT);
}
/**
* @param string $x
*
* @return int
*/
private static function getLength($x)
{
return mb_strlen($x, '8bit') * 8;
}
/**
* @param int $num_bits
* @param int $x
*
* @return string
*/
private static function getMSB($num_bits, $x)
{
$num_bytes = $num_bits / 8;
return mb_substr($x, 0, $num_bytes, '8bit');
}
/**
* @param int $num_bits
* @param int $x
*
* @return string
*/
private static function getLSB($num_bits, $x)
{
$num_bytes = ($num_bits / 8);
return mb_substr($x, -$num_bytes, null, '8bit');
}
/**
* @param int $s_bits
* @param int $x
*
* @return string
*/
private static function getInc($s_bits, $x)
{
$lsb = self::getLSB($s_bits, $x);
$X = self::toUInt32Bits($lsb) + 1;
$res = self::getMSB(self::getLength($x) - $s_bits, $x) . pack('N', $X);
return $res;
}
/**
* @param string $bin
*
* @return mixed
*/
private static function toUInt32Bits($bin)
{
list(, $h, $l) = unpack('n*', $bin);
return $l + ($h * 0x010000);
}
/**
* @param $X
* @param $Y
*
* @return string
*/
private static function getProduct($X, $Y)
{
$R = pack('H*', 'E1') . str_pad('', 15, "\0");
$Z = str_pad('', 16, "\0");
$V = $Y;
$parts = str_split($X, 4);
$x = sprintf('%032b%032b%032b%032b', self::toUInt32Bits($parts[0]), self::toUInt32Bits($parts[1]), self::toUInt32Bits($parts[2]), self::toUInt32Bits($parts[3]));
$lsb_mask = "\1";
for ($i = 0; $i < 128; $i++) {
if ($x[$i]) {
$Z = self::getBitXor($Z, $V);
}
$lsb_8 = mb_substr($V, -1, null, '8bit');
if (ord($lsb_8 & $lsb_mask)) {
$V = self::getBitXor(self::shiftStringToRight($V), $R);
} else {
$V = self::shiftStringToRight($V);
}
}
return $Z;
}
/**
* @param string $input
*
* @return string
*/
private static function shiftStringToRight($input)
{
$width = 4;
$parts = array_map('self::toUInt32Bits', str_split($input, $width));
$runs = count($parts);
for ($i = $runs - 1; $i >= 0; $i--) {
if ($i) {
$lsb1 = $parts[$i - 1] & 0x00000001;
if ($lsb1) {
$parts[$i] = ($parts[$i] >> 1) | 0x80000000;
$parts[$i] = pack('N', $parts[$i]);
continue;
}
}
$parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF;
$parts[$i] = pack('N', $parts[$i]);
}
$res = implode('', $parts);
return $res;
}
/**
* @param string $H
* @param string $X
*
* @return mixed
*/
private static function getHash($H, $X)
{
$Y = [];
$Y[0] = str_pad('', 16, "\0");
$num_blocks = (int)(mb_strlen($X, '8bit') / 16);
for ($i = 1; $i <= $num_blocks; $i++) {
$Y[$i] = self::getProduct(self::getBitXor($Y[$i - 1], mb_substr($X, ($i - 1) * 16, 16, '8bit')), $H);
}
return $Y[$num_blocks];
}
/**
* @param string $K
* @param int $key_length
* @param string $ICB
* @param string $X
*
* @return string
*/
private static function getGCTR($K, $key_length, $ICB, $X)
{
if (empty($X)) {
return '';
}
$n = (int)ceil(self::getLength($X) / 128);
$CB = [];
$Y = [];
$CB[1] = $ICB;
for ($i = 2; $i <= $n; $i++) {
$CB[$i] = self::getInc(32, $CB[$i - 1]);
}
$mode = 'aes-' . ($key_length) . '-ecb';
for ($i = 1; $i < $n; $i++) {
$C = openssl_encrypt($CB[$i], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
$Y[$i] = self::getBitXor(mb_substr($X, ($i - 1) * 16, 16, '8bit'), $C);
}
$Xn = mb_substr($X, ($n - 1) * 16, null, '8bit');
$C = openssl_encrypt($CB[$n], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
$Y[$n] = self::getBitXor($Xn, self::getMSB(self::getLength($Xn), $C));
return implode('', $Y);
}
/**
* @param string $o1
* @param string $o2
*
* @return string
*/
private static function getBitXor($o1, $o2)
{
$xorWidth = PHP_INT_SIZE;
$o1 = str_split($o1, $xorWidth);
$o2 = str_split($o2, $xorWidth);
$res = '';
$runs = count($o1);
for ($i = 0; $i < $runs; $i++) {
$res .= $o1[$i] ^ $o2[$i];
}
return $res;
}
}
?>