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