1<?php
2
3/**
4 * Pure-PHP implementation of RC4.
5 *
6 * Uses mcrypt, if available, and an internal implementation, otherwise.
7 *
8 * PHP version 5
9 *
10 * Useful resources are as follows:
11 *
12 *  - {@link http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt ARCFOUR Algorithm}
13 *  - {@link http://en.wikipedia.org/wiki/RC4 - Wikipedia: RC4}
14 *
15 * RC4 is also known as ARCFOUR or ARC4.  The reason is elaborated upon at Wikipedia.  This class is named RC4 and not
16 * ARCFOUR or ARC4 because RC4 is how it is referred to in the SSH1 specification.
17 *
18 * Here's a short example of how to use this library:
19 * <code>
20 * <?php
21 *    include 'vendor/autoload.php';
22 *
23 *    $rc4 = new \phpseclib3\Crypt\RC4();
24 *
25 *    $rc4->setKey('abcdefgh');
26 *
27 *    $size = 10 * 1024;
28 *    $plaintext = '';
29 *    for ($i = 0; $i < $size; $i++) {
30 *        $plaintext.= 'a';
31 *    }
32 *
33 *    echo $rc4->decrypt($rc4->encrypt($plaintext));
34 * ?>
35 * </code>
36 *
37 * @category  Crypt
38 * @package   RC4
39 * @author    Jim Wigginton <terrafrost@php.net>
40 * @copyright 2007 Jim Wigginton
41 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
42 * @link      http://phpseclib.sourceforge.net
43 */
44
45namespace phpseclib3\Crypt;
46
47use phpseclib3\Crypt\Common\StreamCipher;
48
49/**
50 * Pure-PHP implementation of RC4.
51 *
52 * @package RC4
53 * @author  Jim Wigginton <terrafrost@php.net>
54 * @access  public
55 */
56class RC4 extends StreamCipher
57{
58    /**
59     * @access private
60     * @see \phpseclib3\Crypt\RC4::_crypt()
61     */
62    const ENCRYPT = 0;
63
64    /**
65     * @access private
66     * @see \phpseclib3\Crypt\RC4::_crypt()
67     */
68    const DECRYPT = 1;
69
70    /**
71     * Key Length (in bytes)
72     *
73     * @see \phpseclib3\Crypt\RC4::setKeyLength()
74     * @var int
75     * @access private
76     */
77    protected $key_length = 128; // = 1024 bits
78
79    /**
80     * The mcrypt specific name of the cipher
81     *
82     * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt
83     * @var string
84     * @access private
85     */
86    protected $cipher_name_mcrypt = 'arcfour';
87
88    /**
89     * The Key
90     *
91     * @see self::setKey()
92     * @var string
93     * @access private
94     */
95    protected $key;
96
97    /**
98     * The Key Stream for decryption and encryption
99     *
100     * @see self::setKey()
101     * @var array
102     * @access private
103     */
104    private $stream;
105
106    /**
107     * Test for engine validity
108     *
109     * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
110     *
111     * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
112     * @param int $engine
113     * @access protected
114     * @return bool
115     */
116    protected function isValidEngineHelper($engine)
117    {
118        if ($engine == self::ENGINE_OPENSSL) {
119            if ($this->continuousBuffer) {
120                return false;
121            }
122            if (version_compare(PHP_VERSION, '5.3.7') >= 0) {
123                $this->cipher_name_openssl = 'rc4-40';
124            } else {
125                switch (strlen($this->key)) {
126                    case 5:
127                        $this->cipher_name_openssl = 'rc4-40';
128                        break;
129                    case 8:
130                        $this->cipher_name_openssl = 'rc4-64';
131                        break;
132                    case 16:
133                        $this->cipher_name_openssl = 'rc4';
134                        break;
135                    default:
136                        return false;
137                }
138            }
139        }
140
141        return parent::isValidEngineHelper($engine);
142    }
143
144    /**
145     * Sets the key length
146     *
147     * Keys can be between 1 and 256 bytes long.
148     *
149     * @access public
150     * @param int $length
151     * @throws \LengthException if the key length is invalid
152     */
153    public function setKeyLength($length)
154    {
155        if ($length < 8 || $length > 2048) {
156            throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 256 bytes are supported');
157        }
158
159        $this->key_length = $length >> 3;
160
161        parent::setKeyLength($length);
162    }
163
164    /**
165     * Sets the key length
166     *
167     * Keys can be between 1 and 256 bytes long.
168     *
169     * @access public
170     * @param string $key
171     */
172    public function setKey($key)
173    {
174        $length = strlen($key);
175        if ($length < 1 || $length > 256) {
176            throw new \LengthException('Key size of ' . $length . ' bytes is not supported by RC4. Keys must be between 1 and 256 bytes long');
177        }
178
179        parent::setKey($key);
180    }
181
182    /**
183     * Encrypts a message.
184     *
185     * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
186     * @see self::crypt()
187     * @access public
188     * @param string $plaintext
189     * @return string $ciphertext
190     */
191    public function encrypt($plaintext)
192    {
193        if ($this->engine != self::ENGINE_INTERNAL) {
194            return parent::encrypt($plaintext);
195        }
196        return $this->crypt($plaintext, self::ENCRYPT);
197    }
198
199    /**
200     * Decrypts a message.
201     *
202     * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
203     * At least if the continuous buffer is disabled.
204     *
205     * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
206     * @see self::crypt()
207     * @access public
208     * @param string $ciphertext
209     * @return string $plaintext
210     */
211    public function decrypt($ciphertext)
212    {
213        if ($this->engine != self::ENGINE_INTERNAL) {
214            return parent::decrypt($ciphertext);
215        }
216        return $this->crypt($ciphertext, self::DECRYPT);
217    }
218
219    /**
220     * Encrypts a block
221     *
222     * @access private
223     * @param string $in
224     */
225    protected function encryptBlock($in)
226    {
227        // RC4 does not utilize this method
228    }
229
230    /**
231     * Decrypts a block
232     *
233     * @access private
234     * @param string $in
235     */
236    protected function decryptBlock($in)
237    {
238        // RC4 does not utilize this method
239    }
240
241    /**
242     * Setup the key (expansion)
243     *
244     * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey()
245     * @access private
246     */
247    protected function setupKey()
248    {
249        $key = $this->key;
250        $keyLength = strlen($key);
251        $keyStream = range(0, 255);
252        $j = 0;
253        for ($i = 0; $i < 256; $i++) {
254            $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255;
255            $temp = $keyStream[$i];
256            $keyStream[$i] = $keyStream[$j];
257            $keyStream[$j] = $temp;
258        }
259
260        $this->stream = [];
261        $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = [
262            0, // index $i
263            0, // index $j
264            $keyStream
265        ];
266    }
267
268    /**
269     * Encrypts or decrypts a message.
270     *
271     * @see self::encrypt()
272     * @see self::decrypt()
273     * @access private
274     * @param string $text
275     * @param int $mode
276     * @return string $text
277     */
278    private function crypt($text, $mode)
279    {
280        if ($this->changed) {
281            $this->setup();
282        }
283
284        $stream = &$this->stream[$mode];
285        if ($this->continuousBuffer) {
286            $i = &$stream[0];
287            $j = &$stream[1];
288            $keyStream = &$stream[2];
289        } else {
290            $i = $stream[0];
291            $j = $stream[1];
292            $keyStream = $stream[2];
293        }
294
295        $len = strlen($text);
296        for ($k = 0; $k < $len; ++$k) {
297            $i = ($i + 1) & 255;
298            $ksi = $keyStream[$i];
299            $j = ($j + $ksi) & 255;
300            $ksj = $keyStream[$j];
301
302            $keyStream[$i] = $ksj;
303            $keyStream[$j] = $ksi;
304            $text[$k] = $text[$k] ^ chr($keyStream[($ksj + $ksi) & 255]);
305        }
306
307        return $text;
308    }
309}
310