1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * PHP implementation of XXTEA encryption algorithm.
6 *
7 * XXTEA is a secure and fast encryption algorithm, suitable for web
8 * development.
9 *
10 * PHP versions 4 and 5
11 *
12 * LICENSE: This library is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 * MA 02110-1301 USA.
26 *
27 * @category   Encryption
28 * @package    Crypt_XXTEA
29 * @author     Wudi Liu <wudicgi@gmail.com>
30 * @author     Ma Bingyao <andot@ujn.edu.cn>
31 * @copyright  2005-2008 Coolcode.CN
32 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
33 * @version    CVS: $Id: XXTEA.php,v 1.3 2008/03/06 11:38:45 wudicgi Exp $
34 * @link       http://pear.php.net/package/Crypt_XXTEA
35 */
36
37/**
38 * Needed for error handling
39 */
40require_once 'PEAR.php';
41
42// {{{ constants
43
44define('CRYPT_XXTEA_DELTA', 0x9E3779B9);
45
46// }}}
47
48/**
49 * The main class
50 *
51 * @category   Encryption
52 * @package    Crypt_XXTEA
53 * @author     Wudi Liu <wudicgi@gmail.com>
54 * @author     Ma Bingyao <andot@ujn.edu.cn>
55 * @copyright  2005-2008 Coolcode.CN
56 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
57 * @version    Release: 0.9.0
58 * @link       http://pear.php.net/package/Crypt_XXTEA
59 */
60class Crypt_XXTEA {
61    // {{{ properties
62
63    /**
64     * The long integer array of secret key
65     *
66     * @access private
67     *
68     * @var array
69     */
70    var $_key;
71
72    // }}}
73
74    // {{{ setKey()
75
76    /**
77     * Sets the secret key
78     *
79     * The key must be non-empty, and not more than 16 characters or 4 long values
80     *
81     * @access public
82     *
83     * @param mixed $key  the secret key (string or long integer array)
84     *
85     * @return bool  true on success, PEAR_Error on failure
86     */
87    function setKey($key) {
88        if (is_string($key)) {
89            $k = $this->_str2long($key, false);
90        } elseif (is_array($key)) {
91            $k = $key;
92        } else {
93            return PEAR::raiseError('The secret key must be a string or long integer array.');
94        }
95        if (count($k) > 4) {
96            return PEAR::raiseError('The secret key cannot be more than 16 characters or 4 long values.');
97        } elseif (count($k) == 0) {
98            return PEAR::raiseError('The secret key cannot be empty.');
99        } elseif (count($k) < 4) {
100            for ($i = count($k); $i < 4; $i++) {
101                $k[$i] = 0;
102            }
103        }
104        $this->_key = $k;
105        return true;
106    }
107
108    // }}}
109
110    // {{{ encrypt()
111
112    /**
113     * Encrypts a plain text
114     *
115     * As the XXTEA encryption algorithm is designed for encrypting and decrypting
116     * the long integer array type of data, there is not a standard that defines
117     * how to convert between long integer array and text or binary data for it.
118     * So this package provides the ability to encrypt and decrypt the long integer
119     * arrays directly to satisfy the requirement for working with other
120     * implementations. And at the same time, for convenience, it also provides
121     * the ability to process strings, which uses its own method to group the text
122     * into array.
123     *
124     * @access public
125     *
126     * @param mixed $plaintext  the plain text (string or long integer array)
127     *
128     * @return mixed  the cipher text as the same type as the parameter $plaintext
129     *                on success, PEAR_Error on failure
130     */
131    function encrypt($plaintext) {
132        if ($this->_key == null) {
133            return PEAR::raiseError('Secret key is undefined.');
134        }
135        if (is_string($plaintext)) {
136            return $this->_encryptString($plaintext);
137        } elseif (is_array($plaintext)) {
138            return $this->_encryptArray($plaintext);
139        } else {
140            return PEAR::raiseError('The plain text must be a string or long integer array.');
141        }
142    }
143
144    // }}}
145
146    // {{{ decrypt()
147
148    /**
149     * Decrypts a cipher text
150     *
151     * @access public
152     *
153     * @param mixed $chipertext  the cipher text (string or long integer array)
154     *
155     * @return mixed  the plain text as the same type as the parameter $chipertext
156     *                on success, PEAR_Error on failure
157     */
158    function decrypt($chipertext) {
159        if ($this->_key == null) {
160            return PEAR::raiseError('Secret key is undefined.');
161        }
162        if (is_string($chipertext)) {
163            return $this->_decryptString($chipertext);
164        } elseif (is_array($chipertext)) {
165            return $this->_decryptArray($chipertext);
166        } else {
167            return PEAR::raiseError('The chiper text must be a string or long integer array.');
168        }
169    }
170
171    // }}}
172
173    // {{{ _encryptString()
174
175    /**
176     * Encrypts a string
177     *
178     * @access private
179     *
180     * @param string $str  the string to encrypt
181     *
182     * @return string  the string type of the cipher text on success,
183     *                 PEAR_Error on failure
184     */
185    function _encryptString($str) {
186        if ($str == '') {
187            return '';
188        }
189        $v = $this->_str2long($str, true);
190        $v = $this->_encryptArray($v);
191        return $this->_long2str($v, false);
192    }
193
194    // }}}
195
196    // {{{ _encryptArray()
197
198    /**
199     * Encrypts a long integer array
200     *
201     * @access private
202     *
203     * @param array $v  the long integer array to encrypt
204     *
205     * @return array  the array type of the cipher text on success,
206     *                PEAR_Error on failure
207     */
208    function _encryptArray($v) {
209        $n = count($v) - 1;
210        $z = $v[$n];
211        $y = $v[0];
212        $q = floor(6 + 52 / ($n + 1));
213        $sum = 0;
214        while (0 < $q--) {
215            $sum = $this->_int32($sum + CRYPT_XXTEA_DELTA);
216            $e = $sum >> 2 & 3;
217            for ($p = 0; $p < $n; $p++) {
218                $y = $v[$p + 1];
219                $mx = $this->_int32((($z >> 5 & 0x07FFFFFF) ^ $y << 2) + (($y >> 3 & 0x1FFFFFFF) ^ $z << 4)) ^ $this->_int32(($sum ^ $y) + ($this->_key[$p & 3 ^ $e] ^ $z));
220                $z = $v[$p] = $this->_int32($v[$p] + $mx);
221            }
222            $y = $v[0];
223            $mx = $this->_int32((($z >> 5 & 0x07FFFFFF) ^ $y << 2) + (($y >> 3 & 0x1FFFFFFF) ^ $z << 4)) ^ $this->_int32(($sum ^ $y) + ($this->_key[$p & 3 ^ $e] ^ $z));
224            $z = $v[$n] = $this->_int32($v[$n] + $mx);
225        }
226        return $v;
227    }
228
229    // }}}
230
231    // {{{ _decryptString()
232
233    /**
234     * Decrypts a string
235     *
236     * @access private
237     *
238     * @param string $str  the string to decrypt
239     *
240     * @return string  the string type of the plain text on success,
241     *                 PEAR_Error on failure
242     */
243    function _decryptString($str) {
244        if ($str == '') {
245            return '';
246        }
247        $v = $this->_str2long($str, false);
248        $v = $this->_decryptArray($v);
249        return $this->_long2str($v, true);
250    }
251
252    // }}}
253
254    // {{{ _encryptArray()
255
256    /**
257     * Decrypts a long integer array
258     *
259     * @access private
260     *
261     * @param array $v  the long integer array to decrypt
262     *
263     * @return array  the array type of the plain text on success,
264     *                PEAR_Error on failure
265     */
266    function _decryptArray($v) {
267        $n = count($v) - 1;
268        $z = $v[$n];
269        $y = $v[0];
270        $q = floor(6 + 52 / ($n + 1));
271        $sum = $this->_int32($q * CRYPT_XXTEA_DELTA);
272        while ($sum != 0) {
273            $e = $sum >> 2 & 3;
274            for ($p = $n; $p > 0; $p--) {
275                $z = $v[$p - 1];
276                $mx = $this->_int32((($z >> 5 & 0x07FFFFFF) ^ $y << 2) + (($y >> 3 & 0x1FFFFFFF) ^ $z << 4)) ^ $this->_int32(($sum ^ $y) + ($this->_key[$p & 3 ^ $e] ^ $z));
277                $y = $v[$p] = $this->_int32($v[$p] - $mx);
278            }
279            $z = $v[$n];
280            $mx = $this->_int32((($z >> 5 & 0x07FFFFFF) ^ $y << 2) + (($y >> 3 & 0x1FFFFFFF) ^ $z << 4)) ^ $this->_int32(($sum ^ $y) + ($this->_key[$p & 3 ^ $e] ^ $z));
281            $y = $v[0] = $this->_int32($v[0] - $mx);
282            $sum = $this->_int32($sum - CRYPT_XXTEA_DELTA);
283        }
284        return $v;
285    }
286
287    // }}}
288
289    // {{{ _long2str()
290
291    /**
292     * Converts long integer array to string
293     *
294     * @access private
295     *
296     * @param array $v  the long integer array
297     * @param bool  $w  whether the given array contains the length of
298     *                  original plain text
299     *
300     * @return string  the string
301     */
302    function _long2str($v, $w) {
303        $len = count($v);
304        $s = '';
305        for ($i = 0; $i < $len; $i++) {
306            $s .= pack('V', $v[$i]);
307        }
308        if ($w) {
309            return substr($s, 0, $v[$len - 1]);
310        } else {
311            return $s;
312        }
313    }
314
315    // }}}
316
317    // {{{ _str2long()
318
319    /**
320     * Converts string to long integer array
321     *
322     * @access private
323     *
324     * @param string $s  the string
325     * @param bool   $w  whether to append the length of string to array
326     *
327     * @return string  the long integer array
328     */
329    function _str2long($s, $w) {
330        $v = array_values(unpack('V*', $s.str_repeat("\0", (4-strlen($s)%4)&3)));
331        if ($w) {
332            $v[] = strlen($s);
333        }
334        return $v;
335    }
336
337    // }}}
338
339    // {{{ _int32()
340
341    /**
342     * Corrects long integer value
343     *
344     * Because a number beyond the bounds of the integer type will be automatically
345     * interpreted as a float, the simulation of integer overflow is needed.
346     *
347     * @access private
348     *
349     * @param int $n  the integer
350     *
351     * @return int  the correct integer
352     */
353    function _int32($n) {
354        while ($n >= 2147483648) $n -= 4294967296;
355        while ($n <= -2147483649) $n += 4294967296;
356        return (int)$n;
357    }
358
359    // }}}
360
361}
362
363?>
364