Viel neues
This commit is contained in:
503
qa-tool/htdocs/oidc/phpseclib/Crypt/Salsa20.php
Normal file
503
qa-tool/htdocs/oidc/phpseclib/Crypt/Salsa20.php
Normal file
@@ -0,0 +1,503 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Pure-PHP implementation of Salsa20.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
* @copyright 2019 Jim Wigginton
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @link http://phpseclib.sourceforge.net
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpseclib3\Crypt;
|
||||
|
||||
use phpseclib3\Common\Functions\Strings;
|
||||
use phpseclib3\Crypt\Common\StreamCipher;
|
||||
use phpseclib3\Exception\BadDecryptionException;
|
||||
use phpseclib3\Exception\InsufficientSetupException;
|
||||
use phpseclib3\Exception\LengthException;
|
||||
|
||||
/**
|
||||
* Pure-PHP implementation of Salsa20.
|
||||
*
|
||||
* @author Jim Wigginton <terrafrost@php.net>
|
||||
*/
|
||||
class Salsa20 extends StreamCipher
|
||||
{
|
||||
/**
|
||||
* Part 1 of the state
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
protected $p1 = false;
|
||||
|
||||
/**
|
||||
* Part 2 of the state
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
protected $p2 = false;
|
||||
|
||||
/**
|
||||
* Key Length (in bytes)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $key_length = 32; // = 256 bits
|
||||
|
||||
/**
|
||||
* @see \phpseclib3\Crypt\Salsa20::crypt()
|
||||
*/
|
||||
public const ENCRYPT = 0;
|
||||
|
||||
/**
|
||||
* @see \phpseclib3\Crypt\Salsa20::crypt()
|
||||
*/
|
||||
public const DECRYPT = 1;
|
||||
|
||||
/**
|
||||
* Encryption buffer for continuous mode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enbuffer;
|
||||
|
||||
/**
|
||||
* Decryption buffer for continuous mode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $debuffer;
|
||||
|
||||
/**
|
||||
* Counter
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $counter = 0;
|
||||
|
||||
/**
|
||||
* Using Generated Poly1305 Key
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $usingGeneratedPoly1305Key = false;
|
||||
|
||||
/**
|
||||
* Salsa20 uses a nonce
|
||||
*/
|
||||
public function usesNonce(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key.
|
||||
*
|
||||
* @throws LengthException if the key length isn't supported
|
||||
*/
|
||||
public function setKey(string $key): void
|
||||
{
|
||||
switch (strlen($key)) {
|
||||
case 16:
|
||||
case 32:
|
||||
break;
|
||||
default:
|
||||
throw new LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported');
|
||||
}
|
||||
|
||||
parent::setKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nonce.
|
||||
*/
|
||||
public function setNonce(string $nonce): void
|
||||
{
|
||||
if (strlen($nonce) != 8) {
|
||||
throw new LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported');
|
||||
}
|
||||
|
||||
$this->nonce = $nonce;
|
||||
$this->changed = true;
|
||||
$this->setEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the counter.
|
||||
*/
|
||||
public function setCounter(int $counter): void
|
||||
{
|
||||
$this->counter = $counter;
|
||||
$this->setEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Poly1305 key using the method discussed in RFC8439
|
||||
*
|
||||
* See https://tools.ietf.org/html/rfc8439#section-2.6.1
|
||||
*/
|
||||
protected function createPoly1305Key(): void
|
||||
{
|
||||
if ($this->nonce === false) {
|
||||
throw new InsufficientSetupException('No nonce has been defined');
|
||||
}
|
||||
|
||||
if ($this->key === false) {
|
||||
throw new InsufficientSetupException('No key has been defined');
|
||||
}
|
||||
|
||||
$c = clone $this;
|
||||
$c->setCounter(0);
|
||||
$c->usePoly1305 = false;
|
||||
$block = $c->encrypt(str_repeat("\0", 256));
|
||||
$this->setPoly1305Key(substr($block, 0, 32));
|
||||
|
||||
if ($this->counter == 0) {
|
||||
$this->counter++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the self::ENGINE_INTERNAL $engine
|
||||
*
|
||||
* (re)init, if necessary, the internal cipher $engine
|
||||
*
|
||||
* _setup() will be called each time if $changed === true
|
||||
* typically this happens when using one or more of following public methods:
|
||||
*
|
||||
* - setKey()
|
||||
*
|
||||
* - setNonce()
|
||||
*
|
||||
* - First run of encrypt() / decrypt() with no init-settings
|
||||
*
|
||||
* @see self::setKey()
|
||||
* @see self::setNonce()
|
||||
* @see self::disableContinuousBuffer()
|
||||
*/
|
||||
protected function setup(): void
|
||||
{
|
||||
if (!$this->changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
|
||||
|
||||
$this->changed = $this->nonIVChanged = false;
|
||||
|
||||
if ($this->nonce === false) {
|
||||
throw new InsufficientSetupException('No nonce has been defined');
|
||||
}
|
||||
|
||||
if ($this->key === false) {
|
||||
throw new InsufficientSetupException('No key has been defined');
|
||||
}
|
||||
|
||||
if ($this->usePoly1305 && !isset($this->poly1305Key)) {
|
||||
$this->usingGeneratedPoly1305Key = true;
|
||||
$this->createPoly1305Key();
|
||||
}
|
||||
|
||||
$key = $this->key;
|
||||
if (strlen($key) == 16) {
|
||||
$constant = 'expand 16-byte k';
|
||||
$key .= $key;
|
||||
} else {
|
||||
$constant = 'expand 32-byte k';
|
||||
}
|
||||
|
||||
$this->p1 = substr($constant, 0, 4) .
|
||||
substr($key, 0, 16) .
|
||||
substr($constant, 4, 4) .
|
||||
$this->nonce .
|
||||
"\0\0\0\0";
|
||||
$this->p2 = substr($constant, 8, 4) .
|
||||
substr($key, 16, 16) .
|
||||
substr($constant, 12, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the key (expansion)
|
||||
*/
|
||||
protected function setupKey(): void
|
||||
{
|
||||
// Salsa20 does not utilize this method
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a message.
|
||||
*
|
||||
* @return string $ciphertext
|
||||
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
|
||||
* @see self::crypt()
|
||||
*/
|
||||
public function encrypt(string $plaintext): string
|
||||
{
|
||||
$ciphertext = $this->crypt($plaintext, self::ENCRYPT);
|
||||
if (isset($this->poly1305Key)) {
|
||||
$this->newtag = $this->poly1305($ciphertext);
|
||||
}
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a message.
|
||||
*
|
||||
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
|
||||
* At least if the continuous buffer is disabled.
|
||||
*
|
||||
* @return string $plaintext
|
||||
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
|
||||
* @see self::crypt()
|
||||
*/
|
||||
public function decrypt(string $ciphertext): string
|
||||
{
|
||||
if (isset($this->poly1305Key)) {
|
||||
if ($this->oldtag === false) {
|
||||
throw new InsufficientSetupException('Authentication Tag has not been set');
|
||||
}
|
||||
$newtag = $this->poly1305($ciphertext);
|
||||
if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
|
||||
$this->oldtag = false;
|
||||
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
|
||||
}
|
||||
$this->oldtag = false;
|
||||
}
|
||||
|
||||
return $this->crypt($ciphertext, self::DECRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a block
|
||||
*/
|
||||
protected function encryptBlock(string $in): string
|
||||
{
|
||||
// Salsa20 does not utilize this method
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a block
|
||||
*/
|
||||
protected function decryptBlock(string $in): string
|
||||
{
|
||||
// Salsa20 does not utilize this method
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts a message.
|
||||
*
|
||||
* @return string $text
|
||||
* @see self::decrypt()
|
||||
* @see self::encrypt()
|
||||
*/
|
||||
private function crypt(string $text, int $mode): string
|
||||
{
|
||||
$this->setup();
|
||||
if (!$this->continuousBuffer) {
|
||||
if ($this->engine == self::ENGINE_OPENSSL) {
|
||||
$iv = pack('V', $this->counter) . $this->p2;
|
||||
return openssl_encrypt(
|
||||
$text,
|
||||
$this->cipher_name_openssl,
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
}
|
||||
$i = $this->counter;
|
||||
$blocks = str_split($text, 64);
|
||||
foreach ($blocks as &$block) {
|
||||
$block ^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2);
|
||||
}
|
||||
|
||||
return implode('', $blocks);
|
||||
}
|
||||
|
||||
if ($mode == self::ENCRYPT) {
|
||||
$buffer = &$this->enbuffer;
|
||||
} else {
|
||||
$buffer = &$this->debuffer;
|
||||
}
|
||||
if (!strlen($buffer['ciphertext'])) {
|
||||
$ciphertext = '';
|
||||
} else {
|
||||
$ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text));
|
||||
$text = substr($text, strlen($ciphertext));
|
||||
if (!strlen($text)) {
|
||||
return $ciphertext;
|
||||
}
|
||||
}
|
||||
|
||||
$overflow = strlen($text) % 64; // & 0x3F
|
||||
if ($overflow) {
|
||||
$text2 = Strings::pop($text, $overflow);
|
||||
if ($this->engine == self::ENGINE_OPENSSL) {
|
||||
$iv = pack('V', $buffer['counter']) . $this->p2;
|
||||
// at this point $text should be a multiple of 64
|
||||
$buffer['counter'] += (strlen($text) >> 6) + 1; // ie. divide by 64
|
||||
$encrypted = openssl_encrypt(
|
||||
$text . str_repeat("\0", 64),
|
||||
$this->cipher_name_openssl,
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
$temp = Strings::pop($encrypted, 64);
|
||||
} else {
|
||||
$blocks = str_split($text, 64);
|
||||
if (strlen($text)) {
|
||||
foreach ($blocks as &$block) {
|
||||
$block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
|
||||
}
|
||||
}
|
||||
$encrypted = implode('', $blocks);
|
||||
$temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
|
||||
}
|
||||
$ciphertext .= $encrypted . ($text2 ^ $temp);
|
||||
$buffer['ciphertext'] = substr($temp, $overflow);
|
||||
} elseif (!strlen($buffer['ciphertext'])) {
|
||||
if ($this->engine == self::ENGINE_OPENSSL) {
|
||||
$iv = pack('V', $buffer['counter']) . $this->p2;
|
||||
$buffer['counter'] += (strlen($text) >> 6);
|
||||
$ciphertext .= openssl_encrypt(
|
||||
$text,
|
||||
$this->cipher_name_openssl,
|
||||
$this->key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
} else {
|
||||
$blocks = str_split($text, 64);
|
||||
foreach ($blocks as &$block) {
|
||||
$block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
|
||||
}
|
||||
$ciphertext .= implode('', $blocks);
|
||||
}
|
||||
}
|
||||
|
||||
return $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Left Rotate
|
||||
*/
|
||||
protected static function leftRotate(int $x, int $n): int
|
||||
{
|
||||
if (PHP_INT_SIZE == 8) {
|
||||
$r1 = $x << $n;
|
||||
$r1 &= 0xFFFFFFFF;
|
||||
$r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
|
||||
} else {
|
||||
$x = (int) $x;
|
||||
$r1 = $x << $n;
|
||||
$r2 = $x >> (32 - $n);
|
||||
$r2 &= (1 << $n) - 1;
|
||||
}
|
||||
return $r1 | $r2;
|
||||
}
|
||||
|
||||
/**
|
||||
* The quarterround function
|
||||
*/
|
||||
protected static function quarterRound(int &$a, int &$b, int &$c, int &$d): void
|
||||
{
|
||||
$b ^= self::leftRotate($a + $d, 7);
|
||||
$c ^= self::leftRotate($b + $a, 9);
|
||||
$d ^= self::leftRotate($c + $b, 13);
|
||||
$a ^= self::leftRotate($d + $c, 18);
|
||||
}
|
||||
|
||||
/**
|
||||
* The doubleround function
|
||||
*
|
||||
* @param int $x0 (by reference)
|
||||
* @param int $x1 (by reference)
|
||||
* @param int $x2 (by reference)
|
||||
* @param int $x3 (by reference)
|
||||
* @param int $x4 (by reference)
|
||||
* @param int $x5 (by reference)
|
||||
* @param int $x6 (by reference)
|
||||
* @param int $x7 (by reference)
|
||||
* @param int $x8 (by reference)
|
||||
* @param int $x9 (by reference)
|
||||
* @param int $x10 (by reference)
|
||||
* @param int $x11 (by reference)
|
||||
* @param int $x12 (by reference)
|
||||
* @param int $x13 (by reference)
|
||||
* @param int $x14 (by reference)
|
||||
* @param int $x15 (by reference)
|
||||
*/
|
||||
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
|
||||
{
|
||||
// columnRound
|
||||
static::quarterRound($x0, $x4, $x8, $x12);
|
||||
static::quarterRound($x5, $x9, $x13, $x1);
|
||||
static::quarterRound($x10, $x14, $x2, $x6);
|
||||
static::quarterRound($x15, $x3, $x7, $x11);
|
||||
// rowRound
|
||||
static::quarterRound($x0, $x1, $x2, $x3);
|
||||
static::quarterRound($x5, $x6, $x7, $x4);
|
||||
static::quarterRound($x10, $x11, $x8, $x9);
|
||||
static::quarterRound($x15, $x12, $x13, $x14);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Salsa20 hash function function
|
||||
*/
|
||||
protected static function salsa20(string $x)
|
||||
{
|
||||
$z = $x = unpack('V*', $x);
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
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]);
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= 16; $i++) {
|
||||
$x[$i] += $z[$i];
|
||||
}
|
||||
|
||||
return pack('V*', ...$x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates Poly1305 MAC
|
||||
*
|
||||
* @see self::decrypt()
|
||||
* @see self::encrypt()
|
||||
*/
|
||||
protected function poly1305(string $text): string
|
||||
{
|
||||
if (!$this->usingGeneratedPoly1305Key) {
|
||||
return parent::poly1305($this->aad . $text);
|
||||
} else {
|
||||
/*
|
||||
sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag
|
||||
the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see
|
||||
how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts
|
||||
it:
|
||||
|
||||
$this->newtag = $this->poly1305(
|
||||
$this->aad .
|
||||
pack('V', strlen($this->aad)) . "\0\0\0\0" .
|
||||
$ciphertext .
|
||||
pack('V', strlen($ciphertext)) . "\0\0\0\0"
|
||||
);
|
||||
|
||||
phpseclib opts to use the IETF construction, even when the nonce is 64-bits
|
||||
instead of 96-bits
|
||||
*/
|
||||
return parent::poly1305(
|
||||
self::nullPad128($this->aad) .
|
||||
self::nullPad128($text) .
|
||||
pack('V', strlen($this->aad)) . "\0\0\0\0" .
|
||||
pack('V', strlen($text)) . "\0\0\0\0"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user