Mhash(続々・SHA-256)

なぜか個人的に Mhash がブームだった(過去形)ので、その間に作ってみた関数・クラスを晒してみる。
Mhash がサポートするハッシュ関数を動的に定義する関数:

function mhash_inport_function($name)
{
    if (function_exists($name)) {
        return;
    }
    $algo = strtoupper($name);
    $func = strtolower($name);
    $cnst = 'MHASH_' . $algo;
    if (!defined($cnst)) {
        $errmsg = 'Mhash: No such hashing algorithm (%s)';
        trigger_error(sprintf($errmsg, $algo), E_USER_ERROR);
        return;
    }
    $fmt1 = 'function %1$s($str, $raw_output = false) {
    $hash = mhash(MHASH_%2$s, $str);
    return ($raw_output) ? $hash : bin2hex($hash);
}';
    $fmt2 = 'function %1$s_file($filename, $raw_output = false) {
    return %1$s(file_get_contents($filename), $raw_output);
}';
    $fmt3 = 'function hmac_%1$s($str, $key, $raw_output = false) {
    $hmac = mhash(MHASH_%2$s, $str, $key);
    return ($raw_output) ? $hmac : bin2hex($hmac);
}';
    eval(sprintf($fmt1, $func, $algo));
    eval(sprintf($fmt2, $func));
    if (!in_array($algo, array('CRC32', 'CRC32B', 'GOST', 'ADLER32'))) {
        eval(sprintf($fmt3, $func, $algo));
    }
}

Mhash がサポートするすべてのハッシュ関数を定義するソースコードを生成する関数:

function mhash_generate_functions($eval = false) {
    static $called = false;
    if ($eval) {
        if ($called) {
            return;
        }
        $called = true;
    }
    $list = array();
    for ($i = 0; $i <= mhash_count(); $i++) {
        if ($name = mhash_get_hash_name($i)) {
            $list[$i] = $name;
        }
    }
    sort($list);
    $fmt1 = 'function %1$s($str, $raw_output = false) {
    $hash = mhash(MHASH_%2$s, $str);
    return ($raw_output) ? $hash : bin2hex($hash);
}';
    $fmt2 = 'function %1$s_file($filename, $raw_output = false) {
    return %1$s(file_get_contents($filename), $raw_output);
}';
    $fmt3 = 'function hmac_%1$s($str, $key, $raw_output = false) {
    $hmac = mhash(MHASH_%2$s, $str, $key);
    return ($raw_output) ? $hmac : bin2hex($hmac);
}';
    $code = '';
    foreach ($list as $algo) {
        $func = strtolower($algo);
        if (function_exists($func)) {
            $func = 'mhash_' . $func;
        }
        $code .= sprintf($fmt1, $func, $algo);
        $code .= "\n\n";
        $code .= sprintf($fmt2, $func);
        $code .= "\n\n";
        if (!in_array($algo, array('CRC32', 'CRC32B', 'GOST', 'ADLER32'))) {
            $code .= sprintf($fmt3, $func, $algo);
            $code .= "\n\n";
        }
    }
    if ($eval) {
        eval($code);
    } else {
        fwrite(STDOUT, '<' . '?php' . "\n\n" . $code . '?' . '>' . "\n");
    }
}

メソッドのオーバーロードアルゴリズムを選択する Mhash クラス:

class Mhash
{
    public function __call($name, $args)
    {
        $method = strtolower($name);
        $raw_output = false;
        if (preg_match('/^(hmac_)?([a-z][0-9a-z]+)(_file)?$/', $method, $matches)) {
            $algorithm = strtoupper($matches[2]);
            $is_hmac = !empty($matches[1]);
            $is_file = !empty($matches[3]);
        } else {
            $algorithm = strtoupper($name);
            $is_hmac = false;
            $is_file = false;
        }
        $constant = 'MHASH_' . $algorithm;
        if (!defined($constant) || ($is_hmac && in_array($algorithm, array('CRC32', 'CRC32B', 'GOST', 'ADLER32')))) {
            $errmsg = 'Call to undefined method Mhash::%s()';
            trigger_error(sprintf($errmsg, $method), E_USER_ERROR);
            return;
        }
        $required_argc = ($is_hmac) ? 2 : 1;
        $given_argc = count($args);
        if ($given_argc < $required_argc) {
            $errmsg = 'Mhash::%s() expects at least %d parameter, %d given';
            trigger_error(sprintf($errmsg, $method, $required_argc, $given_argc), E_USER_WARNING);
            return;
        }
        if (!is_scalar($args[0])) {
            $errmsg = 'Mhash::%s() expects parameter 1 to be string, %s given';
            trigger_error(sprintf($errmsg, $method, gettype($args[0])), E_USER_WARNING);
            return;
        }
        if ($is_hmac) {
            if (!is_scalar($args[1])) {
                $errmsg = 'Mhash::%s() expects parameter 2 to be string, %s given';
                trigger_error(sprintf($errmsg, $method, gettype($args[2])), E_USER_WARNING);
                return;
            }
            $key = $args[1];
            if (isset($args[2])) {
                if (!is_scalar($args[2])) {
                    $errmsg = 'Mhash::%s() expects parameter 3 to be boolean, %s given';
                    trigger_error(sprintf($errmsg, $method, gettype($args[2])), E_USER_WARNING);
                    return;
                }
                $raw_output = (bool) $args[2];
            }
        } else {
            if (isset($args[1])) {
                if (!is_scalar($args[1])) {
                    $errmsg = 'Mhash::%s() expects parameter 2 to be boolean, %s given';
                    trigger_error(sprintf($errmsg, $method, gettype($args[1])), E_USER_WARNING);
                    return;
                }
                $raw_output = (bool) $args[1];
            }
        }
        if ($is_file) {
            $file = $args[0];
            if (!file_exists($file)) {
                $errmsg = 'Mhash::%s(%s): failed to open stream: No such file or directory';
                trigger_error(sprintf($errmsg, $method, $file), E_USER_WARNING);
                return;
            } elseif (!is_readable($file)) {
                $errmsg = 'Mhash::%s(%s): failed to open stream: Permission denied';
                trigger_error(sprintf($errmsg, $method, $file), E_USER_WARNING);
                return;
            } else {
                $data = file_get_contents($file);
            }
        } else {
           $data = $args[0];
        }
        $type = eval("return $constant;");
        if ($is_hmac) {
            $hash = mhash($type, $data, $key);
        } else {
            $hash = mhash($type, $data);
        }
        return ($raw_output) ? $hash : bin2hex($hash);
    }
}

あと、先日のコード は 16進数を 32進数に変換しているだけなので

$dec = hexdec(substr($hex, $i, 5));
$o1 = floor($dec / 32768);
$o2 = floor(($dec % 32768) / 1024);
$o3 = floor(($dec % 1024) / 32);
$o4 = $dec % 32;
$hash .= $c[$o1] . $c[$o2] . $c[$o3] . $c[$o4];

より

$hash .= str_pad(base_convert(substr($hex, $i, 5), 16, 32), 4, '0', STR_PAD_LEFT);

の方がスマート。substr() を使わずに一気にやってしまいたいところだけど、大きい数値を base_convert() で処理しようとするとオーバーフローしてしまうようなので 20bit ずつ変換する点はそのまま。
別件でも感じたことだけど任意の整数を底にして対数をとることが出来なかったり(e と 10 だけ)、PHP の数値演算マジ貧弱。