You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wownerowp/include/crypto/SHA3.php

325 lines
8.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?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;
}
}