1<?php 2 3/** 4 * Pure-PHP implementation of Salsa20. 5 * 6 * PHP version 5 7 * 8 * @category Crypt 9 * @package Salsa20 10 * @author Jim Wigginton <terrafrost@php.net> 11 * @copyright 2019 Jim Wigginton 12 * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 * @link http://phpseclib.sourceforge.net 14 */ 15 16namespace phpseclib3\Crypt; 17 18use phpseclib3\Common\Functions\Strings; 19use phpseclib3\Crypt\Common\StreamCipher; 20use phpseclib3\Exception\BadDecryptionException; 21use phpseclib3\Exception\InsufficientSetupException; 22 23/** 24 * Pure-PHP implementation of Salsa20. 25 * 26 * @package Salsa20 27 * @author Jim Wigginton <terrafrost@php.net> 28 * @access public 29 */ 30class Salsa20 extends StreamCipher 31{ 32 /** 33 * Part 1 of the state 34 * 35 * @var string|false 36 */ 37 protected $p1 = false; 38 39 /** 40 * Part 2 of the state 41 * 42 * @var string|false 43 */ 44 protected $p2 = false; 45 46 /** 47 * Key Length (in bytes) 48 * 49 * @var int 50 */ 51 protected $key_length = 32; // = 256 bits 52 53 /** 54 * @access private 55 * @see \phpseclib3\Crypt\Salsa20::crypt() 56 */ 57 const ENCRYPT = 0; 58 59 /** 60 * @access private 61 * @see \phpseclib3\Crypt\Salsa20::crypt() 62 */ 63 const DECRYPT = 1; 64 65 /** 66 * Encryption buffer for continuous mode 67 * 68 * @var array 69 */ 70 protected $enbuffer; 71 72 /** 73 * Decryption buffer for continuous mode 74 * 75 * @var array 76 */ 77 protected $debuffer; 78 79 /** 80 * Counter 81 * 82 * @var int 83 */ 84 protected $counter = 0; 85 86 /** 87 * Using Generated Poly1305 Key 88 * 89 * @var boolean 90 */ 91 protected $usingGeneratedPoly1305Key = false; 92 93 /** 94 * Salsa20 uses a nonce 95 * 96 * @return bool 97 */ 98 public function usesNonce() 99 { 100 return true; 101 } 102 103 /** 104 * Sets the key. 105 * 106 * @param string $key 107 * @throws \LengthException if the key length isn't supported 108 */ 109 public function setKey($key) 110 { 111 switch (strlen($key)) { 112 case 16: 113 case 32: 114 break; 115 default: 116 throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported'); 117 } 118 119 parent::setKey($key); 120 } 121 122 /** 123 * Sets the nonce. 124 * 125 * @param string $nonce 126 */ 127 public function setNonce($nonce) 128 { 129 if (strlen($nonce) != 8) { 130 throw new \LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported'); 131 } 132 133 $this->nonce = $nonce; 134 $this->changed = true; 135 $this->setEngine(); 136 } 137 138 /** 139 * Sets the counter. 140 * 141 * @param int $counter 142 */ 143 public function setCounter($counter) 144 { 145 $this->counter = $counter; 146 $this->setEngine(); 147 } 148 149 /** 150 * Creates a Poly1305 key using the method discussed in RFC8439 151 * 152 * See https://tools.ietf.org/html/rfc8439#section-2.6.1 153 */ 154 protected function createPoly1305Key() 155 { 156 if ($this->nonce === false) { 157 throw new InsufficientSetupException('No nonce has been defined'); 158 } 159 160 if ($this->key === false) { 161 throw new InsufficientSetupException('No key has been defined'); 162 } 163 164 $c = clone $this; 165 $c->setCounter(0); 166 $c->usePoly1305 = false; 167 $block = $c->encrypt(str_repeat("\0", 256)); 168 $this->setPoly1305Key(substr($block, 0, 32)); 169 170 if ($this->counter == 0) { 171 $this->counter++; 172 } 173 } 174 175 /** 176 * Setup the self::ENGINE_INTERNAL $engine 177 * 178 * (re)init, if necessary, the internal cipher $engine 179 * 180 * _setup() will be called each time if $changed === true 181 * typically this happens when using one or more of following public methods: 182 * 183 * - setKey() 184 * 185 * - setNonce() 186 * 187 * - First run of encrypt() / decrypt() with no init-settings 188 * 189 * @see self::setKey() 190 * @see self::setNonce() 191 * @see self::disableContinuousBuffer() 192 */ 193 protected function setup() 194 { 195 if (!$this->changed) { 196 return; 197 } 198 199 $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter]; 200 201 $this->changed = $this->nonIVChanged = false; 202 203 if ($this->nonce === false) { 204 throw new InsufficientSetupException('No nonce has been defined'); 205 } 206 207 if ($this->key === false) { 208 throw new InsufficientSetupException('No key has been defined'); 209 } 210 211 if ($this->usePoly1305 && !isset($this->poly1305Key)) { 212 $this->usingGeneratedPoly1305Key = true; 213 $this->createPoly1305Key(); 214 } 215 216 $key = $this->key; 217 if (strlen($key) == 16) { 218 $constant = 'expand 16-byte k'; 219 $key .= $key; 220 } else { 221 $constant = 'expand 32-byte k'; 222 } 223 224 $this->p1 = substr($constant, 0, 4) . 225 substr($key, 0, 16) . 226 substr($constant, 4, 4) . 227 $this->nonce . 228 "\0\0\0\0"; 229 $this->p2 = substr($constant, 8, 4) . 230 substr($key, 16, 16) . 231 substr($constant, 12, 4); 232 } 233 234 /** 235 * Setup the key (expansion) 236 */ 237 protected function setupKey() 238 { 239 // Salsa20 does not utilize this method 240 } 241 242 /** 243 * Encrypts a message. 244 * 245 * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt() 246 * @see self::crypt() 247 * @param string $plaintext 248 * @return string $ciphertext 249 */ 250 public function encrypt($plaintext) 251 { 252 $ciphertext = $this->crypt($plaintext, self::ENCRYPT); 253 if (isset($this->poly1305Key)) { 254 $this->newtag = $this->poly1305($ciphertext); 255 } 256 return $ciphertext; 257 } 258 259 /** 260 * Decrypts a message. 261 * 262 * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). 263 * At least if the continuous buffer is disabled. 264 * 265 * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt() 266 * @see self::crypt() 267 * @param string $ciphertext 268 * @return string $plaintext 269 */ 270 public function decrypt($ciphertext) 271 { 272 if (isset($this->poly1305Key)) { 273 if ($this->oldtag === false) { 274 throw new InsufficientSetupException('Authentication Tag has not been set'); 275 } 276 $newtag = $this->poly1305($ciphertext); 277 if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) { 278 $this->oldtag = false; 279 throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match'); 280 } 281 $this->oldtag = false; 282 } 283 284 return $this->crypt($ciphertext, self::DECRYPT); 285 } 286 287 /** 288 * Encrypts a block 289 * 290 * @param string $in 291 */ 292 protected function encryptBlock($in) 293 { 294 // Salsa20 does not utilize this method 295 } 296 297 /** 298 * Decrypts a block 299 * 300 * @param string $in 301 */ 302 protected function decryptBlock($in) 303 { 304 // Salsa20 does not utilize this method 305 } 306 307 /** 308 * Encrypts or decrypts a message. 309 * 310 * @see self::encrypt() 311 * @see self::decrypt() 312 * @param string $text 313 * @param int $mode 314 * @return string $text 315 */ 316 private function crypt($text, $mode) 317 { 318 $this->setup(); 319 if (!$this->continuousBuffer) { 320 if ($this->engine == self::ENGINE_OPENSSL) { 321 $iv = pack('V', $this->counter) . $this->p2; 322 return openssl_encrypt( 323 $text, 324 $this->cipher_name_openssl, 325 $this->key, 326 OPENSSL_RAW_DATA, 327 $iv 328 ); 329 } 330 $i = $this->counter; 331 $blocks = str_split($text, 64); 332 foreach ($blocks as &$block) { 333 $block ^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2); 334 } 335 336 return implode('', $blocks); 337 } 338 339 if ($mode == self::ENCRYPT) { 340 $buffer = &$this->enbuffer; 341 } else { 342 $buffer = &$this->debuffer; 343 } 344 if (!strlen($buffer['ciphertext'])) { 345 $ciphertext = ''; 346 } else { 347 $ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text)); 348 $text = substr($text, strlen($ciphertext)); 349 if (!strlen($text)) { 350 return $ciphertext; 351 } 352 } 353 354 $overflow = strlen($text) % 64; // & 0x3F 355 if ($overflow) { 356 $text2 = Strings::pop($text, $overflow); 357 if ($this->engine == self::ENGINE_OPENSSL) { 358 $iv = pack('V', $buffer['counter']) . $this->p2; 359 // at this point $text should be a multiple of 64 360 $buffer['counter'] += (strlen($text) >> 6) + 1; // ie. divide by 64 361 $encrypted = openssl_encrypt( 362 $text . str_repeat("\0", 64), 363 $this->cipher_name_openssl, 364 $this->key, 365 OPENSSL_RAW_DATA, 366 $iv 367 ); 368 $temp = Strings::pop($encrypted, 64); 369 } else { 370 $blocks = str_split($text, 64); 371 if (strlen($text)) { 372 foreach ($blocks as &$block) { 373 $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2); 374 } 375 } 376 $encrypted = implode('', $blocks); 377 $temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2); 378 } 379 $ciphertext .= $encrypted . ($text2 ^ $temp); 380 $buffer['ciphertext'] = substr($temp, $overflow); 381 } elseif (!strlen($buffer['ciphertext'])) { 382 if ($this->engine == self::ENGINE_OPENSSL) { 383 $iv = pack('V', $buffer['counter']) . $this->p2; 384 $buffer['counter'] += (strlen($text) >> 6); 385 $ciphertext .= openssl_encrypt( 386 $text, 387 $this->cipher_name_openssl, 388 $this->key, 389 OPENSSL_RAW_DATA, 390 $iv 391 ); 392 } else { 393 $blocks = str_split($text, 64); 394 foreach ($blocks as &$block) { 395 $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2); 396 } 397 $ciphertext .= implode('', $blocks); 398 } 399 } 400 401 return $ciphertext; 402 } 403 404 /** 405 * Left Rotate 406 * 407 * @param int $x 408 * @param int $n 409 * @return int 410 */ 411 protected static function leftRotate($x, $n) 412 { 413 $r1 = $x << $n; 414 if (PHP_INT_SIZE == 8) { 415 $r1 &= 0xFFFFFFFF; 416 $r2 = ($x & 0xFFFFFFFF) >> (32 - $n); 417 } else { 418 $r2 = $x >> (32 - $n); 419 $r2 &= (1 << $n) - 1; 420 } 421 return $r1 | $r2; 422 } 423 424 /** 425 * The quarterround function 426 * 427 * @param int $a 428 * @param int $b 429 * @param int $c 430 * @param int $d 431 */ 432 protected static function quarterRound(&$a, &$b, &$c, &$d) 433 { 434 $b ^= self::leftRotate($a + $d, 7); 435 $c ^= self::leftRotate($b + $a, 9); 436 $d ^= self::leftRotate($c + $b, 13); 437 $a ^= self::leftRotate($d + $c, 18); 438 } 439 440 /** 441 * The doubleround function 442 * 443 * @param int $x0 (by reference) 444 * @param int $x1 (by reference) 445 * @param int $x2 (by reference) 446 * @param int $x3 (by reference) 447 * @param int $x4 (by reference) 448 * @param int $x5 (by reference) 449 * @param int $x6 (by reference) 450 * @param int $x7 (by reference) 451 * @param int $x8 (by reference) 452 * @param int $x9 (by reference) 453 * @param int $x10 (by reference) 454 * @param int $x11 (by reference) 455 * @param int $x12 (by reference) 456 * @param int $x13 (by reference) 457 * @param int $x14 (by reference) 458 * @param int $x15 (by reference) 459 */ 460 protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15) 461 { 462 // columnRound 463 static::quarterRound($x0, $x4, $x8, $x12); 464 static::quarterRound($x5, $x9, $x13, $x1); 465 static::quarterRound($x10, $x14, $x2, $x6); 466 static::quarterRound($x15, $x3, $x7, $x11); 467 // rowRound 468 static::quarterRound($x0, $x1, $x2, $x3); 469 static::quarterRound($x5, $x6, $x7, $x4); 470 static::quarterRound($x10, $x11, $x8, $x9); 471 static::quarterRound($x15, $x12, $x13, $x14); 472 } 473 474 /** 475 * The Salsa20 hash function function 476 * 477 * @param string $x 478 */ 479 protected static function salsa20($x) 480 { 481 $z = $x = unpack('V*', $x); 482 for ($i = 0; $i < 10; $i++) { 483 static::doubleRound($z[1], $z[2], $z[3], $z[4], $z[5], $z[6], $z[7], $z[8], $z[9], $z[10], $z[11], $z[12], $z[13], $z[14], $z[15], $z[16]); 484 } 485 486 for ($i = 1; $i <= 16; $i++) { 487 $x[$i] += $z[$i]; 488 } 489 490 return pack('V*', ...$x); 491 } 492 493 /** 494 * Calculates Poly1305 MAC 495 * 496 * @see self::decrypt() 497 * @see self::encrypt() 498 * @access private 499 * @param string $ciphertext 500 * @return string 501 */ 502 protected function poly1305($ciphertext) 503 { 504 if (!$this->usingGeneratedPoly1305Key) { 505 return parent::poly1305($this->aad . $ciphertext); 506 } else { 507 /* 508 sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag 509 the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see 510 how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts 511 it: 512 513 $this->newtag = $this->poly1305( 514 $this->aad . 515 pack('V', strlen($this->aad)) . "\0\0\0\0" . 516 $ciphertext . 517 pack('V', strlen($ciphertext)) . "\0\0\0\0" 518 ); 519 520 phpseclib opts to use the IETF construction, even when the nonce is 64-bits 521 instead of 96-bits 522 */ 523 return parent::poly1305( 524 self::nullPad128($this->aad) . 525 self::nullPad128($ciphertext) . 526 pack('V', strlen($this->aad)) . "\0\0\0\0" . 527 pack('V', strlen($ciphertext)) . "\0\0\0\0" 528 ); 529 } 530 } 531} 532