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