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