[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Crypt/ -> Salsa20.php (source)

   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  }