FPDI_Protection.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. /****************************************************************************
  3. * Software: FPDI_Protection *
  4. * Version: 1.0.2 *
  5. * Date: 2006/11/20 *
  6. * Author: Klemen VODOPIVEC, Jan Slabon *
  7. * License: Freeware *
  8. * *
  9. * You may use and modify this software as you wish as stated in original *
  10. * FPDF package. *
  11. * *
  12. * Infos (by Jan Slabon): *
  13. * This class extends the FPDI-class available at http://www.setasign.de *
  14. * so that you can import pages and create new protected pdf files. *
  15. * *
  16. * This release is dedicated to my son Fin Frederik (*2005/06/04) *
  17. * *
  18. ****************************************************************************/
  19. require_once("fpdi.php");
  20. class FPDI_Protection extends FPDI {
  21. var $encrypted; //whether document is protected
  22. var $Uvalue; //U entry in pdf document
  23. var $Ovalue; //O entry in pdf document
  24. var $Pvalue; //P entry in pdf document
  25. var $enc_obj_id; //encryption object id
  26. var $last_rc4_key; //last RC4 key encrypted (cached for optimisation)
  27. var $last_rc4_key_c; //last RC4 computed key
  28. var $padding = '';
  29. function FPDI_Protection($orientation='P',$unit='mm',$format='A4')
  30. {
  31. parent::FPDI($orientation,$unit,$format);
  32. $this->_current_obj_id =& $this->current_obj_id; // for FPDI 1.1 compatibility
  33. $this->encrypted=false;
  34. $this->last_rc4_key = '';
  35. $this->padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08".
  36. "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
  37. }
  38. /**
  39. * Function to set permissions as well as user and owner passwords
  40. *
  41. * - permissions is an array with values taken from the following list:
  42. * 40bit: copy, print, modify, annot-forms
  43. * 128bit: fill-in, screenreaders, assemble, degraded-print
  44. * If a value is present it means that the permission is granted
  45. * - If a user password is set, user will be prompted before document is opened
  46. * - If an owner password is set, document can be opened in privilege mode with no
  47. * restriction if that password is entered
  48. */
  49. function SetProtection($permissions=array(),$user_pass='',$owner_pass=null)
  50. {
  51. $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'annot-forms' => 32 );
  52. $protection = 192;
  53. foreach($permissions as $permission){
  54. if (!isset($options[$permission]))
  55. $this->Error('Incorrect permission: '.$permission);
  56. $protection += $options[$permission];
  57. }
  58. if ($owner_pass === null)
  59. $owner_pass = uniqid(rand());
  60. $this->encrypted = true;
  61. $this->_generateencryptionkey($user_pass, $owner_pass, $protection);
  62. }
  63. function _putstream($s)
  64. {
  65. if ($this->encrypted) {
  66. $s = $this->_RC4($this->_objectkey($this->_current_obj_id), $s);
  67. }
  68. parent::_putstream($s);
  69. }
  70. function _textstring($s)
  71. {
  72. if ($this->encrypted) {
  73. $s = $this->_RC4($this->_objectkey($this->_current_obj_id), $s);
  74. }
  75. return parent::_textstring($s);
  76. }
  77. /**
  78. * Compute key depending on object number where the encrypted data is stored
  79. */
  80. function _objectkey($n)
  81. {
  82. return substr($this->_md5_16($this->encryption_key.pack('VXxx',$n)),0,10);
  83. }
  84. /**
  85. * Escape special characters
  86. */
  87. function _escape($s)
  88. {
  89. return str_replace(
  90. array('\\',')','(',"\r"),
  91. array('\\\\','\\)','\\(','\\r'),$s);
  92. }
  93. function _putresources()
  94. {
  95. parent::_putresources();
  96. if ($this->encrypted) {
  97. $this->_newobj();
  98. $this->enc_obj_id = $this->_current_obj_id;
  99. $this->_out('<<');
  100. $this->_putencryption();
  101. $this->_out('>>');
  102. }
  103. }
  104. function _putencryption()
  105. {
  106. $this->_out('/Filter /Standard');
  107. $this->_out('/V 1');
  108. $this->_out('/R 2');
  109. $this->_out('/O ('.$this->_escape($this->Ovalue).')');
  110. $this->_out('/U ('.$this->_escape($this->Uvalue).')');
  111. $this->_out('/P '.$this->Pvalue);
  112. }
  113. function _puttrailer()
  114. {
  115. parent::_puttrailer();
  116. if ($this->encrypted) {
  117. $this->_out('/Encrypt '.$this->enc_obj_id.' 0 R');
  118. $id = isset($this->fileidentifier) ? $this->fileidentifier : '';
  119. $this->_out('/ID [<'.$id.'><'.$id.'>]');
  120. }
  121. }
  122. /**
  123. * RC4 is the standard encryption algorithm used in PDF format
  124. */
  125. function _RC4($key, $text)
  126. {
  127. if ($this->last_rc4_key != $key) {
  128. $k = str_repeat($key, 256/strlen($key)+1);
  129. $rc4 = range(0,255);
  130. $j = 0;
  131. for ($i=0; $i<256; $i++){
  132. $t = $rc4[$i];
  133. $j = ($j + $t + ord($k{$i})) % 256;
  134. $rc4[$i] = $rc4[$j];
  135. $rc4[$j] = $t;
  136. }
  137. $this->last_rc4_key = $key;
  138. $this->last_rc4_key_c = $rc4;
  139. } else {
  140. $rc4 = $this->last_rc4_key_c;
  141. }
  142. $len = strlen($text);
  143. $a = 0;
  144. $b = 0;
  145. $out = '';
  146. for ($i=0; $i<$len; $i++){
  147. $a = ($a+1)%256;
  148. $t= $rc4[$a];
  149. $b = ($b+$t)%256;
  150. $rc4[$a] = $rc4[$b];
  151. $rc4[$b] = $t;
  152. $k = $rc4[($rc4[$a]+$rc4[$b])%256];
  153. $out.=chr(ord($text{$i}) ^ $k);
  154. }
  155. return $out;
  156. }
  157. /**
  158. * Get MD5 as binary string
  159. */
  160. function _md5_16($string)
  161. {
  162. return pack('H*',md5($string));
  163. }
  164. /**
  165. * Compute O value
  166. */
  167. function _Ovalue($user_pass, $owner_pass)
  168. {
  169. $tmp = $this->_md5_16($owner_pass);
  170. $owner_RC4_key = substr($tmp,0,5);
  171. return $this->_RC4($owner_RC4_key, $user_pass);
  172. }
  173. /**
  174. * Compute U value
  175. */
  176. function _Uvalue()
  177. {
  178. return $this->_RC4($this->encryption_key, $this->padding);
  179. }
  180. /**
  181. * Compute encryption key
  182. */
  183. function _generateencryptionkey($user_pass, $owner_pass, $protection)
  184. {
  185. // Pad passwords
  186. $user_pass = substr($user_pass.$this->padding,0,32);
  187. $owner_pass = substr($owner_pass.$this->padding,0,32);
  188. // Compute O value
  189. $this->Ovalue = $this->_Ovalue($user_pass,$owner_pass);
  190. // Compute encyption key
  191. $tmp = $this->_md5_16($user_pass.$this->Ovalue.chr($protection)."\xFF\xFF\xFF");
  192. $this->encryption_key = substr($tmp,0,5);
  193. // Compute U value
  194. $this->Uvalue = $this->_Uvalue();
  195. // Compute P value
  196. $this->Pvalue = -(($protection^255)+1);
  197. }
  198. function pdf_write_value(&$value) {
  199. switch ($value[0]) {
  200. case PDF_TYPE_STRING :
  201. if ($this->encrypted) {
  202. $value[1] = $this->_RC4($this->_objectkey($this->_current_obj_id), $value[1]);
  203. $value[1] = $this->_escape($value[1]);
  204. }
  205. break;
  206. case PDF_TYPE_STREAM :
  207. if ($this->encrypted) {
  208. $value[2][1] = $this->_RC4($this->_objectkey($this->_current_obj_id), $value[2][1]);
  209. }
  210. break;
  211. case PDF_TYPE_HEX :
  212. if ($this->encrypted) {
  213. $value[1] = $this->hex2str($value[1]);
  214. $value[1] = $this->_RC4($this->_objectkey($this->_current_obj_id), $value[1]);
  215. // remake hexstring of encrypted string
  216. $value[1] = $this->str2hex($value[1]);
  217. }
  218. break;
  219. }
  220. parent::pdf_write_value($value);
  221. }
  222. function hex2str($hex) {
  223. return pack("H*", str_replace(array("\r","\n"," "),"", $hex));
  224. }
  225. function str2hex($str) {
  226. return current(unpack("H*",$str));
  227. }
  228. }
  229. ?>