123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- <?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.
- */
- class SHA3 {
- const SHA3_224 = 1;
- const SHA3_256 = 2;
- const SHA3_384 = 3;
- const SHA3_512 = 4;
-
- const SHAKE128 = 5;
- const SHAKE256 = 6;
-
-
- 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);
- }
-
- 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;
- }
- }
|