Viewing file: Protection.php (7.14 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Mpdf\Pdf;
use Mpdf\Pdf\Protection\UniqidGenerator;
class Protection {
/** * @var string */ private $lastRc4Key;
/** * @var string */ private $lastRc4KeyC;
/** * @var bool */ private $useRC128Encryption;
/** * @var string */ private $encryptionKey;
/** * @var string */ private $padding;
/** * @var string */ private $uniqid;
/** * @var string */ private $oValue;
/** * @var string */ private $uValue;
/** * @var string */ private $pValue;
/** * @var int[] Array of permission => byte representation */ private $options;
/** * @var \Mpdf\Pdf\Protection\UniqidGenerator */ private $uniqidGenerator;
public function __construct(UniqidGenerator $uniqidGenerator) { if (!function_exists('random_int') || !function_exists('random_bytes')) { throw new \Mpdf\MpdfException( 'Unable to set PDF file protection, CSPRNG Functions are not available. ' . 'Use paragonie/random_compat polyfill or upgrade to PHP 7.' ); }
$this->uniqidGenerator = $uniqidGenerator;
$this->lastRc4Key = '';
$this->padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" . "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
$this->useRC128Encryption = false;
$this->options = [ 'print' => 4, // bit 3 'modify' => 8, // bit 4 'copy' => 16, // bit 5 'annot-forms' => 32, // bit 6 'fill-forms' => 256, // bit 9 'extract' => 512, // bit 10 'assemble' => 1024, // bit 11 'print-highres' => 2048 // bit 12 ]; }
/** * @param array $permissions * @param string $user_pass * @param string $owner_pass * @param int $length * * @return bool */ public function setProtection($permissions = [], $user_pass = '', $owner_pass = null, $length = 40) { if (is_string($permissions) && strlen($permissions) > 0) { $permissions = [$permissions]; } elseif (!is_array($permissions)) { return false; }
$protection = $this->getProtectionBitsFromOptions($permissions);
if ($length === 128) { $this->useRC128Encryption = true; } elseif ($length !== 40) { throw new \Mpdf\MpdfException('PDF protection only allows lenghts of 40 or 128'); }
if ($owner_pass === null) { $owner_pass = bin2hex(random_bytes(23)); }
$this->generateEncryptionKey($user_pass, $owner_pass, $protection);
return true; }
/** * Compute key depending on object number where the encrypted data is stored * * @param int $n * * @return string */ public function objectKey($n) { if ($this->useRC128Encryption) { $len = 16; } else { $len = 10; }
return substr($this->md5toBinary($this->encryptionKey . pack('VXxx', $n)), 0, $len); }
/** * RC4 is the standard encryption algorithm used in PDF format * * @param string $key * @param string $text * * @return string */ public function rc4($key, $text) { if ($this->lastRc4Key != $key) { $k = str_repeat($key, round(256 / strlen($key)) + 1); $rc4 = range(0, 255); $j = 0; for ($i = 0; $i < 256; $i++) { $t = $rc4[$i]; $j = ($j + $t + ord($k[$i])) % 256; $rc4[$i] = $rc4[$j]; $rc4[$j] = $t; } $this->lastRc4Key = $key; $this->lastRc4KeyC = $rc4; } else { $rc4 = $this->lastRc4KeyC; }
$len = strlen($text); $a = 0; $b = 0; $out = ''; for ($i = 0; $i < $len; $i++) { $a = ($a + 1) % 256; $t = $rc4[$a]; $b = ($b + $t) % 256; $rc4[$a] = $rc4[$b]; $rc4[$b] = $t; $k = $rc4[($rc4[$a] + $rc4[$b]) % 256]; $out .= chr(ord($text[$i]) ^ $k); }
return $out; }
/** * @return mixed */ public function getUseRC128Encryption() { return $this->useRC128Encryption; }
/** * @return mixed */ public function getUniqid() { return $this->uniqid; }
/** * @return mixed */ public function getOValue() { return $this->oValue; }
/** * @return mixed */ public function getUValue() { return $this->uValue; }
/** * @return mixed */ public function getPValue() { return $this->pValue; }
private function getProtectionBitsFromOptions($permissions) { // bit 31 = 1073741824 // bit 32 = 2147483648 // bits 13-31 = 2147479552 // bits 13-32 = 4294963200 + 192 = 4294963392
$protection = 4294963392; // bits 7, 8, 13-32
foreach ($permissions as $permission) { if (!isset($this->options[$permission])) { throw new \Mpdf\MpdfException(sprintf('Invalid permission type "%s"', $permission)); } if ($this->options[$permission] > 32) { $this->useRC128Encryption = true; } if (isset($this->options[$permission])) { $protection += $this->options[$permission]; } }
return $protection; }
private function oValue($user_pass, $owner_pass) { $tmp = $this->md5toBinary($owner_pass); if ($this->useRC128Encryption) { for ($i = 0; $i < 50; ++$i) { $tmp = $this->md5toBinary($tmp); } } if ($this->useRC128Encryption) { $keybytelen = (128 / 8); } else { $keybytelen = (40 / 8); } $owner_rc4_key = substr($tmp, 0, $keybytelen); $enc = $this->rc4($owner_rc4_key, $user_pass); if ($this->useRC128Encryption) { $len = strlen($owner_rc4_key); for ($i = 1; $i <= 19; ++$i) { $key = ''; for ($j = 0; $j < $len; ++$j) { $key .= chr(ord($owner_rc4_key[$j]) ^ $i); } $enc = $this->rc4($key, $enc); } }
return $enc; }
private function uValue() { if ($this->useRC128Encryption) { $tmp = $this->md5toBinary($this->padding . $this->hexToString($this->uniqid)); $enc = $this->rc4($this->encryptionKey, $tmp); $len = strlen($tmp); for ($i = 1; $i <= 19; ++$i) { $key = ''; for ($j = 0; $j < $len; ++$j) { $key .= chr(ord($this->encryptionKey[$j]) ^ $i); } $enc = $this->rc4($key, $enc); } $enc .= str_repeat("\x00", 16);
return substr($enc, 0, 32); } else { return $this->rc4($this->encryptionKey, $this->padding); } }
private function generateEncryptionKey($user_pass, $owner_pass, $protection) { // Pad passwords $user_pass = substr($user_pass . $this->padding, 0, 32); $owner_pass = substr($owner_pass . $this->padding, 0, 32);
$this->oValue = $this->oValue($user_pass, $owner_pass);
$this->uniqid = $this->uniqidGenerator->generate();
// Compute encyption key if ($this->useRC128Encryption) { $keybytelen = (128 / 8); } else { $keybytelen = (40 / 8); }
$prot = sprintf('%032b', $protection);
$perms = chr(bindec(substr($prot, 24, 8))); $perms .= chr(bindec(substr($prot, 16, 8))); $perms .= chr(bindec(substr($prot, 8, 8))); $perms .= chr(bindec(substr($prot, 0, 8)));
$tmp = $this->md5toBinary($user_pass . $this->oValue . $perms . $this->hexToString($this->uniqid));
if ($this->useRC128Encryption) { for ($i = 0; $i < 50; ++$i) { $tmp = $this->md5toBinary(substr($tmp, 0, $keybytelen)); } }
$this->encryptionKey = substr($tmp, 0, $keybytelen);
$this->uValue = $this->uValue(); $this->pValue = $protection; }
private function md5toBinary($string) { return pack('H*', md5($string)); }
private function hexToString($hs) { $s = ''; $len = strlen($hs); if (($len % 2) != 0) { $hs .= '0'; ++$len; } for ($i = 0; $i < $len; $i += 2) { $s .= chr(hexdec($hs[$i] . $hs[($i + 1)])); }
return $s; } }
|