Subversion Repositories oidplus

Rev

Rev 846 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  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.  
  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.  
  23. /**
  24.  * Pure-PHP implementation of Salsa20.
  25.  *
  26.  * @package Salsa20
  27.  * @author  Jim Wigginton <terrafrost@php.net>
  28.  * @access  public
  29.  */
  30. class 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.