SHA256 version 2.0 alpha 3 - updated
Posted: Fri Jun 01, 2007 12:10 pm
Updated 2007-06-03: Alterations in green. Code posted below has been updated to the latest.
Updated 2007-06-05: Alterations in purple. Code posted below has not changed.
So for whatever reason I finally got around to getting close to finishing version 2.0 of my SHA256 class structure. The current builds of which can be downloaded from here: http://code.google.com/p/redwidow-dna/downloads/list
Changes this run are
The main meat of the PHP 5 library follows for review:
Updated 2007-06-05: Alterations in purple. Code posted below has not changed.
So for whatever reason I finally got around to getting close to finishing version 2.0 of my SHA256 class structure. The current builds of which can be downloaded from here: http://code.google.com/p/redwidow-dna/downloads/list
Changes this run are
- No more static calls. Shifted to objects in favor of more object oriented and easier to switch to another algorithm
- PHP 4 and 5 versions included in the archive.
- PHP 5 version is aware of, and will use if possible, the hashing extension
- It should yield a smaller memory requirement for all inputs, especially files. It no longer requires to load the entire file into memory.
- More of a plug-in style design for class structure, with interfaces defined in PHP 5 (they're base classes in PHP 4) to make adding more inputs or algorithms simple.
- Added UrlInput
- SimpleTest Unit Tests for PHP 4 and PHP 5 builds.
- Compatibility adapter now in for both PHP 4 and PHP 5 builds.
- [s]Compatibility adapter for users of the older version wishing to update[/s]
- [s]Adding UrlInput[/s]
- Other hashing algorithms SHA384, SHA512, SHA768 and SHA1024 are top-most priorities.
- Other inputs. I'm open to suggestions.
The main meat of the PHP 5 library follows for review:
Code: Select all
<?php
/*******************************************************************************
*
* SHA256 object oriented class for PHP 5
* implemented by feyd _at_ devnetwork .dot. net
* specification from http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf
*
* (C) Copyright 2007 PHP Developer's Network. All rights reserved.
*
* This library 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 2.1 of the License, or (at your
* option) any later version.
*
* This library 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 library; if not, write to the
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*
* Thanks to CertainKey Inc. for providing some example outputs in Javascript
*
*----- Version 2.0.0 ----------------------------------------------------------
*
* These classes generate PHP level errors when an error, warning, or notice of
* some kind happens during execution. This allows you to "hide" any of these
* like you would any other function's error output using error_reporting().
*
* It should be noted that this version is completely incompatible with
* previous versions. An adapter for older versions will be forthcoming.
*
* At this time, this library requires the BCMath Arbitrary Precision
* Mathematics Functions library installed.
*
* ---------------------------------
*
* Sha256 is an implementation of HashAlgorithm which performs all the
* computations required to generate a SHA256 hash result of a given input.
*
* ---------------------------------
*
* NOTE: If you receive a parse error, you probably aren't running PHP 5.
*
* ---------------------------------
*
* Method:
* Sha256v2::hash()
*
* Syntax:
* string $sha256->hash( HashInput in[, boolean raw ] )
*
* Description:
* Attempt to hash a given input. The return value is either a hex string
* or binary string. A lowercase hex string is the default. Passing true
* as the second parameter will result in the binary string.
*
* Failures return FALSE.
*
* Usage:
* $sha256 = new Sha256v2();
* $hash = $sha256->hash(new StringInput('string to hash'));
*
*----- History ----------------------------------------------------------------
* 2.0.0 Completely redone adding compatibility for PHP 5 along with
* redesigning the original form to be more modular. The PHP 5
* version will use the hash extension if possible.
*
* 1.1.0 Split out generic 'hash' class to a seperate include, for future
* hashes to use.
* Changed handling around to allow for very large message chunks
* for files and URL handling (does not affect string hashes).
* Added file hashing support. see notes above for details.
* Added url hashing support. see notes above for details.
* Split testing to a seperate file.
* Initial check-in to source control
* Removed octal output as a future option, due to incomplete usage
* Added bit and upper case hex output
*
* 1.0.1 Potential integer truncation bug fix for various operating
* systems and processor combinations.
*
* 1.0.0 First version made available.
*
*----- Source Control Information ---------------------------------------------
*
* $Workfile: Sha256.class.php $
* $Author: feyd $
* $JustDate: 2007-06-03 $
* $Revision: 11 $
*
* $Header: /inc/php5/Sha256.class.php 11 2007-06-03 9:30p feyd $
*
******************************************************************************/
include_once 'HashAlgorithm.class.php';
include_once 'StringInput.class.php';
/**
* This is version 2 of the SHA256 hashing algorithm. The algorithm itself
* hasn't changed much, certainly not the output, but the design approach has.
* This is now much more object oriented and flexible as a result. It is now
* much easier to switch algorithms than previously possible with older
* versions of this library.
*/
class Sha256v2 implements HashAlgorithm
{
/**
* Used as a flag to force the internal routine to run instead of
* attempting to use the hashing extension if it exists.
*/
private $noExt;
/**
* Constructor sets the $noExt flag.
* @param $noExt
* Set the no extension flag.
*/
public function __construct($noExt = false)
{
$this->noExt = is_bool($noExt) && $noExt === true;
}
/**
* Hash the data represented by the HashInput object (or string) given.
* @param in
* An object that implements HashInput or a string.
* @param raw
* Request that the result be returned in binary instead of hex when
* true.
* @return The resulting hash or false on error.
*/
public function hash( $in, $raw = false )
{
if (is_object($in) and $in instanceof HashInput)
{
// bypass the error
}
elseif(is_scalar($in))
{
$in = new StringInput(strval($in));
}
else
{
trigger_error(__METHOD__ .
'(): First parameter is not a subclass of HashInput.',
E_USER_ERROR);
}
$raw = is_bool($raw) && $raw === true;
// Check for the existance of the hashing extenion (and verification
// that it supports sha256)
if( !$this->noExt and
extension_loaded( 'hash' ) and
function_exists( 'hash_algos' ) and
in_array( 'sha256', hash_algos() ) and
function_exists( 'hash_init' ) and
function_exists( 'hash_update' ) and
function_exists( 'hash_final' ))
{
$hash = hash_init( 'sha256' );
while(($buf = $in->read(64)) !== false)
{
hash_update($hash, $buf);
}
return hash_final($hash, $raw);
}
else
{
// H(0) registers
$hash = array (
1779033703, -1150833019, 1013904242, -1521486534,
1359893119, -1694144372, 528734635, 1541459225,
);
// reset the pointer
$in->rewind();
// the byte counter
$counter = '0';
// local buffer used to wait for the chunk size to be reached.
$buffer = '';
// loop until we can't read anymore.
while( ($buf = $in->read(64)) !== false )
{
// concatenate to the actual buffer
$buffer .= $buf;
// add to the byte counter
$counter = bcadd($counter, strlen($buf), 0);
// do we have enough in the buffer to compress it?
if( strlen($buffer) >= 64 )
{
$hash = $this->compress(substr($buffer, 0, 64), $hash);
$buffer = substr($buffer, 64);
}
}
// close the input
$in->close();
// tack on the footer
$buffer .= $this->calculateFooter( $counter );
// perform remaining compressions
while( strlen($buffer) >= 64 )
{
$hash = $this->compress(substr($buffer, 0, 64), $hash);
$buffer = substr($buffer, 64);
}
// make sure the buffer is empty
assert('strlen($buffer) == 0');
// pack the result into either a hex string or binary string
$str = '';
reset($hash);
if( $raw )
{
do
{
$str .= pack('N', current($hash));
}
while(next($hash));
}
else
{
do
{
$str .= sprintf('%08x', current($hash));
}
while(next($hash));
}
return $str;
}
}
/**
* Compress a given chunk using the given registers.
* @param chunk
* The chunk of data to process (must be 64 bytes)
* @param hash
* The current registers for the hash result
* @return The compression results
*/
private function compress($chunk, array $hash)
{
static $K = null;
if($K === null)
{
$K = array (
1116352408, 1899447441, -1245643825, -373957723,
961987163, 1508970993, -1841331548, -1424204075,
-670586216, 310598401, 607225278, 1426881987,
1925078388, -2132889090, -1680079193, -1046744716,
-459576895, -272742522, 264347078, 604807628,
770255983, 1249150122, 1555081692, 1996064986,
-1740746414, -1473132947, -1341970488, -1084653625,
-958395405, -710438585, 113926993, 338241895,
666307205, 773529912, 1294757372, 1396182291,
1695183700, 1986661051, -2117940946, -1838011259,
-1564481375, -1474664885, -1035236496, -949202525,
-778901479, -694614492, -200395387, 275423344,
430227734, 506948616, 659060556, 883997877,
958139571, 1322822218, 1537002063, 1747873779,
1955562222, 2024104815, -2067236844, -1933114872,
-1866530822, -1538233109, -1090935817, -965641998,
);
}
$W = array();
$vars = 'abcdefgh';
list($a, $b, $c, $d, $e, $f, $g, $h) = $hash;
for($j = 0; $j < 64; ++$j)
{
if($j < 16)
{
$T1 = ord($chunk{$j*4 }) & 0xFF; $T1 <<= 8;
$T1 |= ord($chunk{$j*4+1}) & 0xFF; $T1 <<= 8;
$T1 |= ord($chunk{$j*4+2}) & 0xFF; $T1 <<= 8;
$T1 |= ord($chunk{$j*4+3}) & 0xFF;
$W[$j] = $T1;
}
else
{
$W[$j] = $this->sum(
((($W[$j-2] >> 17) & 0x00007FFF) |
($W[$j-2] << 15)) ^ ((($W[$j-2] >> 19) & 0x00001FFF) |
($W[$j-2] << 13)) ^ (($W[$j-2] >> 10) & 0x003FFFFF),
$W[$j-7],
((($W[$j-15] >> 7) & 0x01FFFFFF) | ($W[$j-15] << 25)) ^
((($W[$j-15] >> 18) & 0x00003FFF) | ($W[$j-15] << 14))
^ (($W[$j-15] >> 3) & 0x1FFFFFFF), $W[$j-16]);
}
$T1 = $this->sum(
$h,
((($e >> 6) & 0x03FFFFFF) | ($e << 26)) ^
((($e >> 11) & 0x001FFFFF) | ($e << 21)) ^
((($e >> 25) & 0x0000007F) | ($e << 7)),
($e & $f) ^ (~$e & $g),
$K[$j],
$W[$j]);
$T2 = $this->sum(
((($a >> 2) & 0x3FFFFFFF) | ($a << 30)) ^
((($a >> 13) & 0x0007FFFF) | ($a << 19)) ^
((($a >> 22) & 0x000003FF) | ($a << 10)),
($a & $b) ^ ($a & $c) ^ ($b & $c));
$h = $g;
$g = $f;
$f = $e;
$e = $this->sum($d, $T1);
$d = $c;
$c = $b;
$b = $a;
$a = $this->sum($T1, $T2);
}
// compute the next hash set
for($j = 0; $j < 8; $j++)
$hash[$j] = $this->sum(${$vars{$j}}, $hash[$j]);
return $hash;
}
/**
* Perform a 32-bit summation the way SHA256 expects. This method is
* used by the digest method extensively. This method accepts a
* variable number of arguments which is why none are listed.
* @return The sum calculated
*/
private function sum()
{
$T = 0;
for($x = 0, $y = func_num_args(); $x < $y; ++$x)
{
// force argument to integer
$a = intval(func_get_arg($x));
// carry storage
$c = 0;
for($i = 0; $i < 32; ++$i)
{
// sum of the bits at $i
$j = (($T >> $i) & 1) + (($a >> $i) & 1) + $c;
// carry of the bits at $i
$c = ($j >> 1) & 1;
// strip the carry
$j &= 1;
// clear the bit
$T &= ~(1 << $i);
// set the bit
$T |= $j << $i;
}
}
return $T;
}
/**
* Calculate the footer required for the hash given the length of the
* message.
* @param numbytes
* The quantity of bytes in the message.
* @return The calculated footer binary data
*/
private function calculateFooter( $numbytes )
{
$M = bcmod( $numbytes, '4294967296' ); // clip to 32 bits
$L1 = bcmod(bcdiv($M, '268435456', 0), '16'); // top order bits
$L2 = bcmod( bcmul($M, '8', 0), '4294967296' ); // number of bits
$l = pack('N*', intval($L1), intval($L2));
// 64 = 64 bits needed for the size mark. 1 = the 1 bit added to the
// end. 511 = 511 bits to get the number to be at least large enough
// to require one block. 512 is the block size.
$k = $L2 + 64 + 1 + 511;
$k -= $k % 512 + $L2 + 64 + 1;
$k >>= 3; // convert to byte count
$footer = chr(128) . str_repeat(chr(0), $k) . $l;
assert('($M + strlen($footer)) % 64 == 0');
return $footer;
}
}
?>