PHPで、SHA512を計算するスクリプト
2010年10月10日
Jeans CMSは、ハッシュ値の計算にSHA512を用いていて、これでパスワードの認証などを行っている。Jeansの実行にはPHP5.2が必要で、PHP5.2のデフォルトではhash()関数を用いてSHA512を利用することができる。
ところが、サーバーの設定によっては、hash()関数が使えないらしい。そこで、そういった状況でも対応できるよう、PHPを用いてSHA512を実装することを試みた。
CでSHA512を計算するコードとして、PolarSSLというものを見つけた。ライセンスはGPLなので、Jeansにもそのまま取り込める。Cで動くことを確認して、PHPに移植した。64ビット整数の演算が必要で、PHP5.2ではこれが実装されていないので、この部分もエミュレートするなどした。結果として、ちゃんと計算することができたので、ここにメモしておく。
以下のコードでは、hash2()関数として実装している。色々な長さのランダムな文字列についてSHA512を計算するコードも含まれている。
ところが、サーバーの設定によっては、hash()関数が使えないらしい。そこで、そういった状況でも対応できるよう、PHPを用いてSHA512を実装することを試みた。
CでSHA512を計算するコードとして、PolarSSLというものを見つけた。ライセンスはGPLなので、Jeansにもそのまま取り込める。Cで動くことを確認して、PHPに移植した。64ビット整数の演算が必要で、PHP5.2ではこれが実装されていないので、この部分もエミュレートするなどした。結果として、ちゃんと計算することができたので、ここにメモしておく。
以下のコードでは、hash2()関数として実装している。色々な長さのランダムな文字列についてSHA512を計算するコードも含まれている。
<?php
/*
* The licence of following code is GPL (v2.0).
*/
for($i=1;$i<1024*1024;$i*=2){
echo "$i\n";
$str='';
for($j=0;$j<$i;$j++) $str.=chr(rand(0,255));
$h1=hash('SHA512',$str);
$h2=hash2('SHA512',$str);
if ($h1!=$h2) exit('ERROR!');
echo $h1."\n";
}
function hash2($mode,$str,$raw_output=false){
$mode=strtolower($mode);
switch($mode){
case 'md5':
return md5($str,$raw_output);
case 'sha1':
return sha1($str,$raw_output);
case 'sha512': case 'sha384':
$result=false;
misc_sha512::sha4($str,strlen($str),$result,$mode=='sha384');
if ($raw_output) return $result;
$ret='';
for($i=0;$i<64;$i++){
$byte=ord(substr($result,$i,1));
if ($byte<16) $ret.='0';
$ret.=dechex($byte);
}
return $ret;
default:
exit(__FILE__.__LINE__);
}
}
class misc_sha512 {
/**
* SHA-512/384 calculation routine.
* This code originally came from PolarSSL (http://www.polarssl.org)
* but was modified by Jeans CMS team.
*/
/*
* SHA-512 context structure
*/
public $total,$state,$buffer,$is384;
public function __construct($is384){
$this->total=array(str_repeat("\x00",8),str_repeat("\x00",8));
$this->buffer=str_repeat("\x00",128);
$this->is384=$is384;
$this->state=array();
if( $is384 )
{
/* SHA-384 */
$this->state[0] = "\xCB\xBB\x9D\x5D\xC1\x05\x9E\xD8";
$this->state[1] = "\x62\x9A\x29\x2A\x36\x7C\xD5\x07";
$this->state[2] = "\x91\x59\x01\x5A\x30\x70\xDD\x17";
$this->state[3] = "\x15\x2F\xEC\xD8\xF7\x0E\x59\x39";
$this->state[4] = "\x67\x33\x26\x67\xFF\xC0\x0B\x31";
$this->state[5] = "\x8E\xB4\x4A\x87\x68\x58\x15\x11";
$this->state[6] = "\xDB\x0C\x2E\x0D\x64\xF9\x8F\xA7";
$this->state[7] = "\x47\xB5\x48\x1D\xBE\xFA\x4F\xA4";
}
else
{
/* SHA-512 */
$this->state[0] = "\x6A\x09\xE6\x67\xF3\xBC\xC9\x08";
$this->state[1] = "\xBB\x67\xAE\x85\x84\xCA\xA7\x3B";
$this->state[2] = "\x3C\x6E\xF3\x72\xFE\x94\xF8\x2B";
$this->state[3] = "\xA5\x4F\xF5\x3A\x5F\x1D\x36\xF1";
$this->state[4] = "\x51\x0E\x52\x7F\xAD\xE6\x82\xD1";
$this->state[5] = "\x9B\x05\x68\x8C\x2B\x3E\x6C\x1F";
$this->state[6] = "\x1F\x83\xD9\xAB\xFB\x41\xBD\x6B";
$this->state[7] = "\x5B\xE0\xCD\x19\x13\x7E\x21\x79";
}
}
/*
* There is only a static public function below.
*/
static public function sha4(&$input,$ilen,&$output,$is384){
$ctx=false;
self::sha4_starts( $ctx, $is384 );
self::sha4_update( $ctx, $input, $ilen );
self::sha4_finish( $ctx, $output );
}
/*
* Private functions follow.
*/
/*
* 64 bit calculation routines.
*/
static private function int_to_64($int){
$result='';
for($i=0;$i<8;$i++){
$result=chr($int & 0xff).$result;
$int=$int>>8;
}
return $result;
}
static private function int_from_64($i64str){
$result=0;
for($i=0;$i<8;$i++){
$result*=256;
$result+=ord(substr($i64str,$i,1));
}
return $result;
}
static private function bitc($a,$mode,$b){
if (is_int($a)) $a=self::int_to_64($a);
if (is_int($b)) $b=self::int_to_64($b);
$result='';
$c=0;
for($i=7;0<=$i;$i--){
$aa=ord(substr($a,$i,1));
$bb=ord(substr($b,$i,1));
switch($mode){
case '&':
$byte=$aa & $bb;
break;
case '|':
$byte=$aa | $bb;
break;
case '^':
$byte=$aa ^ $bb;
break;
case '+':
$byte=$aa + $bb +$c;
$c=$byte>>8;
$byte=$byte & 0xff;
break;
default:
exit(__CLASS__.__LINE__);
}
$result=chr($byte).$result;
}
return $result;
}
/*
* Shift and rotate routines.
*/
static private function shr($x,$n){
$n8=$n>>3;
if (0<$n8) {
$x=substr(str_repeat("\x00",$n8).$x,0,8);
}
$n=$n & 7;
if (0<$n) {
$result='';
$c=0;
for($i=0;$i<8;$i++){
$byte=ord(substr($x,$i,1));
$result.=chr(($byte>>$n) | $c);
$c=($byte<<(8-$n)) & 0xff;
}
$x=$result;
}
return $x;
}
static private function ror($x,$n){
$n8=$n>>3;
if (0<$n8) {
$x=substr($x.$x,8-$n8,8);
}
$n=$n & 7;
if (0<$n) {
$result='';
$byte=ord(substr($x,-1));
$byte=$byte<<(8-$n);
$c=$byte & 0xff;
for($i=0;$i<8;$i++){
$byte=ord(substr($x,$i,1));
$result.=chr(($byte>>$n) | $c);
$c=($byte<<(8-$n)) & 0xff;
}
$x=$result;
}
return $x;
}
static private function shl($x,$n){
$n8=$n>>3;
if (0<$n8) {
$x=substr($x.str_repeat("\x00",$n8),-8);
}
$n=$n & 7;
if (0<$n) {
$result='';
$c=0;
for($i=7;0<=$i;$i--){
$byte=ord(substr($x,$i,1))<<$n;
$result=chr(($byte & 0xff) | $c).$result;
$c=$byte>>8;
}
$x=$result;
}
return $x;
}
/*
* Get/put 64bit data from/into buffer.
*/
static private function get_uint64_be(&$n,$b,$i){
$n=substr($b,$i,8);
}
static private function put_uint64_be($n,&$b,$i){
$b=substr($b,0,$i).$n.substr($b,$i+8);
}
/*
* Round constants
*/
static private $k=array(
"\x42\x8A\x2F\x98\xD7\x28\xAE\x22","\x71\x37\x44\x91\x23\xEF\x65\xCD",
"\xB5\xC0\xFB\xCF\xEC\x4D\x3B\x2F","\xE9\xB5\xDB\xA5\x81\x89\xDB\xBC",
"\x39\x56\xC2\x5B\xF3\x48\xB5\x38","\x59\xF1\x11\xF1\xB6\x05\xD0\x19",
"\x92\x3F\x82\xA4\xAF\x19\x4F\x9B","\xAB\x1C\x5E\xD5\xDA\x6D\x81\x18",
"\xD8\x07\xAA\x98\xA3\x03\x02\x42","\x12\x83\x5B\x01\x45\x70\x6F\xBE",
"\x24\x31\x85\xBE\x4E\xE4\xB2\x8C","\x55\x0C\x7D\xC3\xD5\xFF\xB4\xE2",
"\x72\xBE\x5D\x74\xF2\x7B\x89\x6F","\x80\xDE\xB1\xFE\x3B\x16\x96\xB1",
"\x9B\xDC\x06\xA7\x25\xC7\x12\x35","\xC1\x9B\xF1\x74\xCF\x69\x26\x94",
"\xE4\x9B\x69\xC1\x9E\xF1\x4A\xD2","\xEF\xBE\x47\x86\x38\x4F\x25\xE3",
"\x0F\xC1\x9D\xC6\x8B\x8C\xD5\xB5","\x24\x0C\xA1\xCC\x77\xAC\x9C\x65",
"\x2D\xE9\x2C\x6F\x59\x2B\x02\x75","\x4A\x74\x84\xAA\x6E\xA6\xE4\x83",
"\x5C\xB0\xA9\xDC\xBD\x41\xFB\xD4","\x76\xF9\x88\xDA\x83\x11\x53\xB5",
"\x98\x3E\x51\x52\xEE\x66\xDF\xAB","\xA8\x31\xC6\x6D\x2D\xB4\x32\x10",
"\xB0\x03\x27\xC8\x98\xFB\x21\x3F","\xBF\x59\x7F\xC7\xBE\xEF\x0E\xE4",
"\xC6\xE0\x0B\xF3\x3D\xA8\x8F\xC2","\xD5\xA7\x91\x47\x93\x0A\xA7\x25",
"\x06\xCA\x63\x51\xE0\x03\x82\x6F","\x14\x29\x29\x67\x0A\x0E\x6E\x70",
"\x27\xB7\x0A\x85\x46\xD2\x2F\xFC","\x2E\x1B\x21\x38\x5C\x26\xC9\x26",
"\x4D\x2C\x6D\xFC\x5A\xC4\x2A\xED","\x53\x38\x0D\x13\x9D\x95\xB3\xDF",
"\x65\x0A\x73\x54\x8B\xAF\x63\xDE","\x76\x6A\x0A\xBB\x3C\x77\xB2\xA8",
"\x81\xC2\xC9\x2E\x47\xED\xAE\xE6","\x92\x72\x2C\x85\x14\x82\x35\x3B",
"\xA2\xBF\xE8\xA1\x4C\xF1\x03\x64","\xA8\x1A\x66\x4B\xBC\x42\x30\x01",
"\xC2\x4B\x8B\x70\xD0\xF8\x97\x91","\xC7\x6C\x51\xA3\x06\x54\xBE\x30",
"\xD1\x92\xE8\x19\xD6\xEF\x52\x18","\xD6\x99\x06\x24\x55\x65\xA9\x10",
"\xF4\x0E\x35\x85\x57\x71\x20\x2A","\x10\x6A\xA0\x70\x32\xBB\xD1\xB8",
"\x19\xA4\xC1\x16\xB8\xD2\xD0\xC8","\x1E\x37\x6C\x08\x51\x41\xAB\x53",
"\x27\x48\x77\x4C\xDF\x8E\xEB\x99","\x34\xB0\xBC\xB5\xE1\x9B\x48\xA8",
"\x39\x1C\x0C\xB3\xC5\xC9\x5A\x63","\x4E\xD8\xAA\x4A\xE3\x41\x8A\xCB",
"\x5B\x9C\xCA\x4F\x77\x63\xE3\x73","\x68\x2E\x6F\xF3\xD6\xB2\xB8\xA3",
"\x74\x8F\x82\xEE\x5D\xEF\xB2\xFC","\x78\xA5\x63\x6F\x43\x17\x2F\x60",
"\x84\xC8\x78\x14\xA1\xF0\xAB\x72","\x8C\xC7\x02\x08\x1A\x64\x39\xEC",
"\x90\xBE\xFF\xFA\x23\x63\x1E\x28","\xA4\x50\x6C\xEB\xDE\x82\xBD\xE9",
"\xBE\xF9\xA3\xF7\xB2\xC6\x79\x15","\xC6\x71\x78\xF2\xE3\x72\x53\x2B",
"\xCA\x27\x3E\xCE\xEA\x26\x61\x9C","\xD1\x86\xB8\xC7\x21\xC0\xC2\x07",
"\xEA\xDA\x7D\xD6\xCD\xE0\xEB\x1E","\xF5\x7D\x4F\x7F\xEE\x6E\xD1\x78",
"\x06\xF0\x67\xAA\x72\x17\x6F\xBA","\x0A\x63\x7D\xC5\xA2\xC8\x98\xA6",
"\x11\x3F\x98\x04\xBE\xF9\x0D\xAE","\x1B\x71\x0B\x35\x13\x1C\x47\x1B",
"\x28\xDB\x77\xF5\x23\x04\x7D\x84","\x32\xCA\xAB\x7B\x40\xC7\x24\x93",
"\x3C\x9E\xBE\x0A\x15\xC9\xBE\xBC","\x43\x1D\x67\xC4\x9C\x10\x0D\x4C",
"\x4C\xC5\xD4\xBE\xCB\x3E\x42\xB6","\x59\x7F\x29\x9C\xFC\x65\x7E\x2A",
"\x5F\xCB\x6F\xAB\x3A\xD6\xFA\xEC","\x6C\x44\x19\x8C\x4A\x47\x58\x17"
);
/*
* SHA-512 context setup
* See also constructor.
*/
static private $sha4_padding;
static private function sha4_starts( &$ctx, $is384 ){
$ctx=new self($is384);
if (!isset(self::$sha4_padding)) {
self::$sha4_padding="\x80".str_repeat("\x00",127);
}
}
/*
* Bit calculation routines follow
*/
static private function s0($x){
$result=self::ror($x,1);
$result=self::bitc($result,'^',self::ror($x,8));
$result=self::bitc($result,'^',self::shr($x,7));
return $result;
}
static private function s1($x){
$result=self::ror($x,19);
$result=self::bitc($result,'^',self::ror($x,61));
$result=self::bitc($result,'^',self::shr($x,6));
return $result;
}
static private function s2($x){
$result=self::ror($x,28);
$result=self::bitc($result,'^',self::ror($x,34));
$result=self::bitc($result,'^',self::ror($x,39));
return $result;
}
static private function s3($x){
$result=self::ror($x,14);
$result=self::bitc($result,'^',self::ror($x,18));
$result=self::bitc($result,'^',self::ror($x,41));
return $result;
}
static private function f0($x,$y,$z){
$v1=self::bitc($x,'&',$y);
$v2=self::bitc($x,'|',$y);
$v2=self::bitc($z,'&',$v2);
return self::bitc($v1,'|',$v2);
}
static private function f1($x,$y,$z){
$v1=self::bitc($y,'^',$z);
$v1=self::bitc($x,'&',$v1);
return self::bitc($z,'^',$v1);
}
static private function p0($a,$b,$c,&$d,$e,$f,$g,&$h,$x,$k){
$temp1=self::bitc($h,'+',self::s3($e));
$temp1=self::bitc($temp1,'+',self::f1($e,$f,$g));
$temp1=self::bitc($temp1,'+',$k);
$temp1=self::bitc($temp1,'+',$x);
$temp2=self::bitc(self::s2($a),'+',self::f0($a,$b,$c));
$d=self::bitc($d,'+',$temp1);
$h=self::bitc($temp1,'+',$temp2);
}
/*
* SHA512/SHA384 process
*/
static private function sha4_process(&$ctx, $data){
$W=array_fill(0,80,false);
for( $i = 0; $i < 16; $i++ ){
self::get_uint64_be( $W[$i], $data, $i << 3 );
}
for( ; $i < 80; $i++ ){
$temp=self::bitc(self::s1($W[$i-2]),'+',$W[$i-7]);
$temp=self::bitc($temp,'+',self::s0($W[$i-15]));
$W[$i]=self::bitc($temp,'+',$W[$i - 16]);
}
$A = $ctx->state[0];
$B = $ctx->state[1];
$C = $ctx->state[2];
$D = $ctx->state[3];
$E = $ctx->state[4];
$F = $ctx->state[5];
$G = $ctx->state[6];
$H = $ctx->state[7];
$i = 0;
do{
self::p0( $A, $B, $C, $D, $E, $F, $G, $H, $W[$i], self::$k[$i] ); $i++;
self::p0( $H, $A, $B, $C, $D, $E, $F, $G, $W[$i], self::$k[$i] ); $i++;
self::p0( $G, $H, $A, $B, $C, $D, $E, $F, $W[$i], self::$k[$i] ); $i++;
self::p0( $F, $G, $H, $A, $B, $C, $D, $E, $W[$i], self::$k[$i] ); $i++;
self::p0( $E, $F, $G, $H, $A, $B, $C, $D, $W[$i], self::$k[$i] ); $i++;
self::p0( $D, $E, $F, $G, $H, $A, $B, $C, $W[$i], self::$k[$i] ); $i++;
self::p0( $C, $D, $E, $F, $G, $H, $A, $B, $W[$i], self::$k[$i] ); $i++;
self::p0( $B, $C, $D, $E, $F, $G, $H, $A, $W[$i], self::$k[$i] ); $i++;
} while( $i < 80 );
$ctx->state[0] = self::bitc($ctx->state[0],'+', $A);
$ctx->state[1] = self::bitc($ctx->state[1],'+', $B);
$ctx->state[2] = self::bitc($ctx->state[2],'+', $C);
$ctx->state[3] = self::bitc($ctx->state[3],'+', $D);
$ctx->state[4] = self::bitc($ctx->state[4],'+', $E);
$ctx->state[5] = self::bitc($ctx->state[5],'+', $F);
$ctx->state[6] = self::bitc($ctx->state[6],'+', $G);
$ctx->state[7] = self::bitc($ctx->state[7],'+', $H);
}
/*
* SHA-512 process buffer
*/
static private function memcpy(&$to,$to_pos,&$from,$from_pos,$len){
$to=substr($to,0,$to_pos).
substr($from,$from_pos,$len).
substr($to,$to_pos+$len);
}
static private function sha4_update( &$ctx, &$input, $ilen ){
if( $ilen <= 0 )
return;
$input_pos=0;
$temp=array_fill(0,128,0);
$left = ord(substr($ctx->total[0],-1)) & 0x7F;
$fill = (int)( 128 - $left );
$ctx->total[0]=self::bitc($ctx->total[0],'+',(int)$ilen);
if( self::int_from_64($ctx->total[0]) < $ilen )
$ctx->total[1]=self::bitc($ctx->total[1],'+',1);
if( $left && $ilen >= $fill )
{
self::memcpy($ctx->buffer,$left,$input,$input_pos,$fill);
self::sha4_process( $ctx, $ctx->buffer );
$input_pos+=$fill;
$ilen -= $fill;
$left = 0;
}
while( $ilen >= 128 )
{
$temp='';
self::memcpy($temp,0,$input,$input_pos,128);
self::sha4_process( $ctx, $temp );
$input_pos +=128;
$ilen -= 128;
}
if( $ilen > 0 )
{
self::memcpy($ctx->buffer,$left,$input,$input_pos,$ilen);
}
}
/*
* SHA-512 final digest
*/
static private function sha4_finish( &$ctx, &$output){
$msglen=str_repeat("\x00",16);
$high=self::bitc(self::shr($ctx->total[0],61),'|',self::shl($ctx->total[1],3));
$low=self::shl($ctx->total[0],3);
self::put_uint64_be( $high, $msglen, 0 );
self::put_uint64_be( $low, $msglen, 8 );
$last = ord(substr($ctx->total[0],-1)) & 0x7F;
$padn = ( $last < 112 ) ? ( 112 - $last ) : ( 240 - $last );
self::sha4_update( $ctx, self::$sha4_padding, $padn );
self::sha4_update( $ctx, $msglen, 16 );
self::put_uint64_be( $ctx->state[0], $output, 0 );
self::put_uint64_be( $ctx->state[1], $output, 8 );
self::put_uint64_be( $ctx->state[2], $output, 16 );
self::put_uint64_be( $ctx->state[3], $output, 24 );
self::put_uint64_be( $ctx->state[4], $output, 32 );
self::put_uint64_be( $ctx->state[5], $output, 40 );
if( !$ctx->is384 )
{
self::put_uint64_be( $ctx->state[6], $output, 48 );
self::put_uint64_be( $ctx->state[7], $output, 56 );
}
}
}