domingo, 9 de agosto de 2009

Secure Hash Algorithm

Bom, aqui vai uma implementação do hash SHA-1, estou trabalhando na serie SHA-2 (atualmente SHA-256 e futuramente SHA-512).
O código abaixo é para quem gosta de saber o que acontece debaixo dos panos quando você usa uma função nativa ou quando a função nativa não está disponível no seu servidor...

/**
* Implementação PHP do Secure Hash Algorithm segundo a Secure Hash Standard (SHS)
* (FIPS PUB 180-3) de outubro de 2008.
* Nessa primeira versão está sendo implementado apenas o hash SHA-1, a proxima versão já contará
* com SHA-256 e futuramente SHA-384 e SHA-512.
*
* @version 0.1
* @author João Batista Neto
* @since 2009-08-06
* @license LGLC
* @link http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf
* @category Computer Security, Cryptography, Hash.
*/
abstract class SecureHash {
/**
* Identifica o hash SHA-1
*/
const SHA_1 = "sha1";

/**
* Identifica o hash SHA-256
*/
const SHA_256 = "sha256";

/**
* Implementação do algorítimo de via única SHA-1 (Secure Hash Algorithm 1) definido
* pela especificação FIPS 180-3
* Message Size: < 2**64
* Block Size: 512 bits
* Word Size: 32 bits
* Message Digest Size: 160 bits
* @link http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf
* @param $message
* @return string
*/
static public function sha1( $message ){
/**
* Pre-processamento
*/
$M = self::pre_process( $message );

/**
* Define os valores iniciais [5.3.1]
*/
$H = array( 0x67452301 , 0xefcdab89 , 0x98badcfe , 0x10325476 , 0xc3d2e1f0 );

/**
* Calculando o Hash [6.1.2]
*/
for ( $i = 0 , $N = count( $M ) ; $i < $N ; $i += 16 ){
$W = array();

/**
* [6.1.2.1]
*/
for ( $t = 0 ; $t < 80 ; $t++ ){
$W[ $t ] = ( $t <= 15 ) ? $M[ $i + $t ] : self::ROTL( $W[ $t - 3 ] ^ $W[ $t - 8 ] ^ $W[ $t - 14 ] ^ $W[ $t - 16 ] , 1 );
}

/**
* [6.1.2.2]
*/
$a = $H[ 0 ];
$b = $H[ 1 ];
$c = $H[ 2 ];
$d = $H[ 3 ];
$e = $H[ 4 ];

/**
* [6.1.2.3]
*/
for ( $t = 0 ; $t < 80 ; $t++ ){
$T = self::add( self::add( self::ROTL( $a , 5 ) , self::f( $t , $b , $c , $d ) ) , self::add( self::add( $e , $W[ $t ] ) , self::Kt( $t ) ) );
$e = $d;
$d = $c;
$c = self::ROTL( $b , 30 );
$b = $a;
$a = $T;
}

/**
* [6.1.2.4]
*/
$H[ 0 ] = self::add( $H[ 0 ] , $a );
$H[ 1 ] = self::add( $H[ 1 ] , $b );
$H[ 2 ] = self::add( $H[ 2 ] , $c );
$H[ 3 ] = self::add( $H[ 3 ] , $d );
$H[ 4 ] = self::add( $H[ 4 ] , $e );
}

return ( sprintf( "%08x%08x%08x%08x%08x" , $H[ 0 ] , $H[ 1 ] , $H[ 2 ] , $H[ 3 ] , $H[ 4 ] ) );
}

/**
* Pre-processamento [5]
* @return array
*/
static private function pre_process( $message ){
$size = strlen( $message );
$M = array();
$N = ( ( $size + 8 ) >> 6 ) + 1;

/**
* [5.1.1]
*/
$message .= "\x80";

for ( $i = 0 ; $i < $N * 16 ; $i++ ) $M[ $i ] = 0;
for ( $i = 0 ; $i < $size ; $i++ ) $M[ $i >> 2 ] |= ord( $message{ $i } ) << ( 24 - ( $i % 4 ) * 8 );

$M[ $i >> 2 ] |= 0x80 << ( 24 - ( $i % 4 ) * 8 );
$M[ $N * 16 - 1 ] = $size * 8;

return( $M );
}

/**
* Operação AND [3.2.2]
* Z = (X + Y) mod 2^32
* @param integer $x
* @param integer $y
* @return integer O novo valor
*/
static private function add( $x , $y ){
$lsw = ( $x & 0xffff ) + ( $y & 0xffff );
$msw = ( $x >> 16 ) + ( $y >> 16 ) + ( $lsw >> 16 );

return ( ( $msw << 16 ) | ( $lsw & 0xFFFF ) );
}

/**
* Operação Right Shift [3.2.3]
* @param $x
* @param $n
* @return integer
*/
static private function SHR( $x , $n ){
$z = hexdec( 80000000 );

if ( $z & $x ){
$x = ( $x >> 1 );
$x &= ~$z;
$x |= 0x40000000;
$x = ( $x >> ( $n - 1 ) );
} else {
$x = ( $x >> $n );
}

return( $x );
}

/**
* Operação Circular Right Shift [3.2.4]
* @param integer $x
* @param integer $n
* @return integer
*/
static private function ROTR( $x , $n ){
return( ( self::SHR( $x , $n ) | ( $x << ( 32 - $n ) ) & 0xFFFFFFFF ) );
}

/**
* Operação Circular Left Shift [3.2.5]
* @param integer $num
* @param integer $n
* @return integer
*/
static private function ROTL( $x , $n ){
return ( ( $x << $n ) | self::SHR( $x , 32 - $n ) );
}

/**
* Função f [4.1.1]
* @param $t
* @param $b
* @param $c
* @param $d
* @return integer
*/
static private function f( $t , $b , $c , $d ){
if ( ( $t >= 0 ) && ( $t <= 19 ) ) return ( self::Ch( $b , $c , $d ) );
if ( ( $t >= 20 ) && ( $t <= 39 ) ) return ( self::Parity( $b , $c , $d ) );
if ( ( $t >= 40 ) && ( $t <= 59 ) ) return ( self::Maj( $b , $c , $d ) );
if ( ( $t >= 60 ) && ( $t <= 79 ) ) return ( self::Parity( $b , $c , $d ) );
}

/**
* Ch [4.1.1]
* @param integer $x
* @param integer $y
* @param integer $z
* @return integer
*/
static private function Ch( $x , $y , $z ){
return ( ( $x & $y ) ^ ( ~$x & $z ) );
}

/**
* Parity [4.1.1]
* @param integer $x
* @param integer $y
* @param integer $z
* @return integer
*/
static private function Parity( $x , $y , $z ){
return ( $x ^ $y ^ $z );
}

/**
* Maj [4.1.1]
* @param integer $x
* @param integer $y
* @param integer $z
* @return integer
*/
static private function Maj( $x , $y , $z ){
return ( ( $x & $y ) ^ ( $x & $z ) ^ ( $y & $z ) );
}

/**
* Sigma{256} 0 [4.1.2]
* @param integer $x
* @return integer
*/
static private function Sigma_0( $x ){
return( ( self::ROTR( $x , 2 ) ^ self::ROTR( $x , 13 ) ^ self::ROTR( $x , 22 ) ) );
}

/**
* Sigma{256} 1 [4.1.2]
* @param integer $x
* @return integer
*/
static private function Sigma_1( $x ){
return( ( self::ROTR( $x , 6 ) ^ self::ROTR( $x , 11 ) ^ self::ROTR( $x , 25 ) ) );
}

/**
* sigma{256} 0 [4.1.2]
* @param integer $x
* @return integer
*/
static private function sigma0( $x ){
return( ( self::ROTR( $x , 7 ) ^ self::ROTR( $x , 18 ) ^ ( self::SHR( $x , 3 ) ) ) );
}

/**
* sigma{256} 1 [4.1.2]
* @param integer $x
* @return integer
*/
static private function sigma1( $x ){
return( ( self::ROTR( $x , 17 ) ^ self::ROTR( $x , 19 ) ^ ( self::SHR( $x , 10 ) ) ) );
}

/**
* Recupera o valor da constante Kt [4.2.1] e [4.2.2]
* @param integer $t
* @param string $type
* @return integer
*/
static private function Kt( $t , $type = self::SHA_1 ){
/**
* Kt [4.2.1]
*/
$k_SHA1 = array( 0x5a827999 , 0x6ed9eba1 , 0x8f1bbcdc , 0xca62c1d6 );

/**
* Kt [4.2.2]
*/
$k_SHA256 = array(
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
);

/**
* Retorna a constante segundo o hash usado
*/
switch ( $type ){
case self::SHA_1 :
if ( ( $t >= 0 ) && ( $t <= 19 ) ) return ( $k_SHA1[ 0 ] );
if ( ( $t >= 20 ) && ( $t <= 39 ) ) return ( $k_SHA1[ 1 ] );
if ( ( $t >= 40 ) && ( $t <= 59 ) ) return ( $k_SHA1[ 2 ] );
if ( ( $t >= 60 ) && ( $t <= 79 ) ) return ( $k_SHA1[ 3 ] );

throw new UnexpectedValueException( sprintf( "O valor %08x não era esperado." , $t ) );
case self::SHA_256:
return( $k_SHA256[ $t ] );
}
}
}

Para testar o código:

$str = file_get_contents( "SecureHash.php" );

printf( "usando a função nativa.: %s\n" , sha1( $str ) );
printf( "usando SecureHash::sha1: %s\n" , SecureHash::sha1( $str ) );


A saída será:

usando a função nativa.: 127a9585171b50b421660a69f5e2a5f4adf09cfa
usando SecureHash::sha1: 127a9585171b50b421660a69f5e2a5f4adf09cfa