Authentication.class.php 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. <?php
  2. //aes-128/gcm php_version:php7.1++
  3. //yii2
  4. //注意事项
  5. use think\Env;
  6. class Authentication
  7. {
  8. protected $appid, $secret_key, $bizid;
  9. protected $check_url, $query_url, $loginout;
  10. //待加密数据
  11. protected $ai, $name, $id_num;
  12. protected $time;
  13. protected $body;
  14. protected $header;
  15. protected $sign;
  16. protected $isTest;
  17. /**
  18. * [initConfig 初始化配置]
  19. * @Author shyn0121@qq.com
  20. * @DateTime 2021-03-05
  21. * @route [route]
  22. * @return [type] [description]
  23. */
  24. public function initConfig()
  25. {
  26. //测试 配置
  27. // $this->appid = '38dc55300f454e28b6204226fbc3033d';
  28. // $this->secret_key = '3dd02385af49beb9f2227f619e76bdc4';
  29. // //测试替换url 测试码
  30. // $this->check_url = 'https://wlc.nppa.gov.cn/test/authentication/check';
  31. // $this->query_url = 'https://wlc.nppa.gov.cn/test/authentication/query';
  32. // $this->loginout = 'https://wlc.nppa.gov.cn/test/collection/loginout';
  33. // $this->isTest = true;
  34. $config = config('zxsm');
  35. $this->appid = $config['app_id'];
  36. $this->secret_key = $config['secret_key'];
  37. $this->check_url = 'https://api.wlc.nppa.gov.cn/idcard/authentication/check';
  38. $this->query_url = 'http://api2.wlc.nppa.gov.cn/idcard/authentication/query';
  39. $this->loginout = 'http://api2.wlc.nppa.gov.cn/behavior/collection/loginout';
  40. $this->isTest = false;
  41. //时间戳
  42. $this->time = $this->getMillisecond();
  43. }
  44. //入口在这里
  45. //直接运行这里
  46. //1.需要配置$this->check() 函数中的配置 query loginout 同上
  47. public function index()
  48. {
  49. //testcase01-实名认证接口
  50. //testcase02-实名认证接口
  51. //testcase03-实名认证接口
  52. $check_res = $this->check();
  53. echo '<pre>';
  54. var_dump($check_res);
  55. die();
  56. /*
  57. //testcase04-实名认证结果查询接口
  58. //testcase05-实名认证结果查询接口
  59. //testcase06-实名认证结果查询接口
  60. $query_res = $this->query();
  61. var_dump($query_res);die();
  62. //testcase07-游戏用户行为数据上报接口
  63. //testcase08-游戏用户行为数据上报接口
  64. $loginout_res = $this->loginout();
  65. var_dump($loginout_res);
  66. */
  67. }
  68. /**
  69. * [check 实名认证接口]
  70. * @Author shyn0121@qq.com
  71. * @DateTime 2021-03-05
  72. * @route [route]
  73. * @return [type] [description] ($url, $data, $headers, $is_post = true, $is_ssl = false)
  74. */
  75. public function check($bizid = '', $ai = '', $realname = '', $idcard = '')
  76. {
  77. $this->initConfig();
  78. if($this->isTest){
  79. $ai = '100000000000000001';
  80. }
  81. //测试数据替换数据 用预置数据测试 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
  82. //下面的数据测出为失败 一定要用预置数据哦 query 同理 loginout 也要注意配置
  83. if (!trim($bizid) || !trim($ai) || !trim($realname) || !trim($idcard)) {
  84. $retData = array();
  85. $retData['errcode'] = 9999;
  86. $retData['errmsg'] = '验证参数不完整';
  87. return $retData;
  88. }
  89. if (trim($bizid)) {
  90. $this->bizid = trim($bizid);
  91. }
  92. if (trim($ai)) {
  93. $this->ai = trim($ai);
  94. }
  95. if (trim($realname)) {
  96. $this->name = trim($realname);
  97. }
  98. if (trim($idcard)) {
  99. $this->id_num = trim($idcard);
  100. }
  101. /*
  102. if (empty($this->ai) || empty($this->name) || empty($this->id_num)) {
  103. die("body请对配置参数赋值");
  104. }
  105. */
  106. //业务参数
  107. $body_params = [
  108. 'ai' => $this->ai,
  109. 'name' => $this->name,
  110. 'idNum' => $this->id_num,
  111. ];
  112. log_message($body_params, 'log', LOG_PATH . 'authentication/');
  113. //string
  114. $this->body = $this->getBody($body_params);
  115. // echo $this->body."----body-----<br>";
  116. $this->sign = $this->getSign($this->body);
  117. $this->header = $this->makeHeader($this->getHeaders($this->sign));
  118. $return_data = $this->getJson($this->check_url, $this->body, $this->header, true);
  119. $errmsg = $return_data['errmsg'];
  120. if ($return_data['errcode'] > 1000 & $return_data['errcode'] < 2000) {
  121. $errmsg = '系统异常,请联系开发者!';
  122. curlDD("中宣实名认证接口异常:" . $return_data['errcode'] . ' - ' . $return_data['errmsg'], Env::get('dingtalk.warning_url'));
  123. }
  124. if ($return_data['errcode'] > 2000) {
  125. $errmsg = '实名认证业务异常!';
  126. if ($return_data['errcode'] == 2001) {
  127. $errmsg = '身份证号校验不通过!';
  128. } else if ($return_data['errcode'] == 2005) {
  129. $errmsg = '姓名合法性校验不通过!';
  130. } else {
  131. curlDD("中宣实名认证(check)接口失败:" . $return_data['errcode'] . ' - ' . $return_data['errmsg'], Env::get('dingtalk.notic_url'));
  132. }
  133. }
  134. $return_data['errmsg'] = $errmsg;
  135. return $return_data;
  136. }
  137. public function checkV2($bizid = '', $ai = '', $realname = '', $idcard = '')
  138. {
  139. $this->initConfig();
  140. if($this->isTest){
  141. $ai = '100000000000000001';
  142. }
  143. //下面的数据测出为失败 一定要用预置数据哦 query 同理 loginout 也要注意配置
  144. if (!trim($bizid) || !trim($ai) || !trim($realname) || !trim($idcard)) {
  145. return ['code' => -110, 'data' => [], 'msg' => '验证参数不完整'];
  146. }
  147. if (trim($bizid)) {
  148. $this->bizid = trim($bizid);
  149. }
  150. if (trim($ai)) {
  151. $this->ai = trim($ai);
  152. }
  153. if (trim($realname)) {
  154. $this->name = trim($realname);
  155. }
  156. if (trim($idcard)) {
  157. $this->id_num = trim($idcard);
  158. }
  159. //业务参数
  160. $body_params = [
  161. 'ai' => $this->ai,
  162. 'name' => $this->name,
  163. 'idNum' => $this->id_num,
  164. ];
  165. log_message($body_params, 'log', LOG_PATH . 'authentication/');
  166. $this->body = $this->getBody($body_params);
  167. $this->sign = $this->getSign($this->body);
  168. $this->header = $this->makeHeader($this->getHeaders($this->sign));
  169. $return_data = $this->getJson($this->check_url, $this->body, $this->header, true);
  170. /**
  171. * 返回成功示例:
  172. *
  173. *array(3) {
  174. * ["errcode"] => int(0)
  175. * ["errmsg"] => string(2) "OK"
  176. * ["data"] => array(1) {
  177. * ["result"] => array(2) {
  178. * ["status"] => int(0)
  179. * ["pi"] => string(38) "1hhi7599umwfpjhy9sdbk8qn3sids5jfw86ez2"
  180. * }
  181. * }
  182. * }
  183. */
  184. if($return_data['errcode'] === 0){
  185. return ['code' => 200, 'data' => $return_data['data']['result'], 'msg' => 'success'];
  186. }
  187. $errmsg = $return_data['errmsg'];
  188. if ($return_data['errcode'] > 1000 & $return_data['errcode'] < 2000) {
  189. $errmsg = '认证系统异常,请联系商务反馈!';
  190. curlDD("中宣实名认证接口异常:" . $return_data['errcode'] . '; ' . $return_data['errmsg']."; ai={$this->ai}", Env::get('dingtalk.warning_url'));
  191. }
  192. if ($return_data['errcode'] > 2000) {
  193. if ($return_data['errcode'] == 2001) {
  194. $errmsg = '认证失败,身份证号校验不通过!';
  195. } else if ($return_data['errcode'] == 2004) {
  196. $errmsg = '认证失败,认证提交频繁,请稍后!';
  197. } else if ($return_data['errcode'] == 2005) {
  198. $errmsg = '认证失败,姓名合法性校验不通过!';
  199. } else {
  200. $errmsg = '认证失败,实名认证业务异常!';
  201. curlDD("中宣实名认证(check)接口失败:" . $return_data['errcode'] . '; ' . $return_data['errmsg']."; ai={$this->ai}", Env::get('dingtalk.notic_url'));
  202. }
  203. }
  204. return ['code' => -100, 'data' => [], 'msg' => $errmsg];
  205. }
  206. /**
  207. * [query 实名认证结果查询接口]
  208. * @Author shyn0121@qq.com
  209. * @DateTime 2021-03-05
  210. * @route [route]
  211. * @return [type] [description]
  212. */
  213. public function query($bizid = '', $ai = '')
  214. {
  215. //需要用预置数据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
  216. /*
  217. $this->ai = '300000000000000005'; //游戏内部对应的唯一标识建议32位
  218. $this->bizid = "1101999999";
  219. */
  220. $this->initConfig();
  221. if($this->isTest){
  222. $ai = '100000000000000001';
  223. }
  224. if (!trim($bizid) || !trim($ai)) {
  225. $retData = array();
  226. $retData['errcode'] = 9999;
  227. $retData['errmsg'] = '查询参数不完整';
  228. return $retData;
  229. }
  230. if (trim($bizid)) {
  231. $this->bizid = trim($bizid);
  232. }
  233. if (trim($ai)) {
  234. $this->ai = trim($ai);
  235. }
  236. /*
  237. if (empty($this->ai)) {
  238. die("body请对配置参数赋值");
  239. }
  240. */
  241. //业务参数
  242. $body_params = [
  243. 'ai' => $this->ai,
  244. ];
  245. //string
  246. $this->sign = $this->getSign($body_params);
  247. $this->header = $this->makeHeader($this->getHeaders($this->sign));
  248. $return_data = $this->getJson($this->query_url . '?ai=' . $this->ai, '', $this->header, false);
  249. return $return_data;
  250. }
  251. /**
  252. * [loginout 游戏用户行为数据上报接口]
  253. * @param $bizid
  254. * @param $sid 一个会话标识只能对应唯一的实名用户,一个实名用户可以拥有多个会话标识;同一用户单次游戏会话中,上下线动作必须使用同一会话标识上报备注:会话标识仅标识一次用户会话,生命周期仅为一次上线和与之匹配的一次下线,不会对生命周期之外的任何业务有任何影响
  255. * @param $bt 游戏用户行为类 0:下线1:上线
  256. * @param $ct 用户行为数据上报类型 0:已认证通过用户2:游客用户
  257. * @param $di 游客模式设备标识,由游戏运营单位生成,游客用户下必填
  258. * @param $pi 已通过实名认证用户的唯一标识,已认证通过用户必填
  259. * @return array|int|mixed
  260. */
  261. public function loginout($bizid = '', $sid = '', $bt = 1, $ct = 0, $di = '', $pi = '')
  262. {
  263. //$this->bizid = "1101999999";
  264. if (!trim($bizid)) {
  265. $retData = array();
  266. $retData['errcode'] = 9999;
  267. $retData['errmsg'] = '查询参数不完整';
  268. return $retData;
  269. }
  270. if (trim($bizid)) {
  271. $this->bizid = trim($bizid);
  272. }
  273. $this->initConfig();
  274. /*
  275. $cdata = [
  276. //可行 游客
  277. 'no' => 1,
  278. 'si' => 'w7ligxjw355ftctm94yqt9dcew4zd723',
  279. 'bt' =>0,
  280. 'ot' => time(),
  281. 'ct' => 2,
  282. 'di'=>'uyiv6clpf7cu296pd4ppv11le820dhkw',
  283. 'pi' => '1fffbjzos82bs9cnyj1dna7d6d29zg4esnh99u',
  284. //可行 已认证
  285. 'no' => 1,
  286. 'si' => 'w7ligxjw355ftctm94yqt9dcew4zd723',
  287. 'bt' =>0,
  288. 'ot' => time(),
  289. 'ct' => 0,
  290. 'pi' => '1fffbjzos82bs9cnyj1dna7d6d29zg4esnh99u',
  291. ];
  292. */
  293. $cdata = array();
  294. $cdata['no'] = 1;
  295. $cdata['si'] = md5($sid);
  296. $cdata['bt'] = $bt;
  297. $cdata['ot'] = time() - 10;
  298. if ($ct == 0) {
  299. $cdata['ct'] = $ct;
  300. $cdata['pi'] = trim($pi);
  301. } else {
  302. $cdata['ct'] = $ct;
  303. $cdata['di'] = trim($di);
  304. $cdata['pi'] = trim($pi);
  305. }
  306. // var_dump($cdata);
  307. //业务参数
  308. $body_params = [
  309. 'collections' => [$cdata],
  310. ];
  311. //string
  312. $this->body = $this->getBody($body_params);
  313. $this->sign = $this->getSign($this->body);
  314. $this->header = $this->makeHeader($this->getHeaders($this->sign));
  315. $return_data = $this->getJson($this->loginout, $this->body, $this->header, true);
  316. // var_dump($return_data);
  317. return $return_data;
  318. }
  319. /**
  320. * [接口调用签名]
  321. * @Author shyn0121@qq.com
  322. * @DateTime 2021-03-05
  323. * @data [请求体 json]
  324. * @return [type] [description]
  325. */
  326. public function getSign($rawBody)
  327. {
  328. $data = $this->getHeaders();
  329. if (is_array($rawBody)) {
  330. $data += $rawBody;
  331. }
  332. ksort($data);
  333. $source = [$this->secret_key];
  334. foreach ($data as $key => $value) {
  335. if ($key !== 'sign' && $key != 'Content-Type') {
  336. $source[] = "{$key}{$value}";
  337. }
  338. }
  339. if (!is_array($rawBody)) {
  340. $source[] = $rawBody;
  341. }
  342. $presign = implode("", $source);
  343. return hash("sha256", $presign);
  344. }
  345. //报文体
  346. public function getBody($data)
  347. {
  348. // $string = Aes::encrypt(json_encode($data), $this->secret_key);
  349. $string = $this->aesGcmEncrypt($data);
  350. return json_encode(['data' => $string]);
  351. }
  352. public function aesGcmEncrypt($string)
  353. {
  354. $cipher = strtolower('AES-128-GCM');
  355. if (is_array($string)) $string = json_encode($string);
  356. //二进制key
  357. $skey = hex2bin($this->secret_key);
  358. //二进制iv
  359. $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
  360. list($content, $tag) = AESGCM::encrypt($skey, $iv, $string);
  361. //如果环境是php7.1+,直接使用下面的方式
  362. // $tag = NULL;
  363. // $content = openssl_encrypt($string, $cipher, $skey,OPENSSL_RAW_DATA,$iv,$tag);
  364. $str = bin2hex($iv) . bin2hex($content) . bin2hex($tag);
  365. return base64_encode(hex2bin($str));
  366. }
  367. public function getHeaders($sign = null)
  368. {
  369. return [
  370. 'Content-Type' => "application/json;charset=utf-8",
  371. // "Content-type:multipart/form-data",
  372. // "Content-type:application/x-www-form-urlencoded",
  373. "appId" => $this->appid,
  374. "bizId" => $this->bizid,
  375. "timestamps" => $this->time,
  376. "sign" => $sign,
  377. ];
  378. }
  379. public function makeHeader($params)
  380. {
  381. $header = [];
  382. foreach ($params as $k => $v) {
  383. $header[] = $k . ': ' . $v;
  384. }
  385. return $header;
  386. }
  387. /**
  388. * [getJson description]
  389. * @Author shyn0121@qq.com
  390. * @DateTime 2021-03-08
  391. * @route [route]
  392. * @param [string] $url [url]
  393. * @param string $post_body [请求数据]
  394. * @param array $headers [请求头]
  395. * @param boolean $is_post [请求方式 post:true get:false]
  396. * @param boolean $is_ssl [description]
  397. * @return [array] [返回值]
  398. */
  399. public function getJson($url, $post_body = '', $headers = [], $is_post = true, $is_ssl = false)
  400. {
  401. $ch = curl_init();
  402. curl_setopt($ch, CURLOPT_URL, $url);
  403. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $is_ssl);
  404. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
  405. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  406. curl_setopt($ch, CURLOPT_POST, $is_post);
  407. curl_setopt($ch, CURLOPT_TIMEOUT, 5); //设置超时
  408. curl_setopt($ch, CURLOPT_HEADER, 0);
  409. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //$headers array
  410. if ($post_body) {
  411. curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); //$post_body = json串
  412. }
  413. $output = curl_exec($ch);
  414. $errorno = curl_errno($ch);
  415. if (!$output || $errorno) {
  416. return $errorno;
  417. }
  418. curl_close($ch);
  419. // log_message($url . $output, 'log', LOG_PATH . 'authentication/');
  420. trace('auth_zxsm:'.$url .' - '. $output, 'Authentication@getJson:result');
  421. return json_decode($output, true);
  422. }
  423. public function getMillisecond()
  424. {
  425. list($msec, $sec) = explode(' ', microtime());
  426. return (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
  427. }
  428. }
  429. /**
  430. * Class Aes 对称加密
  431. * version : (PHP 7.1+)
  432. */
  433. class Aes
  434. {
  435. /**
  436. * php7.1++
  437. * [encrypt 加密]
  438. * @Author shyn0121@qq.com
  439. * @DateTime 2021-03-05
  440. * @route [route]
  441. * @param string $data [加密数据] json串
  442. * @param string $key [key]
  443. * @param string $type [解密类型]
  444. * @param [type] $options [description]
  445. * @param string $tag [引用]
  446. * @return [type] [description]
  447. */
  448. public static function encrypt($data, $key, $cipher = 'aes-128-gcm', $options = OPENSSL_RAW_DATA, $tag = null)
  449. {
  450. $encrypt_data = false;
  451. //必须将key 转成ascii
  452. $key = hex2bin($key);
  453. // $options = in_array($options, [OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING]) ? $options : OPENSSL_RAW_DATA;
  454. if (in_array($cipher, openssl_get_cipher_methods())) {
  455. $ivlen = openssl_cipher_iv_length($cipher);
  456. $iv = openssl_random_pseudo_bytes($ivlen);
  457. $encrypt_data = openssl_encrypt($data, $cipher, $key, $options, $iv, $tag);
  458. $encrypt_data = base64_encode($iv . $encrypt_data . $tag);
  459. }
  460. return $encrypt_data;
  461. //下面也可以
  462. // $key = hex2bin($key);
  463. // $ivlen = openssl_cipher_iv_length($cipher);
  464. // $iv = openssl_random_pseudo_bytes($ivlen);
  465. // $encrypt = openssl_encrypt($data, $cipher, $key, $options, $iv, $tag);
  466. // $encrypt = bin2hex($iv) . bin2hex($encrypt) . bin2hex($tag);
  467. // return base64_encode(hex2bin($encrypt));
  468. }
  469. /**
  470. * [decrypt 解密]
  471. * @Author shyn0121@qq.com
  472. * @DateTime 2021-03-05
  473. * @route [route]
  474. * @param string $data [解密数据]
  475. * @param string $key [key]
  476. * @param string $type [解密类型]
  477. * @param [type] $options [description]
  478. * @param string $tag [引用]
  479. * @return [type] [json]
  480. */
  481. public static function decrypt($data, $key, $cipher = 'aes-128-gcm', $options = OPENSSL_RAW_DATA, $tag = "")
  482. {
  483. $r = base64_decode($data);
  484. $ivlen = openssl_cipher_iv_length($cipher);
  485. $key = hex2bin($key);
  486. $iv = substr($r, 0, 12);
  487. $tag = substr($r, -16);
  488. $ciphertext = substr($r, $ivlen, -16);
  489. $original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options, $iv, $tag);
  490. if ($original_plaintext == false) {
  491. $err = openssl_error_string();
  492. return $err;
  493. }
  494. return $original_plaintext;
  495. }
  496. }
  497. class AESGCM
  498. {
  499. /**
  500. * @param string $K Key encryption key
  501. * @param string $IV Initialization vector
  502. * @param null|string $P Data to encrypt (null for authentication)
  503. * @param null|string $A Additional Authentication Data
  504. * @param int $tag_length Tag length
  505. *
  506. * @return array
  507. */
  508. public static function encrypt($K, $IV, $P = null, $A = null, $tag_length = 128)
  509. {
  510. /*
  511. Assertion::string($K, 'The key encryption key must be a binary string.');
  512. */
  513. $key_length = mb_strlen($K, '8bit') * 8;
  514. /*
  515. Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
  516. Assertion::string($IV, 'The Initialization Vector must be a binary string.');
  517. Assertion::nullOrString($P, 'The data to encrypt must be null or a binary string.');
  518. Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
  519. Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
  520. Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
  521. */
  522. if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $P) {
  523. return self::encryptWithPHP71($K, $key_length, $IV, $P, $A, $tag_length);
  524. } elseif (class_exists('\Crypto\Cipher')) {
  525. return self::encryptWithCryptoExtension($K, $key_length, $IV, $P, $A, $tag_length);
  526. }
  527. return self::encryptWithPHP($K, $key_length, $IV, $P, $A, $tag_length);
  528. }
  529. /**
  530. * This method will append the tag at the end of the ciphertext.
  531. *
  532. * @param string $K Key encryption key
  533. * @param string $IV Initialization vector
  534. * @param null|string $P Data to encrypt (null for authentication)
  535. * @param null|string $A Additional Authentication Data
  536. * @param int $tag_length Tag length
  537. *
  538. * @return string
  539. */
  540. public static function encryptAndAppendTag($K, $IV, $P = null, $A = null, $tag_length = 128)
  541. {
  542. return implode(self::encrypt($K, $IV, $P, $A, $tag_length));
  543. }
  544. /**
  545. * @param string $K Key encryption key
  546. * @param string $key_length Key length
  547. * @param string $IV Initialization vector
  548. * @param null|string $P Data to encrypt (null for authentication)
  549. * @param null|string $A Additional Authentication Data
  550. * @param int $tag_length Tag length
  551. *
  552. * @return array
  553. */
  554. private static function encryptWithPHP71($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
  555. {
  556. $mode = 'aes-' . ($key_length) . '-gcm';
  557. $T = null;
  558. $C = openssl_encrypt($P, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, $A, $tag_length / 8);
  559. // Assertion::true(false !== $C, 'Unable to encrypt the data.');
  560. return [$C, $T];
  561. }
  562. /**
  563. * @param string $K Key encryption key
  564. * @param string $key_length Key length
  565. * @param string $IV Initialization vector
  566. * @param null|string $P Data to encrypt (null for authentication)
  567. * @param null|string $A Additional Authentication Data
  568. * @param int $tag_length Tag length
  569. *
  570. * @return array
  571. */
  572. private static function encryptWithPHP($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
  573. {
  574. list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);
  575. $C = self::getGCTR($K, $key_length, self::getInc(32, $J0), $P);
  576. $u = self::calcVector($C);
  577. $c_len_padding = self::addPadding($C);
  578. $S = self::getHash($H, $A . str_pad('', $v / 8, "\0") . $C . str_pad('', $u / 8, "\0") . $a_len_padding . $c_len_padding);
  579. $T = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
  580. return [$C, $T];
  581. }
  582. /**
  583. * @param string $K Key encryption key
  584. * @param string $key_length Key length
  585. * @param string $IV Initialization vector
  586. * @param null|string $P Data to encrypt (null for authentication)
  587. * @param null|string $A Additional Authentication Data
  588. * @param int $tag_length Tag length
  589. *
  590. * @return array
  591. */
  592. private static function encryptWithCryptoExtension($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
  593. {
  594. $cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
  595. $cipher->setAAD($A);
  596. $cipher->setTagLength($tag_length / 8);
  597. $C = $cipher->encrypt($P, $K, $IV);
  598. $T = $cipher->getTag();
  599. return [$C, $T];
  600. }
  601. /**
  602. * @param string $K Key encryption key
  603. * @param string $IV Initialization vector
  604. * @param string|null $C Data to encrypt (null for authentication)
  605. * @param string|null $A Additional Authentication Data
  606. * @param string $T Tag
  607. *
  608. * @return string
  609. */
  610. public static function decrypt($K, $IV, $C, $A, $T)
  611. {
  612. /*
  613. Assertion::string($K, 'The key encryption key must be a binary string.');
  614. $key_length = mb_strlen($K, '8bit') * 8;
  615. Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
  616. Assertion::string($IV, 'The Initialization Vector must be a binary string.');
  617. Assertion::nullOrString($C, 'The data to encrypt must be null or a binary string.');
  618. Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
  619. $tag_length = self::getLength($T);
  620. Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
  621. Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
  622. */
  623. if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $C) {
  624. return self::decryptWithPHP71($K, $key_length, $IV, $C, $A, $T);
  625. } elseif (class_exists('\Crypto\Cipher')) {
  626. return self::decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length);
  627. }
  628. return self::decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length);
  629. }
  630. /**
  631. * This method should be used if the tag is appended at the end of the ciphertext.
  632. * It is used by some AES GCM implementations such as the Java one.
  633. *
  634. * @param string $K Key encryption key
  635. * @param string $IV Initialization vector
  636. * @param string|null $Ciphertext Data to encrypt (null for authentication)
  637. * @param string|null $A Additional Authentication Data
  638. * @param int $tag_length Tag length
  639. *
  640. * @return string
  641. *
  642. * @see self::encryptAndAppendTag
  643. */
  644. public static function decryptWithAppendedTag($K, $IV, $Ciphertext = null, $A = null, $tag_length = 128)
  645. {
  646. $tag_length_in_bits = $tag_length / 8;
  647. $C = mb_substr($Ciphertext, 0, -$tag_length_in_bits, '8bit');
  648. $T = mb_substr($Ciphertext, -$tag_length_in_bits, null, '8bit');
  649. return self::decrypt($K, $IV, $C, $A, $T);
  650. }
  651. /**
  652. * @param string $K Key encryption key
  653. * @param string $key_length Key length
  654. * @param string $IV Initialization vector
  655. * @param string|null $C Data to encrypt (null for authentication)
  656. * @param string|null $A Additional Authentication Data
  657. * @param string $T Tag
  658. *
  659. * @return string
  660. */
  661. private static function decryptWithPHP71($K, $key_length, $IV, $C, $A, $T)
  662. {
  663. $mode = 'aes-' . ($key_length) . '-gcm';
  664. $P = openssl_decrypt(null === $C ? '' : $C, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, null === $A ? '' : $A);
  665. // Assertion::true(false !== $P, 'Unable to decrypt or to verify the tag.');
  666. return $P;
  667. }
  668. /**
  669. * @param string $K Key encryption key
  670. * @param string $key_length Key length
  671. * @param string $IV Initialization vector
  672. * @param string|null $C Data to encrypt (null for authentication)
  673. * @param string|null $A Additional Authentication Data
  674. * @param string $T Tag
  675. * @param int $tag_length Tag length
  676. *
  677. * @return string
  678. */
  679. private static function decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
  680. {
  681. list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);
  682. $P = self::getGCTR($K, $key_length, self::getInc(32, $J0), $C);
  683. $u = self::calcVector($C);
  684. $c_len_padding = self::addPadding($C);
  685. $S = self::getHash($H, $A . str_pad('', $v / 8, "\0") . $C . str_pad('', $u / 8, "\0") . $a_len_padding . $c_len_padding);
  686. $T1 = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
  687. // Assertion::eq($T1, $T, 'Unable to decrypt or to verify the tag.');
  688. return $P;
  689. }
  690. /**
  691. * @param string $K Key encryption key
  692. * @param string $key_length Key length
  693. * @param string $IV Initialization vector
  694. * @param string|null $C Data to encrypt (null for authentication)
  695. * @param string|null $A Additional Authentication Data
  696. * @param string $T Tag
  697. * @param int $tag_length Tag length
  698. *
  699. * @return string
  700. */
  701. private static function decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
  702. {
  703. $cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
  704. $cipher->setTag($T);
  705. $cipher->setAAD($A);
  706. $cipher->setTagLength($tag_length / 8);
  707. return $cipher->decrypt($C, $K, $IV);
  708. }
  709. /**
  710. * @param $K
  711. * @param $key_length
  712. * @param $IV
  713. * @param $A
  714. *
  715. * @return array
  716. */
  717. private static function common($K, $key_length, $IV, $A)
  718. {
  719. $H = openssl_encrypt(str_repeat("\0", 16), 'aes-' . ($key_length), $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA); //---
  720. $iv_len = self::getLength($IV);
  721. if (96 === $iv_len) {
  722. $J0 = $IV . pack('H*', '00000001');
  723. } else {
  724. $s = self::calcVector($IV);
  725. // Assertion::eq(($s + 64) % 8, 0, 'Unable to decrypt or to verify the tag.');
  726. $packed_iv_len = pack('N', $iv_len);
  727. $iv_len_padding = str_pad($packed_iv_len, 8, "\0", STR_PAD_LEFT);
  728. $hash_X = $IV . str_pad('', ($s + 64) / 8, "\0") . $iv_len_padding;
  729. $J0 = self::getHash($H, $hash_X);
  730. }
  731. $v = self::calcVector($A);
  732. $a_len_padding = self::addPadding($A);
  733. return [$J0, $v, $a_len_padding, $H];
  734. }
  735. /**
  736. * @param string $value
  737. *
  738. * @return int
  739. */
  740. private static function calcVector($value)
  741. {
  742. return (128 * ceil(self::getLength($value) / 128)) - self::getLength($value);
  743. }
  744. /**
  745. * @param string $value
  746. *
  747. * @return string
  748. */
  749. private static function addPadding($value)
  750. {
  751. return str_pad(pack('N', self::getLength($value)), 8, "\0", STR_PAD_LEFT);
  752. }
  753. /**
  754. * @param string $x
  755. *
  756. * @return int
  757. */
  758. private static function getLength($x)
  759. {
  760. return mb_strlen($x, '8bit') * 8;
  761. }
  762. /**
  763. * @param int $num_bits
  764. * @param int $x
  765. *
  766. * @return string
  767. */
  768. private static function getMSB($num_bits, $x)
  769. {
  770. $num_bytes = $num_bits / 8;
  771. return mb_substr($x, 0, $num_bytes, '8bit');
  772. }
  773. /**
  774. * @param int $num_bits
  775. * @param int $x
  776. *
  777. * @return string
  778. */
  779. private static function getLSB($num_bits, $x)
  780. {
  781. $num_bytes = ($num_bits / 8);
  782. return mb_substr($x, -$num_bytes, null, '8bit');
  783. }
  784. /**
  785. * @param int $s_bits
  786. * @param int $x
  787. *
  788. * @return string
  789. */
  790. private static function getInc($s_bits, $x)
  791. {
  792. $lsb = self::getLSB($s_bits, $x);
  793. $X = self::toUInt32Bits($lsb) + 1;
  794. $res = self::getMSB(self::getLength($x) - $s_bits, $x) . pack('N', $X);
  795. return $res;
  796. }
  797. /**
  798. * @param string $bin
  799. *
  800. * @return mixed
  801. */
  802. private static function toUInt32Bits($bin)
  803. {
  804. list(, $h, $l) = unpack('n*', $bin);
  805. return $l + ($h * 0x010000);
  806. }
  807. /**
  808. * @param $X
  809. * @param $Y
  810. *
  811. * @return string
  812. */
  813. private static function getProduct($X, $Y)
  814. {
  815. $R = pack('H*', 'E1') . str_pad('', 15, "\0");
  816. $Z = str_pad('', 16, "\0");
  817. $V = $Y;
  818. $parts = str_split($X, 4);
  819. $x = sprintf('%032b%032b%032b%032b', self::toUInt32Bits($parts[0]), self::toUInt32Bits($parts[1]), self::toUInt32Bits($parts[2]), self::toUInt32Bits($parts[3]));
  820. $lsb_mask = "\1";
  821. for ($i = 0; $i < 128; $i++) {
  822. if ($x[$i]) {
  823. $Z = self::getBitXor($Z, $V);
  824. }
  825. $lsb_8 = mb_substr($V, -1, null, '8bit');
  826. if (ord($lsb_8 & $lsb_mask)) {
  827. $V = self::getBitXor(self::shiftStringToRight($V), $R);
  828. } else {
  829. $V = self::shiftStringToRight($V);
  830. }
  831. }
  832. return $Z;
  833. }
  834. /**
  835. * @param string $input
  836. *
  837. * @return string
  838. */
  839. private static function shiftStringToRight($input)
  840. {
  841. $width = 4;
  842. $parts = array_map('self::toUInt32Bits', str_split($input, $width));
  843. $runs = count($parts);
  844. for ($i = $runs - 1; $i >= 0; $i--) {
  845. if ($i) {
  846. $lsb1 = $parts[$i - 1] & 0x00000001;
  847. if ($lsb1) {
  848. $parts[$i] = ($parts[$i] >> 1) | 0x80000000;
  849. $parts[$i] = pack('N', $parts[$i]);
  850. continue;
  851. }
  852. }
  853. $parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF;
  854. $parts[$i] = pack('N', $parts[$i]);
  855. }
  856. $res = implode('', $parts);
  857. return $res;
  858. }
  859. /**
  860. * @param string $H
  861. * @param string $X
  862. *
  863. * @return mixed
  864. */
  865. private static function getHash($H, $X)
  866. {
  867. $Y = [];
  868. $Y[0] = str_pad('', 16, "\0");
  869. $num_blocks = (int)(mb_strlen($X, '8bit') / 16);
  870. for ($i = 1; $i <= $num_blocks; $i++) {
  871. $Y[$i] = self::getProduct(self::getBitXor($Y[$i - 1], mb_substr($X, ($i - 1) * 16, 16, '8bit')), $H);
  872. }
  873. return $Y[$num_blocks];
  874. }
  875. /**
  876. * @param string $K
  877. * @param int $key_length
  878. * @param string $ICB
  879. * @param string $X
  880. *
  881. * @return string
  882. */
  883. private static function getGCTR($K, $key_length, $ICB, $X)
  884. {
  885. if (empty($X)) {
  886. return '';
  887. }
  888. $n = (int)ceil(self::getLength($X) / 128);
  889. $CB = [];
  890. $Y = [];
  891. $CB[1] = $ICB;
  892. for ($i = 2; $i <= $n; $i++) {
  893. $CB[$i] = self::getInc(32, $CB[$i - 1]);
  894. }
  895. $mode = 'aes-' . ($key_length) . '-ecb';
  896. for ($i = 1; $i < $n; $i++) {
  897. $C = openssl_encrypt($CB[$i], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
  898. $Y[$i] = self::getBitXor(mb_substr($X, ($i - 1) * 16, 16, '8bit'), $C);
  899. }
  900. $Xn = mb_substr($X, ($n - 1) * 16, null, '8bit');
  901. $C = openssl_encrypt($CB[$n], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
  902. $Y[$n] = self::getBitXor($Xn, self::getMSB(self::getLength($Xn), $C));
  903. return implode('', $Y);
  904. }
  905. /**
  906. * @param string $o1
  907. * @param string $o2
  908. *
  909. * @return string
  910. */
  911. private static function getBitXor($o1, $o2)
  912. {
  913. $xorWidth = PHP_INT_SIZE;
  914. $o1 = str_split($o1, $xorWidth);
  915. $o2 = str_split($o2, $xorWidth);
  916. $res = '';
  917. $runs = count($o1);
  918. for ($i = 0; $i < $runs; $i++) {
  919. $res .= $o1[$i] ^ $o2[$i];
  920. }
  921. return $res;
  922. }
  923. }
  924. ?>