mirror of
https://github.com/Karaka-Management/Resources.git
synced 2026-01-11 13:28:40 +00:00
361 lines
7.1 KiB
PHP
361 lines
7.1 KiB
PHP
<?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, 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;
|
|
}
|
|
}
|