4, 'modify' => 8, 'copy' => 16, 'annot-forms' => 32 ); $protection = 192; foreach($permissions as $permission){ if (!isset($options[$permission])) $this->Error('Incorrect permission: '.$permission); $protection += $options[$permission]; } if ($owner_pass === null) $owner_pass = uniqid(rand()); $this->encrypted = true; $this->_generateencryptionkey($user_pass, $owner_pass, $protection); } function _putstream($s) { if ($this->encrypted) { $s = $this->_RC4($this->_objectkey($this->_current_obj_id), $s); } parent::_putstream($s); } function _textstring($s) { if ($this->encrypted) { $s = $this->_RC4($this->_objectkey($this->_current_obj_id), $s); } return parent::_textstring($s); } /** * Compute key depending on object number where the encrypted data is stored */ function _objectkey($n) { return substr($this->_md5_16($this->encryption_key.pack('VXxx', $n)), 0, 10); } /** * Escape special characters */ function _escape($s) { return str_replace( array('\\',')','(',"\r", "\n", "\t"), array('\\\\','\\)','\\(','\\r', '\\n', '\\t'),$s); } function _putresources() { parent::_putresources(); if ($this->encrypted) { $this->_newobj(); $this->enc_obj_id = $this->_current_obj_id; $this->_out('<<'); $this->_putencryption(); $this->_out('>>'); } } function _putencryption() { $this->_out('/Filter /Standard'); $this->_out('/V 1'); $this->_out('/R 2'); $this->_out('/O ('.$this->_escape($this->Ovalue).')'); $this->_out('/U ('.$this->_escape($this->Uvalue).')'); $this->_out('/P '.$this->Pvalue); } function _puttrailer() { parent::_puttrailer(); if ($this->encrypted) { $this->_out('/Encrypt '.$this->enc_obj_id.' 0 R'); $this->_out('/ID [()()]'); } } /** * RC4 is the standard encryption algorithm used in PDF format */ function _RC4($key, $text) { if ($this->last_rc4_key != $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->last_rc4_key = $key; $this->last_rc4_key_c = $rc4; } else { $rc4 = $this->last_rc4_key_c; } $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; } /** * Get MD5 as binary string */ function _md5_16($string) { return pack('H*',md5($string)); } /** * Compute O value */ function _Ovalue($user_pass, $owner_pass) { $tmp = $this->_md5_16($owner_pass); $owner_RC4_key = substr($tmp,0,5); return $this->_RC4($owner_RC4_key, $user_pass); } /** * Compute U value */ function _Uvalue() { return $this->_RC4($this->encryption_key, $this->padding); } /** * Compute encryption key */ 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); // Compute O value $this->Ovalue = $this->_Ovalue($user_pass,$owner_pass); // Compute encyption key $tmp = $this->_md5_16($user_pass.$this->Ovalue.chr($protection)."\xFF\xFF\xFF"); $this->encryption_key = substr($tmp,0,5); // Compute U value $this->Uvalue = $this->_Uvalue(); // Compute P value $this->Pvalue = -(($protection^255)+1); } function pdf_write_value(&$value) { switch ($value[0]) { case PDF_TYPE_STRING : if ($this->encrypted) { $value[1] = $this->_unescape($value[1]); $value[1] = $this->_RC4($this->_objectkey($this->_current_obj_id), $value[1]); $value[1] = $this->_escape($value[1]); } break; case PDF_TYPE_STREAM : if ($this->encrypted) { $value[2][1] = $this->_RC4($this->_objectkey($this->_current_obj_id), $value[2][1]); } break; case PDF_TYPE_HEX : if ($this->encrypted) { $value[1] = $this->hex2str($value[1]); $value[1] = $this->_RC4($this->_objectkey($this->_current_obj_id), $value[1]); // remake hexstring of encrypted string $value[1] = $this->str2hex($value[1]); } break; } parent::pdf_write_value($value); } function hex2str($hex) { return pack('H*', str_replace(array("\r","\n",' '),'', $hex)); } function str2hex($str) { return current(unpack('H*',$str)); } /** * Deescape special characters */ function _unescape($s) { $out = ''; for ($count = 0, $n = strlen($s); $count < $n; $count++) { if ($s[$count] != '\\' || $count == $n-1) { $out .= $s[$count]; } else { switch ($s[++$count]) { case ')': case '(': case '\\': $out .= $s[$count]; break; case 'f': $out .= chr(0x0C); break; case 'b': $out .= chr(0x08); break; case 't': $out .= chr(0x09); break; case 'r': $out .= chr(0x0D); break; case 'n': $out .= chr(0x0A); break; case "\r": if ($count != $n-1 && $s[$count+1] == "\n") $count++; break; case "\n": break; default: // Octal-Values if (ord($s[$count]) >= ord('0') && ord($s[$count]) <= ord('9')) { $oct = ''. $s[$count]; if (ord($s[$count+1]) >= ord('0') && ord($s[$count+1]) <= ord('9')) { $oct .= $s[++$count]; if (ord($s[$count+1]) >= ord('0') && ord($s[$count+1]) <= ord('9')) { $oct .= $s[++$count]; } } $out .= chr(octdec($oct)); } else { $out .= $s[$count]; } } } } return $out; } }