entry = $entry; } /** * Initial keys * * @param string $password */ private function initKeys($password) { $this->keys[0] = 305419896; $this->keys[1] = 591751049; $this->keys[2] = 878082192; foreach (unpack('C*', $password) as $b) { $this->updateKeys($b); } } /** * Update keys. * * @param string $charAt */ private function updateKeys($charAt) { $this->keys[0] = self::crc32($this->keys[0], $charAt); $this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff); $this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1); $this->keys[2] = PackUtil::toSignedInt32(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff)); } /** * Update crc. * * @param int $oldCrc * @param string $charAt * @return int */ private function crc32($oldCrc, $charAt) { return (($oldCrc >> 8) & 0xffffff) ^ self::$CRC_TABLE[($oldCrc ^ $charAt) & 0xff]; } /** * @param string $content * @return string * @throws ZipAuthenticationException */ public function decrypt($content) { $password = $this->entry->getPassword(); $this->initKeys($password); $headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE))); $byte = 0; foreach ($headerBytes as &$byte) { $byte = ($byte ^ $this->decryptByte()) & 0xff; $this->updateKeys($byte); } if ($this->entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) { // compare against the file type from extended local headers $checkByte = ($this->entry->getDosTime() >> 8) & 0xff; } else { // compare against the CRC otherwise $checkByte = ($this->entry->getCrc() >> 24) & 0xff; } if ($byte !== $checkByte) { throw new ZipAuthenticationException("Bad password for entry " . $this->entry->getName()); } $outputContent = ""; foreach (unpack('C*', substr($content, self::STD_DEC_HDR_SIZE)) as $val) { $val = ($val ^ $this->decryptByte()) & 0xff; $this->updateKeys($val); $outputContent .= pack('c', $val); } return $outputContent; } /** * Decrypt byte. * * @return int */ private function decryptByte() { $temp = $this->keys[2] | 2; return (($temp * ($temp ^ 1)) >> 8) & 0xffffff; } /** * Encryption data * * @param string $data * @return string */ public function encrypt($data) { $crc = $this->entry->isDataDescriptorRequired() ? ($this->entry->getDosTime() & 0x0000ffff) << 16 : $this->entry->getCrc(); $headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE); // Initialize again since the generated bytes were encrypted. $password = $this->entry->getPassword(); $this->initKeys($password); $headerBytes[self::STD_DEC_HDR_SIZE - 1] = pack('c', ($crc >> 24) & 0xff); $headerBytes[self::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff); $headerBytes = $this->encryptData($headerBytes); return $headerBytes . $this->encryptData($data); } /** * @param string $content * @return string * @throws ZipCryptoException */ private function encryptData($content) { if (null === $content) { throw new ZipCryptoException('content is null'); } $buff = ''; foreach (unpack('C*', $content) as $val) { $buff .= pack('c', $this->encryptByte($val)); } return $buff; } /** * @param int $byte * @return int */ private function encryptByte($byte) { $tempVal = $byte ^ $this->decryptByte() & 0xff; $this->updateKeys($byte); return $tempVal; } }