]> git.mxchange.org Git - friendica.git/blob - phpsec/Crypt/AES.php
do a better job of comparing same URLs.
[friendica.git] / phpsec / Crypt / AES.php
1 <?php\r
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */\r
3 \r
4 /**\r
5  * Pure-PHP implementation of AES.\r
6  *\r
7  * Uses mcrypt, if available, and an internal implementation, otherwise.\r
8  *\r
9  * PHP versions 4 and 5\r
10  *\r
11  * If {@link Crypt_AES::setKeyLength() setKeyLength()} isn't called, it'll be calculated from\r
12  * {@link Crypt_AES::setKey() setKey()}.  ie. if the key is 128-bits, the key length will be 128-bits.  If it's 136-bits\r
13  * it'll be null-padded to 160-bits and 160 bits will be the key length until {@link Crypt_Rijndael::setKey() setKey()}\r
14  * is called, again, at which point, it'll be recalculated.\r
15  *\r
16  * Since Crypt_AES extends Crypt_Rijndael, some functions are available to be called that, in the context of AES, don't\r
17  * make a whole lot of sense.  {@link Crypt_AES::setBlockLength() setBlockLength()}, for instance.  Calling that function,\r
18  * however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).\r
19  *\r
20  * Here's a short example of how to use this library:\r
21  * <code>\r
22  * <?php\r
23  *    include('Crypt/AES.php');\r
24  *\r
25  *    $aes = new Crypt_AES();\r
26  *\r
27  *    $aes->setKey('abcdefghijklmnop');\r
28  *\r
29  *    $size = 10 * 1024;\r
30  *    $plaintext = '';\r
31  *    for ($i = 0; $i < $size; $i++) {\r
32  *        $plaintext.= 'a';\r
33  *    }\r
34  *\r
35  *    echo $aes->decrypt($aes->encrypt($plaintext));\r
36  * ?>\r
37  * </code>\r
38  *\r
39  * LICENSE: This library is free software; you can redistribute it and/or\r
40  * modify it under the terms of the GNU Lesser General Public\r
41  * License as published by the Free Software Foundation; either\r
42  * version 2.1 of the License, or (at your option) any later version.\r
43  *\r
44  * This library is distributed in the hope that it will be useful,\r
45  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
47  * Lesser General Public License for more details.\r
48  *\r
49  * You should have received a copy of the GNU Lesser General Public\r
50  * License along with this library; if not, write to the Free Software\r
51  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,\r
52  * MA  02111-1307  USA\r
53  *\r
54  * @category   Crypt\r
55  * @package    Crypt_AES\r
56  * @author     Jim Wigginton <terrafrost@php.net>\r
57  * @copyright  MMVIII Jim Wigginton\r
58  * @license    http://www.gnu.org/licenses/lgpl.txt\r
59  * @version    $Id: AES.php,v 1.7 2010/02/09 06:10:25 terrafrost Exp $\r
60  * @link       http://phpseclib.sourceforge.net\r
61  */\r
62 \r
63 /**\r
64  * Include Crypt_Rijndael\r
65  */\r
66 require_once 'Rijndael.php';\r
67 \r
68 /**#@+\r
69  * @access public\r
70  * @see Crypt_AES::encrypt()\r
71  * @see Crypt_AES::decrypt()\r
72  */\r
73 /**\r
74  * Encrypt / decrypt using the Counter mode.\r
75  *\r
76  * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.\r
77  *\r
78  * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29\r
79  */\r
80 define('CRYPT_AES_MODE_CTR', -1);\r
81 /**\r
82  * Encrypt / decrypt using the Electronic Code Book mode.\r
83  *\r
84  * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29\r
85  */\r
86 define('CRYPT_AES_MODE_ECB', 1);\r
87 /**\r
88  * Encrypt / decrypt using the Code Book Chaining mode.\r
89  *\r
90  * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29\r
91  */\r
92 define('CRYPT_AES_MODE_CBC', 2);\r
93 /**#@-*/\r
94 \r
95 /**#@+\r
96  * @access private\r
97  * @see Crypt_AES::Crypt_AES()\r
98  */\r
99 /**\r
100  * Toggles the internal implementation\r
101  */\r
102 define('CRYPT_AES_MODE_INTERNAL', 1);\r
103 /**\r
104  * Toggles the mcrypt implementation\r
105  */\r
106 define('CRYPT_AES_MODE_MCRYPT', 2);\r
107 /**#@-*/\r
108 \r
109 /**\r
110  * Pure-PHP implementation of AES.\r
111  *\r
112  * @author  Jim Wigginton <terrafrost@php.net>\r
113  * @version 0.1.0\r
114  * @access  public\r
115  * @package Crypt_AES\r
116  */\r
117 class Crypt_AES extends Crypt_Rijndael {\r
118     /**\r
119      * mcrypt resource for encryption\r
120      *\r
121      * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.\r
122      * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.\r
123      *\r
124      * @see Crypt_AES::encrypt()\r
125      * @var String\r
126      * @access private\r
127      */\r
128     var $enmcrypt;\r
129 \r
130     /**\r
131      * mcrypt resource for decryption\r
132      *\r
133      * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.\r
134      * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.\r
135      *\r
136      * @see Crypt_AES::decrypt()\r
137      * @var String\r
138      * @access private\r
139      */\r
140     var $demcrypt;\r
141 \r
142     /**\r
143      * Default Constructor.\r
144      *\r
145      * Determines whether or not the mcrypt extension should be used.  $mode should only, at present, be\r
146      * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC.  If not explictly set, CRYPT_AES_MODE_CBC will be used.\r
147      *\r
148      * @param optional Integer $mode\r
149      * @return Crypt_AES\r
150      * @access public\r
151      */\r
152     function Crypt_AES($mode = CRYPT_AES_MODE_CBC)\r
153     {\r
154         if ( !defined('CRYPT_AES_MODE') ) {\r
155             switch (true) {\r
156                 case extension_loaded('mcrypt'):\r
157                     // i'd check to see if aes was supported, by doing in_array('des', mcrypt_list_algorithms('')),\r
158                     // but since that can be changed after the object has been created, there doesn't seem to be\r
159                     // a lot of point...\r
160                     define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT);\r
161                     break;\r
162                 default:\r
163                     define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL);\r
164             }\r
165         }\r
166 \r
167         switch ( CRYPT_AES_MODE ) {\r
168             case CRYPT_AES_MODE_MCRYPT:\r
169                 switch ($mode) {\r
170                     case CRYPT_AES_MODE_ECB:\r
171                         $this->mode = MCRYPT_MODE_ECB;\r
172                         break;\r
173                     case CRYPT_AES_MODE_CTR:\r
174                         // ctr doesn't have a constant associated with it even though it appears to be fairly widely\r
175                         // supported.  in lieu of knowing just how widely supported it is, i've, for now, opted not to\r
176                         // include a compatibility layer.  the layer has been implemented but, for now, is commented out.\r
177                         $this->mode = 'ctr';\r
178                         //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_AES_MODE_CTR;\r
179                         break;\r
180                     case CRYPT_AES_MODE_CBC:\r
181                     default:\r
182                         $this->mode = MCRYPT_MODE_CBC;\r
183                 }\r
184 \r
185                 break;\r
186             default:\r
187                 switch ($mode) {\r
188                     case CRYPT_AES_MODE_ECB:\r
189                         $this->mode = CRYPT_RIJNDAEL_MODE_ECB;\r
190                         break;\r
191                     case CRYPT_AES_MODE_CTR:\r
192                         $this->mode = CRYPT_RIJNDAEL_MODE_CTR;\r
193                         break;\r
194                     case CRYPT_AES_MODE_CBC:\r
195                     default:\r
196                         $this->mode = CRYPT_RIJNDAEL_MODE_CBC;\r
197                 }\r
198         }\r
199 \r
200         if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) {\r
201             parent::Crypt_Rijndael($this->mode);\r
202         }\r
203     }\r
204 \r
205     /**\r
206      * Dummy function\r
207      *\r
208      * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything.\r
209      *\r
210      * @access public\r
211      * @param Integer $length\r
212      */\r
213     function setBlockLength($length)\r
214     {\r
215         return;\r
216     }\r
217 \r
218     /**\r
219      * Encrypts a message.\r
220      *\r
221      * $plaintext will be padded with up to 16 additional bytes.  Other AES implementations may or may not pad in the\r
222      * same manner.  Other common approaches to padding and the reasons why it's necessary are discussed in the following\r
223      * URL:\r
224      *\r
225      * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}\r
226      *\r
227      * An alternative to padding is to, separately, send the length of the file.  This is what SSH, in fact, does.\r
228      * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that\r
229      * length.\r
230      *\r
231      * @see Crypt_AES::decrypt()\r
232      * @access public\r
233      * @param String $plaintext\r
234      */\r
235     function encrypt($plaintext)\r
236     {\r
237         if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) {\r
238             $this->_mcryptSetup();\r
239             /*\r
240             if ($this->mode == CRYPT_AES_MODE_CTR) {\r
241                 $iv = $this->encryptIV;\r
242                 $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($plaintext), $iv));\r
243                 $ciphertext = $plaintext ^ $xor;\r
244                 if ($this->continuousBuffer) {\r
245                     $this->encryptIV = $iv;\r
246                 }\r
247                 return $ciphertext;\r
248             }\r
249             */\r
250 \r
251             if ($this->mode != 'ctr') {\r
252                 $plaintext = $this->_pad($plaintext);\r
253             }\r
254 \r
255             $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext);\r
256 \r
257             if (!$this->continuousBuffer) {\r
258                 mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv);\r
259             }\r
260 \r
261             return $ciphertext;\r
262         }\r
263 \r
264         return parent::encrypt($plaintext);\r
265     }\r
266 \r
267     /**\r
268      * Decrypts a message.\r
269      *\r
270      * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is.\r
271      *\r
272      * @see Crypt_AES::encrypt()\r
273      * @access public\r
274      * @param String $ciphertext\r
275      */\r
276     function decrypt($ciphertext)\r
277     {\r
278         if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) {\r
279             $this->_mcryptSetup();\r
280             /*\r
281             if ($this->mode == CRYPT_AES_MODE_CTR) {\r
282                 $iv = $this->decryptIV;\r
283                 $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($ciphertext), $iv));\r
284                 $plaintext = $ciphertext ^ $xor;\r
285                 if ($this->continuousBuffer) {\r
286                     $this->decryptIV = $iv;\r
287                 }\r
288                 return $plaintext;\r
289             }\r
290             */\r
291 \r
292             if ($this->mode != 'ctr') {\r
293                 // we pad with chr(0) since that's what mcrypt_generic does.  to quote from http://php.net/function.mcrypt-generic :\r
294                 // "The data is padded with "\0" to make sure the length of the data is n * blocksize."\r
295                 $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0));\r
296             }\r
297 \r
298             $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext);\r
299 \r
300             if (!$this->continuousBuffer) {\r
301                 mcrypt_generic_init($this->demcrypt, $this->key, $this->iv);\r
302             }\r
303 \r
304             return $this->mode != 'ctr' ? $this->_unpad($plaintext) : $plaintext;\r
305         }\r
306 \r
307         return parent::decrypt($ciphertext);\r
308     }\r
309 \r
310     /**\r
311      * Setup mcrypt\r
312      *\r
313      * Validates all the variables.\r
314      *\r
315      * @access private\r
316      */\r
317     function _mcryptSetup()\r
318     {\r
319         if (!$this->changed) {\r
320             return;\r
321         }\r
322 \r
323         if (!$this->explicit_key_length) {\r
324             // this just copied from Crypt_Rijndael::_setup()\r
325             $length = strlen($this->key) >> 2;\r
326             if ($length > 8) {\r
327                 $length = 8;\r
328             } else if ($length < 4) {\r
329                 $length = 4;\r
330             }\r
331             $this->Nk = $length;\r
332             $this->key_size = $length << 2;\r
333         }\r
334 \r
335         switch ($this->Nk) {\r
336             case 4: // 128\r
337                 $this->key_size = 16;\r
338                 break;\r
339             case 5: // 160\r
340             case 6: // 192\r
341                 $this->key_size = 24;\r
342                 break;\r
343             case 7: // 224\r
344             case 8: // 256\r
345                 $this->key_size = 32;\r
346         }\r
347 \r
348         $this->key = substr($this->key, 0, $this->key_size);\r
349         $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0));\r
350 \r
351         if (!isset($this->enmcrypt)) {\r
352             $mode = $this->mode;\r
353             //$mode = $this->mode == CRYPT_AES_MODE_CTR ? MCRYPT_MODE_ECB : $this->mode;\r
354 \r
355             $this->demcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, '');\r
356             $this->enmcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, '');\r
357         } // else should mcrypt_generic_deinit be called?\r
358 \r
359         mcrypt_generic_init($this->demcrypt, $this->key, $this->iv);\r
360         mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv);\r
361 \r
362         $this->changed = false;\r
363     }\r
364 \r
365     /**\r
366      * Encrypts a block\r
367      *\r
368      * Optimized over Crypt_Rijndael's implementation by means of loop unrolling.\r
369      *\r
370      * @see Crypt_Rijndael::_encryptBlock()\r
371      * @access private\r
372      * @param String $in\r
373      * @return String\r
374      */\r
375     function _encryptBlock($in)\r
376     {\r
377         $state = unpack('N*word', $in);\r
378 \r
379         $Nr = $this->Nr;\r
380         $w = $this->w;\r
381         $t0 = $this->t0;\r
382         $t1 = $this->t1;\r
383         $t2 = $this->t2;\r
384         $t3 = $this->t3;\r
385 \r
386         // addRoundKey and reindex $state\r
387         $state = array(\r
388             $state['word1'] ^ $w[0][0],\r
389             $state['word2'] ^ $w[0][1],\r
390             $state['word3'] ^ $w[0][2],\r
391             $state['word4'] ^ $w[0][3]\r
392         );\r
393 \r
394         // shiftRows + subWord + mixColumns + addRoundKey\r
395         // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields\r
396         // only a marginal improvement.  since that also, imho, hinders the readability of the code, i've opted not to do it.\r
397         for ($round = 1; $round < $this->Nr; $round++) {\r
398             $state = array(\r
399                 $t0[$state[0] & 0xFF000000] ^ $t1[$state[1] & 0x00FF0000] ^ $t2[$state[2] & 0x0000FF00] ^ $t3[$state[3] & 0x000000FF] ^ $w[$round][0],\r
400                 $t0[$state[1] & 0xFF000000] ^ $t1[$state[2] & 0x00FF0000] ^ $t2[$state[3] & 0x0000FF00] ^ $t3[$state[0] & 0x000000FF] ^ $w[$round][1],\r
401                 $t0[$state[2] & 0xFF000000] ^ $t1[$state[3] & 0x00FF0000] ^ $t2[$state[0] & 0x0000FF00] ^ $t3[$state[1] & 0x000000FF] ^ $w[$round][2],\r
402                 $t0[$state[3] & 0xFF000000] ^ $t1[$state[0] & 0x00FF0000] ^ $t2[$state[1] & 0x0000FF00] ^ $t3[$state[2] & 0x000000FF] ^ $w[$round][3]\r
403             );\r
404 \r
405         }\r
406 \r
407         // subWord\r
408         $state = array(\r
409             $this->_subWord($state[0]),\r
410             $this->_subWord($state[1]),\r
411             $this->_subWord($state[2]),\r
412             $this->_subWord($state[3])\r
413         );\r
414 \r
415         // shiftRows + addRoundKey\r
416         $state = array(\r
417             ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0],\r
418             ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1],\r
419             ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2],\r
420             ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3]\r
421         );\r
422 \r
423         return pack('N*', $state[0], $state[1], $state[2], $state[3]);\r
424     }\r
425 \r
426     /**\r
427      * Decrypts a block\r
428      *\r
429      * Optimized over Crypt_Rijndael's implementation by means of loop unrolling.\r
430      *\r
431      * @see Crypt_Rijndael::_decryptBlock()\r
432      * @access private\r
433      * @param String $in\r
434      * @return String\r
435      */\r
436     function _decryptBlock($in)\r
437     {\r
438         $state = unpack('N*word', $in);\r
439 \r
440         $Nr = $this->Nr;\r
441         $dw = $this->dw;\r
442         $dt0 = $this->dt0;\r
443         $dt1 = $this->dt1;\r
444         $dt2 = $this->dt2;\r
445         $dt3 = $this->dt3;\r
446 \r
447         // addRoundKey and reindex $state\r
448         $state = array(\r
449             $state['word1'] ^ $dw[$this->Nr][0],\r
450             $state['word2'] ^ $dw[$this->Nr][1],\r
451             $state['word3'] ^ $dw[$this->Nr][2],\r
452             $state['word4'] ^ $dw[$this->Nr][3]\r
453         );\r
454 \r
455 \r
456         // invShiftRows + invSubBytes + invMixColumns + addRoundKey\r
457         for ($round = $this->Nr - 1; $round > 0; $round--) {\r
458             $state = array(\r
459                 $dt0[$state[0] & 0xFF000000] ^ $dt1[$state[3] & 0x00FF0000] ^ $dt2[$state[2] & 0x0000FF00] ^ $dt3[$state[1] & 0x000000FF] ^ $dw[$round][0],\r
460                 $dt0[$state[1] & 0xFF000000] ^ $dt1[$state[0] & 0x00FF0000] ^ $dt2[$state[3] & 0x0000FF00] ^ $dt3[$state[2] & 0x000000FF] ^ $dw[$round][1],\r
461                 $dt0[$state[2] & 0xFF000000] ^ $dt1[$state[1] & 0x00FF0000] ^ $dt2[$state[0] & 0x0000FF00] ^ $dt3[$state[3] & 0x000000FF] ^ $dw[$round][2],\r
462                 $dt0[$state[3] & 0xFF000000] ^ $dt1[$state[2] & 0x00FF0000] ^ $dt2[$state[1] & 0x0000FF00] ^ $dt3[$state[0] & 0x000000FF] ^ $dw[$round][3]\r
463             );\r
464         }\r
465 \r
466         // invShiftRows + invSubWord + addRoundKey\r
467         $state = array(\r
468             $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $dw[0][0],\r
469             $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $dw[0][1],\r
470             $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $dw[0][2],\r
471             $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $dw[0][3]\r
472         );\r
473 \r
474         return pack('N*', $state[0], $state[1], $state[2], $state[3]);\r
475     }\r
476 }\r
477 \r
478 // vim: ts=4:sw=4:et:\r
479 // vim6: fdl=1: