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 * @author    Jim Wigginton <terrafrost@php.net>
38 * @copyright 2007 Jim Wigginton
39 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
40 * @link      http://phpseclib.sourceforge.net
41 */
42
43namespace phpseclib3\Crypt;
44
45use phpseclib3\Crypt\Common\StreamCipher;
46
47/**
48 * Pure-PHP implementation of RC4.
49 *
50 * @author  Jim Wigginton <terrafrost@php.net>
51 */
52class RC4 extends StreamCipher
53{
54    /**
55     * @see \phpseclib3\Crypt\RC4::_crypt()
56     */
57    const ENCRYPT = 0;
58
59    /**
60     * @see \phpseclib3\Crypt\RC4::_crypt()
61     */
62    const DECRYPT = 1;
63
64    /**
65     * Key Length (in bytes)
66     *
67     * @see \phpseclib3\Crypt\RC4::setKeyLength()
68     * @var int
69     */
70    protected $key_length = 128; // = 1024 bits
71
72    /**
73     * The mcrypt specific name of the cipher
74     *
75     * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt
76     * @var string
77     */
78    protected $cipher_name_mcrypt = 'arcfour';
79
80    /**
81     * The Key
82     *
83     * @see self::setKey()
84     * @var string
85     */
86    protected $key;
87
88    /**
89     * The Key Stream for decryption and encryption
90     *
91     * @see self::setKey()
92     * @var array
93     */
94    private $stream;
95
96    /**
97     * Test for engine validity
98     *
99     * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
100     *
101     * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
102     * @param int $engine
103     * @return bool
104     */
105    protected function isValidEngineHelper($engine)
106    {
107        if ($engine == self::ENGINE_OPENSSL) {
108            if ($this->continuousBuffer) {
109                return false;
110            }
111            // quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
112            // "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
113            // in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
114            if (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
115                return false;
116            }
117            $this->cipher_name_openssl = 'rc4-40';
118        }
119
120        return parent::isValidEngineHelper($engine);
121    }
122
123    /**
124     * Sets the key length
125     *
126     * Keys can be between 1 and 256 bytes long.
127     *
128     * @param int $length
129     * @throws \LengthException if the key length is invalid
130     */
131    public function setKeyLength($length)
132    {
133        if ($length < 8 || $length > 2048) {
134            throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 256 bytes are supported');
135        }
136
137        $this->key_length = $length >> 3;
138
139        parent::setKeyLength($length);
140    }
141
142    /**
143     * Sets the key length
144     *
145     * Keys can be between 1 and 256 bytes long.
146     *
147     * @param string $key
148     */
149    public function setKey($key)
150    {
151        $length = strlen($key);
152        if ($length < 1 || $length > 256) {
153            throw new \LengthException('Key size of ' . $length . ' bytes is not supported by RC4. Keys must be between 1 and 256 bytes long');
154        }
155
156        parent::setKey($key);
157    }
158
159    /**
160     * Encrypts a message.
161     *
162     * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
163     * @see self::crypt()
164     * @param string $plaintext
165     * @return string $ciphertext
166     */
167    public function encrypt($plaintext)
168    {
169        if ($this->engine != self::ENGINE_INTERNAL) {
170            return parent::encrypt($plaintext);
171        }
172        return $this->crypt($plaintext, self::ENCRYPT);
173    }
174
175    /**
176     * Decrypts a message.
177     *
178     * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
179     * At least if the continuous buffer is disabled.
180     *
181     * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
182     * @see self::crypt()
183     * @param string $ciphertext
184     * @return string $plaintext
185     */
186    public function decrypt($ciphertext)
187    {
188        if ($this->engine != self::ENGINE_INTERNAL) {
189            return parent::decrypt($ciphertext);
190        }
191        return $this->crypt($ciphertext, self::DECRYPT);
192    }
193
194    /**
195     * Encrypts a block
196     *
197     * @param string $in
198     */
199    protected function encryptBlock($in)
200    {
201        // RC4 does not utilize this method
202    }
203
204    /**
205     * Decrypts a block
206     *
207     * @param string $in
208     */
209    protected function decryptBlock($in)
210    {
211        // RC4 does not utilize this method
212    }
213
214    /**
215     * Setup the key (expansion)
216     *
217     * @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey()
218     */
219    protected function setupKey()
220    {
221        $key = $this->key;
222        $keyLength = strlen($key);
223        $keyStream = range(0, 255);
224        $j = 0;
225        for ($i = 0; $i < 256; $i++) {
226            $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255;
227            $temp = $keyStream[$i];
228            $keyStream[$i] = $keyStream[$j];
229            $keyStream[$j] = $temp;
230        }
231
232        $this->stream = [];
233        $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = [
234            0, // index $i
235            0, // index $j
236            $keyStream
237        ];
238    }
239
240    /**
241     * Encrypts or decrypts a message.
242     *
243     * @see self::encrypt()
244     * @see self::decrypt()
245     * @param string $text
246     * @param int $mode
247     * @return string $text
248     */
249    private function crypt($text, $mode)
250    {
251        if ($this->changed) {
252            $this->setup();
253        }
254
255        $stream = &$this->stream[$mode];
256        if ($this->continuousBuffer) {
257            $i = &$stream[0];
258            $j = &$stream[1];
259            $keyStream = &$stream[2];
260        } else {
261            $i = $stream[0];
262            $j = $stream[1];
263            $keyStream = $stream[2];
264        }
265
266        $len = strlen($text);
267        for ($k = 0; $k < $len; ++$k) {
268            $i = ($i + 1) & 255;
269            $ksi = $keyStream[$i];
270            $j = ($j + $ksi) & 255;
271            $ksj = $keyStream[$j];
272
273            $keyStream[$i] = $ksj;
274            $keyStream[$j] = $ksi;
275            $text[$k] = $text[$k] ^ chr($keyStream[($ksj + $ksi) & 255]);
276        }
277
278        return $text;
279    }
280}
281