WBAes.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. namespace utils;
  3. /**
  4. * 按照加密方式可分为对称和非对称加密,对称加密即发送方和接收方都是用相同的秘钥进行加解密,非对称加密则使用一对公私钥来进行加解密,发送方使用公钥加密数据,接收方可使用私钥来解密。
  5. * 对称加密:
  6. * 简单的加密设计: 用密钥对原文做字节代替、行移动、列混淆、加轮秘钥
  7. * 优点: 安全、快速(加解密运算速度快、资源消耗少、消耗时间少)、支持二进制
  8. * 缺点: 发送方和接收方协定秘钥,双方保存好秘钥安全不被泄漏,加重了心智负担
  9. * 常见的对称加密方式有 DES、3DES、AES、Blowfish、IDEA、RC5、RC6, 从安全性、资源消耗、运算速度、消耗时间综合来看 AES 都是值得选择的对称加密方式
  10. *
  11. * AES(Advanced Encryption Standard)
  12. * 1.分块:将明文按照一定长度分块(block0、block1、block2、blockN....),根据分块长度可以分为:AES-128、AES-192、AES-256 三种,对应分组长度为 128bit、192bit、256bit
  13. * 2.分块加密组合:将分块明文进行加密组合,分为四种模式: ECB(Electronic Code Book电子密码本)模式、CBC(Cipher Block Chaining,加密块链)模式、CFB(Cipher FeedBack Mode,加密反馈)模式、OFB(Output FeedBack,输出反馈)模式
  14. *
  15. * ECB(Electronic Code Book电子密码本)模式,ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
  16. * 优点: 1.简单; 2.有利于并行计算; 3.误差不会被扩散;
  17. * 缺点: 1.不能隐藏明文的模式; 2.可能对明文进行主动攻击;
  18. *
  19. * CBC(Cipher Block Chaining,加密块链)模式
  20. * 优点: 不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
  21. * 缺点: 1.不利于并行计算; 2.误差传递; 3.需要初始化向量IV
  22. *
  23. * CFB(Cipher FeedBack Mode,加密反馈)模式
  24. * 优点:1.隐藏了明文模式; 2.分组密码转化为流模式; 3.可以及时加密传送小于分组的数据;
  25. * 缺点: 1.不利于并行计算; 2.误差传送:一个明文单元损坏影响多个单元; 3.唯一的IV;
  26. *
  27. * OFB(Output FeedBack,输出反馈)模式
  28. * 优点: 1.隐藏了明文模式; 2.分组密码转化为流模式; 3.可以及时加密传送小于分组的数据;
  29. * 缺点: 1.不利于并行计算; 2.对明文的主动攻击是可能的; 3.误差传送:一个明文单元损坏影响多个单元;
  30. *
  31. * 涉及到2个小知识:
  32. * 填充方式(原始数据长度不是分组大小的整数倍,则需要对数据进行填充),以AES-128为例:
  33. * NoPadding:
  34. * 不做任何填充,但是要求明文必须是16字节的整数倍。
  35. * ZeroPadding:
  36. * 数据长度不对齐时使用0填充,否则不填充
  37. * PKCS5Padding:
  38. * 如果明文长度为10bytes, 进行分组时候则需要 6bytes 才满分组条件,在明文块末尾补足相应数量的字符
  39. * 比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个数量字符,则补全为{1,2,3,4,5,a,b,c,d,e,6,6,6,6,6,6}
  40. * ISO10126Padding:
  41. * 如果明文长度为10bytes, 进行分组时候则需要 6bytes 才满分组条件,和 PKCS5Padding 不同的是在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数。
  42. * 比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则可能补全为{1,2,3,4,5,a,b,c,d,e,x,c,7,G,e,6}
  43. * 其中:x,c,7,G,e 随机字符, 最后一个 6 则为缺省的字符数
  44. * ...............:
  45. * IV 初始向量:
  46. * 其中CBC、CFB、OFB 三种加密分组模式需要初始向量 IV 来辅助加密,但是增加了复杂度,它的作用和MD5的“加盐”有些类似,目的是防止同样的明文块始终加密成同样的密文块
  47. * Class AesTool
  48. * @package App\Utils
  49. */
  50. class WBAes
  51. {
  52. /**
  53. * @var string 秘钥
  54. * AES-128-CBC key 长度 16 位,IV 16位
  55. * AES-192-CBC key 长度 24 位,IV 16位
  56. * AES-256-CBC key 长度 32 位,IV 16位
  57. */
  58. protected $securityKey;
  59. /**
  60. * @var string 加密方式 https://www.php.net/manual/zh/function.openssl-get-cipher-methods.php
  61. */
  62. protected $method;
  63. /**
  64. * @var string 偏移量
  65. */
  66. protected $iv;
  67. /**
  68. * Aes constructor.
  69. * @param string $securityKey
  70. * @param string $method
  71. * @param string $iv
  72. */
  73. public function __construct($securityKey,$method = 'AES-128-CBC',$iv = ''){
  74. if (empty($securityKey)) {
  75. throw new \RuntimeException('秘钥不能为空');
  76. }
  77. $this->securityKey = $securityKey;
  78. if (false === $this->isSupportCipherMethod($method)) {
  79. throw new \RuntimeException('暂不支持该加密方式');
  80. }
  81. $this->method = $method;
  82. $this->iv = $this->initializationVector($method, $iv);
  83. }
  84. /**
  85. * 加密
  86. * @param string $plainText 明文
  87. * @return bool|string
  88. */
  89. public function encrypt($plainText)
  90. {
  91. $originData = (openssl_encrypt($this->addPkcs7Padding($plainText, 16), $this->method, $this->securityKey, OPENSSL_NO_PADDING, $this->iv));
  92. return $originData === false ? false : base64_encode($originData);
  93. }
  94. /**
  95. * 解密
  96. * @param string $cipherText 密文
  97. * @return bool|string
  98. */
  99. public function decrypt($cipherText)
  100. {
  101. $str = base64_decode($cipherText);
  102. $data = openssl_decrypt($str, $this->method, $this->securityKey, OPENSSL_NO_PADDING, $this->iv);
  103. return $data === false ? false : $this->stripPKSC7Padding($data);
  104. }
  105. /**
  106. * 初始化向量
  107. * @param string $method
  108. * @param string $iv
  109. * @return false|string
  110. */
  111. private function initializationVector($method,$iv = '')
  112. {
  113. $originIvLen = openssl_cipher_iv_length($method);
  114. if(false === $originIvLen) { return ''; }
  115. $currentIvLen = strlen($iv);
  116. if ($originIvLen === $currentIvLen) {
  117. $outIv = $iv;
  118. } elseif ($currentIvLen < $originIvLen) {
  119. $outIv = $iv . str_repeat("\0", $originIvLen - $currentIvLen);
  120. } elseif ($currentIvLen > $originIvLen) {
  121. $outIv = substr($iv, 0, $originIvLen);
  122. } else {
  123. $outIv = str_repeat("\0", $originIvLen);
  124. }
  125. return $outIv;
  126. }
  127. /**
  128. * 填充算法
  129. * @param string $source
  130. * @return string
  131. */
  132. private function addPKCS7Padding($source)
  133. {
  134. $source = trim($source);
  135. $block = 16;
  136. $pad = $block - (strlen($source) % $block);
  137. if ($pad <= $block) {
  138. $char = chr($pad);
  139. $source .= str_repeat($char, $pad);
  140. }
  141. return $source;
  142. }
  143. /**
  144. * 是否支持该加密方式
  145. * @param string $method
  146. * @return bool
  147. */
  148. private function isSupportCipherMethod($method)
  149. {
  150. $method = strtolower($method);
  151. if (in_array($method, openssl_get_cipher_methods(), true)) {
  152. return true;
  153. }
  154. return false;
  155. }
  156. /**
  157. * 移去填充算法
  158. * @param string $source
  159. * @return string
  160. */
  161. private function stripPKSC7Padding($source)
  162. {
  163. $char = substr($source, -1);
  164. $num = ord($char);
  165. if ($num === 62) return $source;
  166. $source = substr($source, 0, -$num);
  167. return $source;
  168. }
  169. /**
  170. * 十六进制转字符串
  171. * @param $hex
  172. * @return string
  173. */
  174. private function hexToStr($hex)
  175. {
  176. $string = "";
  177. for ($i = 0; $i < strlen($hex) - 1; $i += 2) {
  178. $string .= chr(hexdec($hex[$i] . $hex[$i + 1]));
  179. }
  180. return $string;
  181. }
  182. /**
  183. * 字符串转十六进制
  184. * @param $string
  185. * @return string
  186. */
  187. private function strToHex($string)
  188. {
  189. $hex = "";
  190. $tmp = "";
  191. $iMax = strlen($string);
  192. for ($i = 0; $i < $iMax; $i++) {
  193. $tmp = dechex(ord($string[$i]));
  194. $hex .= strlen($tmp) === 1 ? "0" . $tmp : $tmp;
  195. }
  196. $hex = strtoupper($hex);
  197. return $hex;
  198. }
  199. }