SecurityUtil.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. <?php
  2. include './SecretContext.php';
  3. include './MagicCrypt.php';
  4. class SecurityUtil
  5. {
  6. private $BASE64_ARRAY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  7. private $SEPARATOR_CHAR_MAP;
  8. function __construct()
  9. {
  10. if(!defined("PHONE_SEPARATOR_CHAR"))
  11. {
  12. define('PHONE_SEPARATOR_CHAR','$');
  13. }
  14. if(!defined("NICK_SEPARATOR_CHAR"))
  15. {
  16. define('NICK_SEPARATOR_CHAR','~');
  17. }
  18. if(!defined("NORMAL_SEPARATOR_CHAR"))
  19. {
  20. define('NORMAL_SEPARATOR_CHAR',chr(1));
  21. }
  22. $this->SEPARATOR_CHAR_MAP['nick'] = NICK_SEPARATOR_CHAR;
  23. $this->SEPARATOR_CHAR_MAP['simple'] = NICK_SEPARATOR_CHAR;
  24. $this->SEPARATOR_CHAR_MAP['receiver_name'] = NICK_SEPARATOR_CHAR;
  25. $this->SEPARATOR_CHAR_MAP['search'] = NICK_SEPARATOR_CHAR;
  26. $this->SEPARATOR_CHAR_MAP['normal'] = NORMAL_SEPARATOR_CHAR;
  27. $this->SEPARATOR_CHAR_MAP['phone'] = PHONE_SEPARATOR_CHAR;
  28. }
  29. /*
  30. * 判断是否是base64格式的数据
  31. */
  32. function isBase64Str($str)
  33. {
  34. $strLen = strlen($str);
  35. for($i = 0; $i < $strLen ; $i++)
  36. {
  37. if(!$this->isBase64Char($str[$i]))
  38. {
  39. return false;
  40. }
  41. }
  42. return true;
  43. }
  44. /*
  45. * 判断是否是base64格式的字符
  46. */
  47. function isBase64Char($char)
  48. {
  49. return strpos($this->BASE64_ARRAY,$char) !== false;
  50. }
  51. /*
  52. * 使用sep字符进行trim
  53. */
  54. function trimBySep($str,$sep)
  55. {
  56. $start = 0;
  57. $end = strlen($str);
  58. for($i = 0; $i < $end; $i++)
  59. {
  60. if($str[$i] == $sep)
  61. {
  62. $start = $i + 1;
  63. }
  64. else
  65. {
  66. break;
  67. }
  68. }
  69. for($i = $end -1 ; $i >= 0; $i--)
  70. {
  71. if($str[$i] == $sep)
  72. {
  73. $end = $i - 1;
  74. }
  75. else
  76. {
  77. break;
  78. }
  79. }
  80. return substr($str,$start,$end);
  81. }
  82. function checkEncryptData($dataArray)
  83. {
  84. if(count($dataArray) == 2){
  85. return $this->isBase64Str($dataArray[0]);
  86. }else{
  87. return $this->isBase64Str($dataArray[0]) && $this->isBase64Str($dataArray[1]);
  88. }
  89. }
  90. /*
  91. * 判断是否是加密数据
  92. */
  93. function isEncryptDataArray($array,$type)
  94. {
  95. foreach ($array as $value) {
  96. if(!$this->isEncryptData($value,$type)){
  97. return false;
  98. }
  99. }
  100. return true;
  101. }
  102. /**
  103. * 判断是否是已加密的数据,数据必须是同一个类型
  104. */
  105. function isPartEncryptData($array,$type)
  106. {
  107. $result = false;
  108. foreach ($array as $value) {
  109. if($this->isEncryptData($value,$type)){
  110. $result = true;
  111. break;
  112. }
  113. }
  114. return $result;
  115. }
  116. /*
  117. * 判断是否是加密数据
  118. */
  119. function isEncryptData($data,$type)
  120. {
  121. if(!is_string($data) || strlen($data) < 4)
  122. {
  123. return false;
  124. }
  125. $separator = $this->SEPARATOR_CHAR_MAP[$type];
  126. $strlen = strlen($data);
  127. if($data[0] != $separator || $data[$strlen -1] != $separator)
  128. {
  129. return false;
  130. }
  131. $dataArray = explode($separator,$this->trimBySep($data,$separator));
  132. $arrayLength = count($dataArray);
  133. if($separator == PHONE_SEPARATOR_CHAR)
  134. {
  135. if($arrayLength != 3)
  136. {
  137. return false;
  138. }
  139. if($data[$strlen - 2] == $separator)
  140. {
  141. return $this->checkEncryptData($dataArray);
  142. }
  143. else
  144. {
  145. $version = $dataArray[$arrayLength -1];
  146. if(is_numeric($version))
  147. {
  148. $base64Val = $dataArray[$arrayLength -2];
  149. return $this->isBase64Str($base64Val);
  150. }
  151. }
  152. }else{
  153. if($data[strlen($data) - 2] == $separator && $arrayLength == 3)
  154. {
  155. return $this->checkEncryptData($dataArray);
  156. }
  157. else if($arrayLength == 2)
  158. {
  159. return $this->checkEncryptData($dataArray);
  160. }
  161. else
  162. {
  163. return false;
  164. }
  165. }
  166. }
  167. function search($data, $type,$secretContext)
  168. {
  169. $separator = $this->SEPARATOR_CHAR_MAP[$type];
  170. if('phone' == $type) {
  171. if (strlen($data) != 4 ) {
  172. throw new Exception("phoneNumber error");
  173. }
  174. return $separator.$this->hmacMD5EncryptToBase64($data, $secretContext->secret).$separator;
  175. } else {
  176. $compressLen = $this->getArrayValue($secretContext->appConfig,'encrypt_index_compress_len',3);
  177. $slideSize = $this->getArrayValue($secretContext->appConfig,'encrypt_slide_size',4);
  178. $slideList = $this->getSlideWindows($data, $slideSize);
  179. $builder = '';
  180. foreach ($slideList as $slide) {
  181. $builder .= $this->hmacMD5EncryptToBase64($slide,$secretContext->secret,$compressLen);
  182. }
  183. return $builder;
  184. }
  185. }
  186. /*
  187. * 加密逻辑
  188. */
  189. function encrypt($data,$type,$version,$secretContext)
  190. {
  191. if(!is_string($data))
  192. {
  193. return false;
  194. }
  195. $separator = $this->SEPARATOR_CHAR_MAP[$type];
  196. $isIndexEncrypt = $this->isIndexEncrypt($type,$version,$secretContext);
  197. if($isIndexEncrypt || $type == "search"){
  198. if('phone' == $type) {
  199. return $this->encryptPhoneIndex($data,$separator,$secretContext);
  200. } else {
  201. $compressLen = $this->getArrayValue($secretContext->appConfig,'encrypt_index_compress_len',3);
  202. $slideSize = $this->getArrayValue($secretContext->appConfig,'encrypt_slide_size',4);
  203. return $this->encryptNormalIndex($data,$compressLen,$slideSize,$separator,$secretContext);
  204. }
  205. }else{
  206. if('phone' == $type) {
  207. return $this->encryptPhone($data,$separator,$secretContext);
  208. } else {
  209. return $this->encryptNormal($data,$separator,$secretContext);
  210. }
  211. }
  212. }
  213. /*
  214. * 加密逻辑,手机号码格式
  215. */
  216. function encryptPhone($data,$separator,$secretContext)
  217. {
  218. $len = strlen($data);
  219. if($len < 11)
  220. {
  221. return $data;
  222. }
  223. $prefixNumber = substr($data,0,$len -8);
  224. $last8Number = substr($data,$len -8,$len);
  225. return $separator.$prefixNumber.$separator.Security::encrypt($last8Number,$secretContext->secret)
  226. .$separator.$secretContext->secretVersion.$separator ;
  227. }
  228. /*
  229. * 加密逻辑,非手机号码格式
  230. */
  231. function encryptNormal($data,$separator,$secretContext)
  232. {
  233. return $separator.Security::encrypt($data,$secretContext->secret)
  234. .$separator.$secretContext->secretVersion.$separator;
  235. }
  236. /*
  237. * 解密逻辑
  238. */
  239. function decrypt($data,$type,$secretContext)
  240. {
  241. if(!$this->isEncryptData($data,$type))
  242. {
  243. throw new Exception("数据[".$data."]不是类型为[".$type."]的加密数据");
  244. }
  245. $dataLen = strlen($data);
  246. $separator = $this->SEPARATOR_CHAR_MAP[$type];
  247. $secretData = null;
  248. if($data[$dataLen - 2] == $separator){
  249. $secretData = $this->getIndexSecretData($data,$separator);
  250. }else{
  251. $secretData = $this->getSecretData($data,$separator);
  252. }
  253. if($secretData == null){
  254. return $data;
  255. }
  256. $result = Security::decrypt($secretData->originalBase64Value,$secretContext->secret);
  257. if($separator == PHONE_SEPARATOR_CHAR && !$secretData->search)
  258. {
  259. return $secretData->originalValue.$result;
  260. }
  261. return $result;
  262. }
  263. /*
  264. * 判断是否是公钥数据
  265. */
  266. function isPublicData($data,$type)
  267. {
  268. $secretData = $this->getSecretDataByType($data,$type);
  269. if(empty($secretData)){
  270. return false;
  271. }
  272. if(intval($secretData->secretVersion) < 0){
  273. return true;
  274. }
  275. return false;
  276. }
  277. function getSecretDataByType($data,$type)
  278. {
  279. $separator = $this->SEPARATOR_CHAR_MAP[$type];
  280. $dataLen = strlen($data);
  281. if($data[$dataLen - 2] == $separator){
  282. return $secretData = $this->getIndexSecretData($data,$separator);
  283. }else{
  284. return $secretData = $this->getSecretData($data,$separator);
  285. }
  286. }
  287. /*
  288. * 分解密文
  289. */
  290. function getSecretData($data,$separator)
  291. {
  292. $secretData = new SecretData;
  293. $dataArray = explode($separator,$this->trimBySep($data,$separator));
  294. $arrayLength = count($dataArray);
  295. if($separator == PHONE_SEPARATOR_CHAR)
  296. {
  297. if($arrayLength != 3){
  298. return null;
  299. }else{
  300. $version = $dataArray[2];
  301. if(is_numeric($version))
  302. {
  303. $secretData->originalValue = $dataArray[0];
  304. $secretData->originalBase64Value = $dataArray[1];
  305. $secretData->secretVersion = $version;
  306. }
  307. }
  308. }
  309. else
  310. {
  311. if($arrayLength != 2){
  312. return null;
  313. }else{
  314. $version = $dataArray[1];
  315. if(is_numeric($version))
  316. {
  317. $secretData->originalBase64Value = $dataArray[0];
  318. $secretData->secretVersion = $version;
  319. }
  320. }
  321. }
  322. return $secretData;
  323. }
  324. function getIndexSecretData($data,$separator) {
  325. $secretData = new SecretData;
  326. $dataArray = explode($separator,$this->trimBySep($data,$separator));
  327. $arrayLength = count($dataArray);
  328. if($separator == PHONE_SEPARATOR_CHAR) {
  329. if ($arrayLength != 3) {
  330. return null;
  331. }else{
  332. $version = $dataArray[2];
  333. if(is_numeric($version))
  334. {
  335. $secretData->originalValue = $dataArray[0];
  336. $secretData->originalBase64Value = $dataArray[1];
  337. $secretData->secretVersion = $version;
  338. }
  339. }
  340. } else {
  341. if($arrayLength != 3){
  342. return null;
  343. } else {
  344. $version = $dataArray[2];
  345. if(is_numeric($version))
  346. {
  347. $secretData->originalBase64Value = $dataArray[0];
  348. $secretData->originalValue = $dataArray[1];
  349. $secretData->secretVersion = $version;
  350. }
  351. }
  352. }
  353. $secretData->search = true;
  354. return $secretData;
  355. }
  356. /**
  357. * 判断密文是否支持检索
  358. *
  359. * @param key
  360. * @param version
  361. * @return
  362. */
  363. function isIndexEncrypt($key,$version,$secretContext)
  364. {
  365. if ($version != null && $version < 0) {
  366. $key = "previous_".$key;
  367. } else {
  368. $key = "current_".$key;
  369. }
  370. return $secretContext->appConfig != null &&
  371. array_key_exists($key,$secretContext->appConfig) &&
  372. $secretContext->appConfig[$key] == "2";
  373. }
  374. function isLetterOrDigit($ch)
  375. {
  376. $code = ord($ch);
  377. if (0 <= $code && $code <= 127) {
  378. return true;
  379. }
  380. return false;
  381. }
  382. function utf8_strlen($string = null) {
  383. // 将字符串分解为单元
  384. preg_match_all("/./us", $string, $match);
  385. // 返回单元个数
  386. return count($match[0]);
  387. }
  388. function utf8_substr($string,$start,$end) {
  389. // 将字符串分解为单元
  390. preg_match_all("/./us", $string, $match);
  391. // 返回单元个数
  392. $result = "";
  393. for($i = $start; $i < $end; $i++){
  394. $result .= $match[0][$i];
  395. }
  396. return $result;
  397. }
  398. function utf8_str_at($string,$index) {
  399. // 将字符串分解为单元
  400. preg_match_all("/./us", $string, $match);
  401. // 返回单元个数
  402. return $match[0][$index];
  403. }
  404. function compress($input,$toLength) {
  405. if($toLength < 0) {
  406. return null;
  407. }
  408. $output = array();
  409. for($i = 0; $i < $toLength; $i++) {
  410. $output[$i] = chr(0);
  411. }
  412. $input = $this->getBytes($input);
  413. $inputLength = count($input);
  414. for ($i = 0; $i < $inputLength; $i++) {
  415. $index_output = $i % $toLength;
  416. $output[$index_output] = $output[$index_output] ^ $input[$i];
  417. }
  418. return $output;
  419. }
  420. /**
  421. * @see #hmacMD5Encrypt
  422. *
  423. * @param encryptText
  424. * 被签名的字符串
  425. * @param encryptKey
  426. * 密钥
  427. * @param compressLen压缩长度
  428. * @return
  429. * @throws Exception
  430. */
  431. function hmacMD5EncryptToBase64($encryptText,$encryptKey,$compressLen = 0) {
  432. $encryptResult = Security::hmac_md5($encryptText,$encryptKey);
  433. if($compressLen != 0){
  434. $encryptResult = $this->compress($encryptResult,$compressLen);
  435. }
  436. return base64_encode($this->toStr($encryptResult));
  437. }
  438. /**
  439. * 生成滑动窗口
  440. *
  441. * @param input
  442. * @param slideSize
  443. * @return
  444. */
  445. function getSlideWindows($input,$slideSize = 4)
  446. {
  447. $endIndex = 0;
  448. $startIndex = 0;
  449. $currentWindowSize = 0;
  450. $currentWindow = null;
  451. $dataLength = $this->utf8_strlen($input);
  452. $windows = array();
  453. while($endIndex < $dataLength || $currentWindowSize > $slideSize)
  454. {
  455. $startsWithLetterOrDigit = false;
  456. if(!empty($currentWindow)){
  457. $startsWithLetterOrDigit = $this->isLetterOrDigit($this->utf8_str_at($currentWindow,0));
  458. }
  459. if($endIndex == $dataLength && $startsWithLetterOrDigit == false){
  460. break;
  461. }
  462. if($currentWindowSize == $slideSize &&
  463. $startsWithLetterOrDigit == false &&
  464. $this->isLetterOrDigit($this->utf8_str_at($input,$endIndex))) {
  465. $endIndex ++;
  466. $currentWindow = $this->utf8_substr($input,$startIndex,$endIndex);
  467. $currentWindowSize = 5;
  468. } else {
  469. if($endIndex != 0){
  470. if($startsWithLetterOrDigit){
  471. $currentWindowSize -= 1;
  472. }else{
  473. $currentWindowSize -= 2;
  474. }
  475. $startIndex ++;
  476. }
  477. while ($currentWindowSize < $slideSize && $endIndex < $dataLength) {
  478. $currentChar = $this->utf8_str_at($input,$endIndex);
  479. if ($this->isLetterOrDigit($currentChar)) {
  480. $currentWindowSize += 1;
  481. } else {
  482. $currentWindowSize += 2;
  483. }
  484. $endIndex++;
  485. }
  486. $currentWindow = $this->utf8_substr($input,$startIndex,$endIndex);
  487. }
  488. array_push($windows,$currentWindow);
  489. }
  490. return $windows;
  491. }
  492. function encryptPhoneIndex($data,$separator,$secretContext) {
  493. $dataLength = strlen($data);
  494. if($dataLength < 11) {
  495. return $data;
  496. }
  497. $last4Number = substr($data,$dataLength -4 ,$dataLength);
  498. return $separator.$this->hmacMD5EncryptToBase64($last4Number,$secretContext->secret).$separator
  499. .Security::encrypt($data,$secretContext->secret).$separator.$secretContext->secretVersion
  500. .$separator.$separator;
  501. }
  502. function encryptNormalIndex($data,$compressLen,$slideSize,$separator,$secretContext) {
  503. $slideList = $this->getSlideWindows($data, $slideSize);
  504. $builder = "";
  505. foreach ($slideList as $slide) {
  506. $builder .= $this->hmacMD5EncryptToBase64($slide,$secretContext->secret,$compressLen);
  507. }
  508. return $separator.Security::encrypt($data,$secretContext->secret).$separator.$builder.$separator
  509. .$secretContext->secretVersion.$separator.$separator;
  510. }
  511. function getArrayValue($array,$key,$default) {
  512. if(array_key_exists($key, $array)){
  513. return $array[$key];
  514. }
  515. return $default;
  516. }
  517. function getBytes($string) {
  518. $bytes = array();
  519. for($i = 0; $i < strlen($string); $i++){
  520. $bytes[] = ord($string[$i]);
  521. }
  522. return $bytes;
  523. }
  524. function toStr($bytes) {
  525. if(!is_array($bytes)){
  526. return $bytes;
  527. }
  528. $str = '';
  529. foreach($bytes as $ch) {
  530. $str .= chr($ch);
  531. }
  532. return $str;
  533. }
  534. }
  535. ?>