325 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php /* -*- coding: utf-8; indent-tabs-mode: t; tab-width: 4 -*-
 | ||
| vim: ts=4 noet ai */
 | ||
| 
 | ||
| /**
 | ||
| 	Streamable SHA-3 for PHP 5.2+, with no lib/ext dependencies!
 | ||
| 
 | ||
| 	Copyright © 2018  Desktopd Developers
 | ||
| 
 | ||
| 	This program is free software: you can redistribute it and/or modify
 | ||
| 	it under the terms of the GNU Lesser General Public License as published by
 | ||
| 	the Free Software Foundation, either version 3 of the License, or
 | ||
| 	(at your option) any later version.
 | ||
| 
 | ||
| 	This program is distributed in the hope that it will be useful,
 | ||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||
| 	GNU Lesser General Public License for more details.
 | ||
| 
 | ||
| 	You should have received a copy of the GNU Lesser General Public License
 | ||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||
| 
 | ||
| 	@license LGPL-3+
 | ||
| 	@file
 | ||
| */
 | ||
| 
 | ||
| /**
 | ||
| 	SHA-3 (FIPS-202) for PHP strings (byte arrays) (PHP 5.2.1+)
 | ||
| 	PHP 7.0 computes SHA-3 about 4 times faster than PHP 5.2 - 5.6 (on x86_64)
 | ||
| 	
 | ||
| 	Based on the reference implementations, which are under CC-0
 | ||
| 	Reference: http://keccak.noekeon.org/
 | ||
| 	
 | ||
| 	This uses PHP's native byte strings. Supports 32-bit as well as 64-bit
 | ||
| 	systems. Also for LE vs. BE systems.
 | ||
| */
 | ||
| 
 | ||
| defined( 'ABSPATH' ) || exit;
 | ||
| 
 | ||
| class SHA3 {
 | ||
| 	const SHA3_224 = 1;
 | ||
| 	const SHA3_256 = 2;
 | ||
| 	const SHA3_384 = 3;
 | ||
| 	const SHA3_512 = 4;
 | ||
| 	
 | ||
| 	const SHAKE128 = 5;
 | ||
| 	const SHAKE256 = 6;
 | ||
|     
 | ||
|     	const KECCAK_256 = 7;
 | ||
| 	
 | ||
| 	
 | ||
| 	public static function init ($type = null) {
 | ||
| 		switch ($type) {
 | ||
| 			case self::SHA3_224: return new self (1152, 448, 0x06, 28);
 | ||
| 			case self::SHA3_256: return new self (1088, 512, 0x06, 32);
 | ||
| 			case self::SHA3_384: return new self (832, 768, 0x06, 48);
 | ||
| 			case self::SHA3_512: return new self (576, 1024, 0x06, 64);
 | ||
| 			case self::SHAKE128: return new self (1344, 256, 0x1f);
 | ||
| 			case self::SHAKE256: return new self (1088, 512, 0x1f);
 | ||
|             		case self::KECCAK_256: return new self (1088, 512, 0x01, 32);
 | ||
| 		}
 | ||
| 		
 | ||
| 		throw new Exception ('Invalid operation type');
 | ||
| 	}
 | ||
| 	
 | ||
| 	
 | ||
| 	/**
 | ||
| 		Feed input to SHA-3 "sponge"
 | ||
| 	*/
 | ||
| 	public function absorb ($data) {
 | ||
| 		if (self::PHASE_INPUT != $this->phase) {
 | ||
| 			throw new Exception ('No more input accepted');
 | ||
| 		}
 | ||
| 		
 | ||
| 		$rateInBytes = $this->rateInBytes;
 | ||
| 		$this->inputBuffer .= $data;
 | ||
| 		while (strlen ($this->inputBuffer) >= $rateInBytes) {
 | ||
| 			list ($input, $this->inputBuffer) = array (
 | ||
| 				substr ($this->inputBuffer, 0, $rateInBytes)
 | ||
| 				, substr ($this->inputBuffer, $rateInBytes));
 | ||
| 			
 | ||
| 			$blockSize = $rateInBytes;
 | ||
| 			for ($i = 0; $i < $blockSize; $i++) {
 | ||
| 				$this->state[$i] = $this->state[$i] ^ $input[$i];
 | ||
| 			}
 | ||
| 			
 | ||
| 			$this->state = self::keccakF1600Permute ($this->state);
 | ||
| 			$this->blockSize = 0;
 | ||
| 		}
 | ||
| 		
 | ||
| 		return $this;
 | ||
| 	}
 | ||
| 	
 | ||
| 	/**
 | ||
| 		Get hash output
 | ||
| 	*/
 | ||
| 	public function squeeze ($length = null) {
 | ||
| 		$outputLength = $this->outputLength; // fixed length output
 | ||
| 		if ($length && 0 < $outputLength && $outputLength != $length) {
 | ||
| 			throw new Exception ('Invalid length');
 | ||
| 		}
 | ||
| 		
 | ||
| 		if (self::PHASE_INPUT == $this->phase) {
 | ||
| 			$this->finalizeInput ();
 | ||
| 		}
 | ||
| 		
 | ||
| 		if (self::PHASE_OUTPUT != $this->phase) {
 | ||
| 			throw new Exception ('No more output allowed');
 | ||
| 		}
 | ||
| 		if (0 < $outputLength) {
 | ||
| 			$this->phase = self::PHASE_DONE;
 | ||
| 			return $this->getOutputBytes ($outputLength);
 | ||
| 		}
 | ||
| 		
 | ||
| 		$blockLength = $this->rateInBytes;
 | ||
| 		list ($output, $this->outputBuffer) = array (
 | ||
| 			substr ($this->outputBuffer, 0, $length)
 | ||
| 			, substr ($this->outputBuffer, $length));
 | ||
| 		$neededLength = $length - strlen ($output);
 | ||
| 		$diff = $neededLength % $blockLength;
 | ||
| 		if ($diff) {
 | ||
| 			$readLength = (($neededLength - $diff) / $blockLength + 1)
 | ||
| 				* $blockLength;
 | ||
| 		} else {
 | ||
| 			$readLength = $neededLength;
 | ||
| 		}
 | ||
| 		
 | ||
| 		$read = $this->getOutputBytes ($readLength);
 | ||
| 		$this->outputBuffer .= substr ($read, $neededLength);
 | ||
| 		return $output . substr ($read, 0, $neededLength);
 | ||
| 	}
 | ||
| 	
 | ||
| 	
 | ||
| 	// internally used
 | ||
| 	const PHASE_INIT = 1;
 | ||
| 	const PHASE_INPUT = 2;
 | ||
| 	const PHASE_OUTPUT = 3;
 | ||
| 	const PHASE_DONE = 4;
 | ||
| 	
 | ||
| 	private $phase = self::PHASE_INIT;
 | ||
| 	private $state; // byte array (string)
 | ||
| 	private $rateInBytes; // positive integer
 | ||
| 	private $suffix; // 8-bit unsigned integer
 | ||
| 	private $inputBuffer = ''; // byte array (string): max length = rateInBytes
 | ||
| 	private $outputLength = 0;
 | ||
| 	private $outputBuffer = '';
 | ||
| 	
 | ||
| 	
 | ||
| 	public function __construct ($rate, $capacity, $suffix, $length = 0) {
 | ||
| 		if (1600 != ($rate + $capacity)) {
 | ||
| 			throw new Error ('Invalid parameters');
 | ||
| 		}
 | ||
| 		if (0 != ($rate % 8)) {
 | ||
| 			throw new Error ('Invalid rate');
 | ||
| 		}
 | ||
| 		
 | ||
| 		$this->suffix = $suffix;
 | ||
| 		$this->state = str_repeat ("\0", 200);
 | ||
| 		$this->blockSize = 0;
 | ||
| 		
 | ||
| 		$this->rateInBytes = $rate / 8;
 | ||
| 		$this->outputLength = $length;
 | ||
| 		$this->phase = self::PHASE_INPUT;
 | ||
| 		return;
 | ||
| 	}
 | ||
| 	
 | ||
| 	protected function finalizeInput () {
 | ||
| 		$this->phase = self::PHASE_OUTPUT;
 | ||
| 		
 | ||
| 		$input = $this->inputBuffer;
 | ||
| 		$inputLength = strlen ($input);
 | ||
| 		if (0 < $inputLength) {
 | ||
| 			$blockSize = $inputLength;
 | ||
| 			for ($i = 0; $i < $blockSize; $i++) {
 | ||
| 				$this->state[$i] = $this->state[$i] ^ $input[$i];
 | ||
| 			}
 | ||
| 			
 | ||
| 			$this->blockSize = $blockSize;
 | ||
| 		}
 | ||
| 		
 | ||
| 		// Padding
 | ||
| 		$rateInBytes = $this->rateInBytes;
 | ||
| 		$this->state[$this->blockSize] = $this->state[$this->blockSize]
 | ||
| 			^ chr ($this->suffix);
 | ||
| 		if (($this->suffix & 0x80) != 0
 | ||
| 			&& $this->blockSize == ($rateInBytes - 1)) {
 | ||
| 			$this->state = self::keccakF1600Permute ($this->state);
 | ||
| 		}
 | ||
| 		$this->state[$rateInBytes - 1] = $this->state[$rateInBytes - 1] ^ "\x80";
 | ||
| 		$this->state = self::keccakF1600Permute ($this->state);
 | ||
| 	}
 | ||
| 	
 | ||
| 	protected function getOutputBytes ($outputLength) {
 | ||
| 		// Squeeze
 | ||
| 		$output = '';
 | ||
| 		while (0 < $outputLength) {
 | ||
| 			$blockSize = min ($outputLength, $this->rateInBytes);
 | ||
| 			$output .= substr ($this->state, 0, $blockSize);
 | ||
| 			$outputLength -= $blockSize;
 | ||
| 			if (0 < $outputLength) {
 | ||
| 				$this->state = self::keccakF1600Permute ($this->state);
 | ||
| 			}
 | ||
| 		}
 | ||
| 		
 | ||
| 		return $output;
 | ||
| 	}
 | ||
| 	
 | ||
| 	/**
 | ||
| 		1600-bit state version of Keccak's permutation
 | ||
| 	*/
 | ||
| 	protected static function keccakF1600Permute ($state) {
 | ||
| 		$lanes = str_split ($state, 8);
 | ||
| 		$R = 1;
 | ||
| 		$values = "\1\2\4\10\20\40\100\200";
 | ||
| 		
 | ||
| 		for ($round = 0; $round < 24; $round++) {
 | ||
| 			// θ step
 | ||
| 			$C = array ();
 | ||
| 			for ($x = 0; $x < 5; $x++) {
 | ||
| 				// (x, 0) (x, 1) (x, 2) (x, 3) (x, 4)
 | ||
| 				$C[$x] = $lanes[$x] ^ $lanes[$x + 5] ^ $lanes[$x + 10]
 | ||
| 					^ $lanes[$x + 15] ^ $lanes[$x + 20];
 | ||
| 			}
 | ||
| 			for ($x = 0; $x < 5; $x++) {
 | ||
| 				//$D = $C[($x + 4) % 5] ^ self::rotL64 ($C[($x + 1) % 5], 1);
 | ||
| 				$D = $C[($x + 4) % 5] ^ self::rotL64One ($C[($x + 1) % 5]);
 | ||
| 				for ($y = 0; $y < 5; $y++) {
 | ||
| 					$idx = $x + 5 * $y; // x, y
 | ||
| 					$lanes[$idx] = $lanes[$idx] ^ $D;
 | ||
| 				}
 | ||
| 			}
 | ||
| 			unset ($C, $D);
 | ||
| 			
 | ||
| 			// ρ and π steps
 | ||
| 			$x = 1;
 | ||
| 			$y = 0;
 | ||
| 			$current = $lanes[1]; // x, y
 | ||
| 			for ($t = 0; $t < 24; $t++) {
 | ||
| 				list ($x, $y) = array ($y, (2 * $x + 3 * $y) % 5);
 | ||
| 				$idx = $x + 5 * $y;
 | ||
| 				list ($current, $lanes[$idx]) = array ($lanes[$idx]
 | ||
| 					, self::rotL64 ($current
 | ||
| 						, (($t + 1) * ($t + 2) / 2) % 64));
 | ||
| 			}
 | ||
| 			unset ($temp, $current);
 | ||
| 			
 | ||
| 			// χ step
 | ||
| 			$temp = array ();
 | ||
| 			for ($y = 0; $y < 5; $y++) {
 | ||
| 				for ($x = 0; $x < 5; $x++) {
 | ||
| 					$temp[$x] = $lanes[$x + 5 * $y];
 | ||
| 				}
 | ||
| 				for ($x = 0; $x < 5; $x++) {
 | ||
| 					$lanes[$x + 5 * $y] = $temp[$x]
 | ||
| 						^ ((~ $temp[($x + 1) % 5]) & $temp[($x + 2) % 5]);
 | ||
| 					
 | ||
| 				}
 | ||
| 			}
 | ||
| 			unset ($temp);
 | ||
| 			
 | ||
| 			// ι step
 | ||
| 			for ($j = 0; $j < 7; $j++) {
 | ||
| 				$R = (($R << 1) ^ (($R >> 7) * 0x71)) & 0xff;
 | ||
| 				if ($R & 2) {
 | ||
| 					$offset = (1 << $j) - 1;
 | ||
| 					$shift = $offset % 8;
 | ||
| 					$octetShift = ($offset - $shift) / 8;
 | ||
| 					$n = "\0\0\0\0\0\0\0\0";
 | ||
| 					$n[$octetShift] = $values[$shift];
 | ||
| 					
 | ||
| 					$lanes[0] = $lanes[0]
 | ||
| 						^ $n;
 | ||
| 						//^ self::rotL64 ("\1\0\0\0\0\0\0\0", (1 << $j) - 1);
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 		
 | ||
| 		return implode ($lanes);
 | ||
| 	}
 | ||
| 	
 | ||
| 	protected static function rotL64_64 ($n, $offset) {
 | ||
| 		return ($n << $offset) & ($n >> (64 - $offset));
 | ||
| 	}
 | ||
| 	
 | ||
| 	/**
 | ||
| 		64-bit bitwise left rotation (Little endian)
 | ||
| 	*/
 | ||
| 	protected static function rotL64 ($n, $offset) {
 | ||
| 		
 | ||
| 		//$n = (binary) $n;
 | ||
| 		//$offset = ((int) $offset) % 64;
 | ||
| 		//if (8 != strlen ($n)) throw new Exception ('Invalid number');
 | ||
| 		//if ($offset < 0) throw new Exception ('Invalid offset');
 | ||
| 		
 | ||
| 		$shift = $offset % 8;
 | ||
| 		$octetShift = ($offset - $shift) / 8;
 | ||
| 		$n = substr ($n, - $octetShift) . substr ($n, 0, - $octetShift);
 | ||
| 		
 | ||
| 		$overflow = 0x00;
 | ||
| 		for ($i = 0; $i < 8; $i++) {
 | ||
| 			$a = ord ($n[$i]) << $shift;
 | ||
| 			$n[$i] = chr (0xff & $a | $overflow);
 | ||
| 			$overflow = $a >> 8;
 | ||
| 		}
 | ||
| 		$n[0] = chr (ord ($n[0]) | $overflow);
 | ||
| 		return $n;
 | ||
| 	}
 | ||
| 	
 | ||
| 	/**
 | ||
| 		64-bit bitwise left rotation (Little endian)
 | ||
| 	*/
 | ||
| 	protected static function rotL64One ($n) {
 | ||
| 		list ($n[0], $n[1], $n[2], $n[3], $n[4], $n[5], $n[6], $n[7])
 | ||
| 			= array (
 | ||
| 				chr (((ord ($n[0]) << 1) & 0xff) ^ (ord ($n[7]) >> 7))
 | ||
| 				,chr (((ord ($n[1]) << 1) & 0xff) ^ (ord ($n[0]) >> 7))
 | ||
| 				,chr (((ord ($n[2]) << 1) & 0xff) ^ (ord ($n[1]) >> 7))
 | ||
| 				,chr (((ord ($n[3]) << 1) & 0xff) ^ (ord ($n[2]) >> 7))
 | ||
| 				,chr (((ord ($n[4]) << 1) & 0xff) ^ (ord ($n[3]) >> 7))
 | ||
| 				,chr (((ord ($n[5]) << 1) & 0xff) ^ (ord ($n[4]) >> 7))
 | ||
| 				,chr (((ord ($n[6]) << 1) & 0xff) ^ (ord ($n[5]) >> 7))
 | ||
| 				,chr (((ord ($n[7]) << 1) & 0xff) ^ (ord ($n[6]) >> 7)));
 | ||
| 		return $n;
 | ||
| 	}
 | ||
| }
 |