stringToArrayBufferInUtf8($key); if(count($keyBuffer) != $this->len){ throw new \Exception('秘钥长度必须为16位'); } $this->key = $keyBuffer; $ivBuffer = $this->stringToArrayBufferInUtf8($iv); if(count($ivBuffer) != $this->len){ throw new \Exception('偏移量长度必须为16位'); } $this->iv = $ivBuffer; $encryptRoundKeys = []; $this->encryptRoundKeys = array_pad($encryptRoundKeys, 32, 0); $this->spawnEncryptRoundKeys(); $this->decryptRoundKeys = array_reverse($this->encryptRoundKeys); $code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; for ($i = 0, $len = strlen($code); $i < $len; ++$i) { $this->revLookup[$this->get_bianma(substr($code, $i, 1))] = $i; } $this->revLookup[$this->get_bianma(substr('-', 0, 1))] = 62; $this->revLookup[$this->get_bianma(substr('_', 0, 1))] = 63; } private function stringToArrayBufferInUtf8($str) { $arr = str_split($str); foreach ($arr as &$v) { $v = ord($v); } return $arr; } private function spawnEncryptRoundKeys() { $mk = []; $mk[0] = ($this->ll($this->key[0],24)) | ($this->ll($this->key[1],16)) | ($this->ll($this->key[2],8)) | $this->key[3]; $mk[1] = ($this->ll($this->key[4],24)) | ($this->ll($this->key[5],16)) | ($this->ll($this->key[6],8)) | $this->key[7]; $mk[2] = ($this->ll($this->key[8],24)) | ($this->ll($this->key[9],16)) | ($this->ll($this->key[10],8)) | $this->key[11]; $mk[3] = ($this->ll($this->key[12],24)) | ($this->ll($this->key[13],16)) | ($this->ll($this->key[14],8)) | $this->key[15]; $k = []; $k[0] = $mk[0] ^ $this->fk[0]; $k[1] = $mk[1] ^ $this->fk[1]; $k[2] = $mk[2] ^ $this->fk[2]; $k[3] = $mk[3] ^ $this->fk[3]; for ($i = 0; $i < 32; $i++) { $k[$i + 4] = $k[$i] ^ $this->tTransform2($k[$i + 1] ^ $k[$i + 2] ^ $k[$i + 3] ^ $this->ck[$i]); $this->encryptRoundKeys[$i] = $k[$i + 4]; } } private function tauTransform($a) { return ($this->ll($this->sbox[$this->uright($a, 24) & 0xff],24)) | ($this->ll($this->sbox[$this->uright($a, 16) & 0xff],16)) | ($this->ll($this->sbox[$this->uright($a, 8) & 0xff],8)) | $this->sbox[$a & 0xff]; } private function tTransform1($z) { $b = $this->tauTransform($z); $c = $this->linearTransform1($b); return $c; } private function tTransform2($z) { $b = $this->tauTransform($z); $c = $this->linearTransform2($b); return $c; } private function linearTransform1($b) { return ($b ^ $this->rotateLeft($b, 2) ^ $this->rotateLeft($b, 10) ^ $this->rotateLeft($b, 18) ^ $this->rotateLeft($b, 24)); } private function linearTransform2($b) { return $b ^ $this->rotateLeft($b, 13) ^ $this->rotateLeft($b, 23); } private function rotateLeft($x, $y) { return ($this->ll($x,$y)) | $this->uright($x, (32 - $y)); } private function dePadding($paddedBuffer) { if ($paddedBuffer === null) { return null; } $paddingLength = $paddedBuffer[count($paddedBuffer) - 1]; $originalBuffer = array_slice($paddedBuffer,0,count($paddedBuffer) - $paddingLength); return $originalBuffer; } public function encrypt($str) { $strBuffer = $this->stringToArrayBufferInUtf8($str); $padded = $this->padding($strBuffer); $blockTimes = count($padded) / 16; $outArray = []; $outArray = array_pad($outArray,count($padded),0); $chainBlock = $this->uint8ToUint32Block($this->iv); for ($i = 0; $i < $blockTimes; $i++){ $roundIndex = $i * 16; $block = $this->uint8ToUint32Block($padded, $roundIndex); $chainBlock[0] = $chainBlock[0] ^ $block[0]; $chainBlock[1] = $chainBlock[1] ^ $block[1]; $chainBlock[2] = $chainBlock[2] ^ $block[2]; $chainBlock[3] = $chainBlock[3] ^ $block[3]; // use chain block to crypt $cipherBlock = $this->doBlockCrypt($chainBlock, $this->encryptRoundKeys); // make the cipher block be part of next chain block $chainBlock = $cipherBlock; for ($l = 0; $l < 16; $l++) { $outArray[$roundIndex + $l] = ($this->rr($cipherBlock[intval($l / 4)],(((3 - $l) % 4) * 8))) & 0xff; } } return base64_encode(base64_encode($this->tostr($outArray))); } public function decrypt($str){ $cipherByteArray = $this->getbytes(base64_decode($str)); $blockTimes = count($cipherByteArray) / 16; $outArray = []; $outArray = array_pad($outArray,count($cipherByteArray),0); $chainBlock = $this->uint8ToUint32Block($this->iv); for ($i = 0; $i < $blockTimes; $i++) { // extract the 16 bytes block data for this round to encrypt $roundIndex = $i * 16; // make Uint8Array to Uint32Array block $block = $this->uint8ToUint32Block($cipherByteArray, $roundIndex); // reverse the round keys to decrypt $plainBlockBeforeXor = $this->doBlockCrypt($block,$this->decryptRoundKeys); $plainBlock = []; $plainBlock[0] = $chainBlock[0] ^ $plainBlockBeforeXor[0]; $plainBlock[1] = $chainBlock[1] ^ $plainBlockBeforeXor[1]; $plainBlock[2] = $chainBlock[2] ^ $plainBlockBeforeXor[2]; $plainBlock[3] = $chainBlock[3] ^ $plainBlockBeforeXor[3]; $chainBlock = $block; for ($l = 0; $l < 16; $l++) { $outArray[$roundIndex + $l] = ($this->rr($plainBlock[intval($l / 4)],(((3 - $l) % 4) * 8))) & 0xff; } } $depaddedPlaintext = $this->dePadding($outArray); return $this->tostr($depaddedPlaintext); } public function uright($a, $n) { $c = $this->rr(2147483647,$n-1); return $c&($this->rr($a,$n)); } private function padding($originalBuffer) { if ($originalBuffer === null) { return null; } $paddingLength = 16 - (count($originalBuffer) % 16); $paddedBuffer = []; $length = count($originalBuffer) + $paddingLength; $paddedBuffer = array_pad($paddedBuffer, $length, $paddingLength); $paddedBuffer = $originalBuffer+$paddedBuffer; return $paddedBuffer; } private function uint8ToUint32Block($arr,$baseIndex = 0) { $block = [ ($this->ll($arr[$baseIndex],24)) | ($this->ll($arr[$baseIndex + 1],16)) | ($this->ll($arr[$baseIndex + 2],8)) | $arr[$baseIndex + 3], ($this->ll($arr[$baseIndex + 4],24)) | ($this->ll($arr[$baseIndex + 5],16)) | ($this->ll($arr[$baseIndex + 6],8)) | $arr[$baseIndex + 7], ($this->ll($arr[$baseIndex + 8],24)) | ($this->ll($arr[$baseIndex + 9],16)) | ($this->ll($arr[$baseIndex + 10],8)) | $arr[$baseIndex + 11], ($this->ll($arr[$baseIndex + 12],24)) | ($this->ll($arr[$baseIndex + 13],16)) | ($this->ll($arr[$baseIndex + 14],8)) | $arr[$baseIndex + 15] ]; return $block; } private function doBlockCrypt($blockData, $roundKeys) { $xBlock = []; $xBlock = array_pad($xBlock, 36, 0); $xBlock = $blockData + $xBlock; // loop to process 32 rounds crypt for ($i = 0; $i < 32; $i++) { $xBlock[$i + 4] = $xBlock[$i] ^ $this->tTransform1($xBlock[$i + 1] ^ $xBlock[$i + 2] ^ $xBlock[$i + 3] ^ $roundKeys[$i]); } $yBlock = []; // reverse last 4 xBlock member $yBlock[0] = $xBlock[35]; $yBlock[1] = $xBlock[34]; $yBlock[2] = $xBlock[33]; $yBlock[3] = $xBlock[32]; return $yBlock; } /** * >>> javascript operator in php x86_64 * @param int $v * @param int $n * @return int */ private function rrr($v, $n) { return ($v & 0xFFFFFFFF) >> ($n & 0x1F); } /** * >> javascript operator in php x86_64 * @param int $v * @param int $n * @return int */ private function rr($v, $n) { $v = $v & 0x80000000 ? $v | 0xFFFFFFFF00000000 : $v & 0xFFFFFFFF; return $v >> ($n & 0x1F); } /** * << javascript operator in php x86_64 * @param int $v * @param int $n * @return int */ private function ll($v, $n) { $t = ($v & 0xFFFFFFFF) << ($n & 0x1F); return $t & 0x80000000 ? $t | 0xFFFFFFFF00000000 : $t & 0xFFFFFFFF; } private function f($x0, $x1, $x2, $x3, $r) { return $x0 ^ $this->t($x1 ^ $x2 ^ $x3 ^ $r); } /** * 将字节数组转化为string类型的数据 * @param $bytes 字节数组 * @param $str 目标字符串 * @return 一个string类型的数据 */ public function tostr($bytes) { $str = ''; foreach($bytes as $ch) { $str .= chr($ch); } return $str; } /** * 转换一个string字符串为byte数组 * @param $str 需要转换的字符串 * @param $bytes 目标byte数组 */ public function getbytes($str) { $len = strlen($str); if ($len % 4 > 0) { throw new Error('Invalid string. Length must be a multiple of 4'); } $validLen = stripos($str,'='); if ($validLen === -1) $validLen = $len; $placeHoldersLen = $validLen === $len ? 0 : 4 - ($validLen % 4); $array_length = (($validLen + $placeHoldersLen) * 3 / 4) - $placeHoldersLen; $bytes = array(); $bytes = array_pad($bytes, $array_length, 0); $curByte = 0; $len = $placeHoldersLen > 0 ? $validLen - 4 : $validLen; for($i=0;$i<$len;$i += 4) { $tmp = ($this->ll($this->revLookup[$this->get_bianma(substr($str, $i, 1))],18)) | ($this->ll($this->revLookup[$this->get_bianma(substr($str, $i + 1, 1))],12)) | ($this->ll($this->revLookup[$this->get_bianma(substr($str, $i + 2, 1))],6)) | ($this->revLookup[$this->get_bianma(substr($str, $i + 3, 1))]); //$tmp = $this->ll('sdfasdf',18); $bytes[$curByte++] = ($this->rr($tmp, 16)) & 0xFF; $bytes[$curByte++] = ($this->rr($tmp, 8)) & 0xFF; $bytes[$curByte++] = $tmp & 0xFF; // if(ord($str[$i]) >= 128){ // $byte = ord($str[$i]) - 256; // }else{ // $byte = ord($str[$i]); // } // $bytes[] = $byte ; } if ($placeHoldersLen === 2) { $tmp = ($this->ll($this->revLookup[$this->get_bianma(substr($str, $i, 1))],2)) | ($this->rr($this->revLookup[$this->get_bianma(substr($str, $i + 1, 1))],4)); $bytes[$curByte++] = $tmp & 0xFF; } if ($placeHoldersLen === 1) { $tmp = ($this->ll($this->revLookup[$this->get_bianma(substr($str, $i, 1))],10)) | ($this->ll($this->revLookup[$this->get_bianma(substr($str, $i + 1, 1))],4)) | ($this->rr($this->revLookup[$this->get_bianma(substr($str, $i + 2, 1))],2)); $bytes[$curByte++] = ($this->rr($tmp, 8)) & 0xFF; $bytes[$curByte++] = $tmp & 0xFF; } return $bytes; } private function get_bianma($str)//等同于js的charCodeAt() { $result = array(); for($i = 0, $l = mb_strlen($str, 'utf-8');$i < $l;++$i) { $result[] = $this->uniord(mb_substr($str, $i, 1, 'utf-8')); } return join(",", $result); } private function uniord($str, $from_encoding = false) { $from_encoding = $from_encoding ? $from_encoding : 'UTF-8'; if (strlen($str) == 1) return ord($str); $str = mb_convert_encoding($str, 'UCS-4BE', $from_encoding); $tmp = unpack('N', $str); return $tmp[1]; } }