PvPGN:Old hash

Материал из WikiServia
Перейти к: навигация, поиск

Эта функция - слегка модифицированый алгоритм SHA-1 (известный под названием broken SHA-1 или xsha1), который используется в Battle.Net играх до Warcraft 3. С не-SRP w3l лоадером для PvPGN 1.8.x он используется и в Warcraft 3. Поскольку PvPGN - это эмулятор Battle.Net, то эта функция, естественно, используется и в нем.

Пароли при хешировании дополнительно приводятся к нижнему регистру (безопасность это, естественно, не улучшает...), в результате хеш пароля выглядит так: xsha1(lowercase(ascii_password)). В базе PvPGN хранится в BNET\passhash1 в виде шестнадцатиричной строки.


Готовые реализации на разных языках можно скачать по ссылке http://harpywar.pvpgn.pl?do=hash

Содержание

PHP

pvpgn_hash.class.php

<?php
/**
* PHP implementation of the PvPGN Password Hash Algorithm.
* Copyright 2004 Aaron (aaron@pvpgn.org)
* 
* This code is available under the GNU Lesser General Public License:
* http://www.gnu.org/licenses/lgpl.txt

* This code in general is based on:
* Copyright 2002 - 2003 Marcus Campbell
* http://www.tecknik.net/sha-1/
* Based on the JavaScript SHA-1 implementation by Paul Johnston
* http://pajhome.org.uk/
* the safe_rol function is taken from an PHP SHA-1 implementation
* written by Chris Monson (chris@bouncingchairs.net)
* Most recent version available on http://bouncingchairs.net
* (Based on the SHA algorithm as given in "Applied Cryptography")
*/
final class pvpgn_hash {
    private static function str2blks_pvpgn($str) {
        $nblk = ((strlen($str) + 8) >> 6) + 1;
        for($i = 0; $i < $nblk * 16; $i++) {
            $blks[$i] = 0;
        }
        for($i = 0; $i < strlen($str); $i++) {
            $blks[$i >> 2] |= ord($str[$i]) << (($i % 4) * 8);
        }
        return $blks;
    }

    private static function safe_add($x,$y) {
        $lsw = ($x & 0xFFFF) + ($y & 0xFFFF);
        $msw = ($x >> 16) + ($y >> 16) + ($lsw >> 16);
        return ($msw << 16) | ($lsw & 0xFFFF);
    }

    private static function safe_not($num) {
        $lsw = (~($num & 0xFFFF)) & 0xFFFF;
        $msw = (~($num >> 16)) & 0xFFFF;
        return ($msw << 16) | $lsw;
    }

    private static function safe_rol($num,$amt) {
        $leftmask = 0xffff | (0xffff << 16);
        $leftmask <<= 32 - $amt;
        $rightmask = 0xffff | (0xffff << 16);
        $rightmask <<= $amt;
        $rightmask = self::safe_not($rightmask);

        $remains = $num & $leftmask;
        $remains >>= 32 - $amt;
        $remains &= $rightmask;

        $res = ($num << $amt) | $remains;
        return $res;
    }

    private static function ft($t,$b,$c,$d) {
        if($t < 20) {
            return ($b & $c) | ((self::safe_not($b)) & $d);
        }
        if($t < 40) {
            return $d ^ $c ^ $b;
        }
        if($t < 60) {
            return ($c & $b) | ($d & $c) | ($d & $b);
        }
        return $d ^ $c ^ $b;
    }

    private static function kt($t) {
        if($t < 20) {
            return 0x5a82 << 16 | 0x7999;
        }
        elseif($t < 40) {
            return 0x6ed9 << 16 | 0xeba1;
        }
        elseif($t < 60) {
            return 0x8f1b << 16 | 0xbcdc;
        }
        else {
            return 0xca62 << 16 | 0xc1d6;
        }
    }

    public static function get_hash($str) {
		// Unicode support fixed by Naki-BoT
        for($i = 0;$i < strlen($str);$i++) {
			// PvPGN hash is case insensitive but only for ASCII characters
            if(ord($str[$i]) < 128) {
                $str[$i] = strtolower($str[$i]);
            }
        }

        $x = self::str2blks_pvpgn($str);

        $a = 0x6745 << 16 | 0x2301;
        $b = 0xefcd << 16 | 0xab89;
        $c = 0x98ba << 16 | 0xdcfe;
        $d = 0x1032 << 16 | 0x5476;
        $e = 0xc3d2 << 16 | 0xe1f0;    

        for($i = 0; $i < count($x); $i += 16) {
            $olda = $a;
            $oldb = $b;
            $oldc = $c;
            $oldd = $d;
            $olde = $e;

            for($j = 0; $j < 16; $j++) {
                $w[$j] = $x[$i + $j];
            }

            for($j = 0; $j < 64; $j++) {
                $ww = $w[$j] ^ $w[$j + 8] ^ $w[$j + 2] ^ $w[$j + 13];
                $w[$j + 16] = 1 << ($ww % 32);
            }

            for($j = 0; $j < 80; $j++) {
                if($j < 20) {
                    $t = self::safe_add(self::safe_add(self::safe_rol($a,5),
                            self::ft($j,$b,$c,$d)),self::safe_add(self::safe_add($e,$w[$j]),self::kt($j)));
                }
                else {
                    $t = self::safe_add(self::safe_add(self::safe_rol($t,5),
                            self::ft($j,$b,$c,$d)),self::safe_add(self::safe_add($e,$w[$j]),self::kt($j)));
                }

                $e = $d;
                $d = $c;
                $c = self::safe_rol($b,30);
                $b = $a;
                $a = $t;
            }

            // Fix for 64-bit OS by Pada
            $a = (self::safe_add($t,$olda) & 0xffffffff);
            $b = (self::safe_add($b,$oldb) & 0xffffffff);
            $c = (self::safe_add($c,$oldc) & 0xffffffff);
            $d = (self::safe_add($d,$oldd) & 0xffffffff);
            $e = (self::safe_add($e,$olde) & 0xffffffff);
        }
        return sprintf("%08x%08x%08x%08x%08x",$a,$b,$c,$d,$e);
    }
}
?>

Пример использования этого класса (работает только в PHP5 и выше):

require_once("pvpgn_hash.class.php"); 

$pass = "12345";
$hash = pvpgn_hash::get_hash($pass);
echo $hash; // выведет 460e0af6c1828a93fe887cbe103d6ca6ab97a0e4

JavaScript

/**
* JavaScript implementation of the PvPGN Password Hash Algorithm.
* Copyright 2011 Naki-BoT (naki@pvpgn.pl)
* http://naki.info/
* 
* This code is available under the GNU Lesser General Public License:
* http://www.gnu.org/licenses/lgpl.txt
* 
* This code in general is based on:
* Copyright 2004 Aaron (aaron@pvpgn.org)
* PHP implementation of the PvPGN Password Hash Algorithm
* Copyright 2002 - 2003 Marcus Campbell
* http://www.tecknik.net/sha-1/
* Based on the JavaScript SHA-1 implementation by Paul Johnston
* http://pajhome.org.uk/
* the safe_rol function is taken from an PHP SHA-1 implementation
* written by Chris Monson (chris@bouncingchairs.net)
* Most recent version available on http://bouncingchairs.net
* (Based on the SHA algorithm as given in "Applied Cryptography")
*/
var pvpgn_hash = function() {
    var ord_unicode = function(chr) {
        // JavaScript charCodeAt() function returns Unicode of the character
        unicode = chr.charCodeAt(0);
        if(unicode <= 0x7F) {
            return [unicode];
        }
        else if(unicode < 0x80) {
            return false;
        }
        else if(unicode <= 0x7FF) {
            binary = str_pad_left(decbin(unicode),11);
            return [
                bindec("110" + binary.substr(0,5)),
                bindec("10" + binary.substr(5))
            ];
        }
        else if(unicode <= 0xFFFF) {
            binary = str_pad_left(decbin(unicode),16);
            return [
                bindec("1110" + binary.substr(0,4)),
                bindec("10" + binary.substr(4,6)),
                bindec("10" + binary.substr(10))
            ];
        }
        else if(unicode <= 0x1FFFFF) {
            binary = str_pad_left(decbin(unicode),21);
            return [
                bindec("11110" + binary.substr(0,3)),
                bindec("10" + binary.substr(3,6)),
                bindec("10" + binary.substr(9,6)),
                bindec("10" + binary.substr(15))
            ];
        }
        else {
            return false;
        }
    };
    var str_pad_left = function(number,length) {
        if(number.length < length) {
            for(var i = number.length;i < length;i++) {
                number = "0" + number;
            }
        }
        return number;
    };
    var str2blks_pvpgn = function(str) {
        nblk = ((str.length + 8) >> 6) + 1;
        var blks = [];
        for(var i = 0; i < nblk * 16; i++) {
            blks[i] = 0;
        }
        for(var i = 0,j = 0; i < str.length; i++) {
            var unicode = ord_unicode(str.charAt(i));
            if(typeof unicode === "object") {
                for(var k = 0; k < unicode.length; k++) {
                    blks[j >> 2] |= unicode[k] << ((j % 4) * 8);
                    j++;
                }
            }
        }
        return blks;
    };
    var safe_add = function(x,y) {
        lsw = (x & 0xFFFF) + (y & 0xFFFF);
        msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    };
    var safe_not = function(num) {
        lsw = (~(num & 0xFFFF)) & 0xFFFF;
        msw = (~(num >> 16)) & 0xFFFF;
        return (msw << 16) | lsw;
    };
    var safe_rol = function(num,amt) {
        leftmask = 0xffff | (0xffff << 16);
        leftmask <<= 32 - amt;
        rightmask = 0xffff | (0xffff << 16);
        rightmask <<= amt;
        rightmask = safe_not(rightmask);

        remains = num & leftmask;
        remains >>= 32 - amt;
        remains &= rightmask;

        res = (num << amt) | remains;
        return res;
    };
    var ft = function(t,b,c,d) {
        if(t < 20) {
            return (b & c) | ((safe_not(b)) & d);
        }
        if(t < 40) {
            return d ^ c ^ b;
        }
        if(t < 60) {
            return (c & b) | (d & c) | (d & b);
        }
        return d ^ c ^ b;
    };
    var kt = function(t) {
        if(t < 20) {
            return 0x5a82 << 16 | 0x7999;
        }
        else if(t < 40) {
            return 0x6ed9 << 16 | 0xeba1;
        }
        else if(t < 60) {
            return 0x8f1b << 16 | 0xbcdc;
        }
        else {
            return 0xca62 << 16 | 0xc1d6;
        }
    };
    var pvpgn_hash = function(str,decode_html_entities) {
        // Convert special HTML entities back to characters: ">" -> ">" etc.
        if(decode_html_entities) {
            str = htmlspecialchars_decode(str,"ENT_QUOTES");
        }

        // Unicode support
        for(var i = 0;i < str.length; i++) {
            // PvPGN hash is case insensitive but only for ASCII characters
            if(str.charCodeAt(i) < 128) {
                str = str.substr(0,i) + str.charAt(i).toLowerCase() + str.substr(i + 1);
            }
        }

        x = str2blks_pvpgn(str);

        a = 0x6745 << 16 | 0x2301;
        b = 0xefcd << 16 | 0xab89;
        c = 0x98ba << 16 | 0xdcfe;
        d = 0x1032 << 16 | 0x5476;
        e = 0xc3d2 << 16 | 0xe1f0;    

        for(var i = 0; i < x.length; i += 16) {
            olda = a;
            oldb = b;
            oldc = c;
            oldd = d;
            olde = e;

            var w = [];
            for(var j = 0; j < 16; j++) {
                w[j] = x[i + j];
            }

            for(var j = 0; j < 64; j++) {
                ww = w[j] ^ w[j + 8] ^ w[j + 2] ^ w[j + 13];
                w[j + 16] = 1 << (ww % 32);
            }

            for(var j = 0; j < 80; j++) {
                if(j < 20) {
                    t = safe_add(safe_add(safe_rol(a,5),ft(j,b,c,d)),safe_add(safe_add(e,w[j]),kt(j)));
                }
                else {
                    t = safe_add(safe_add(safe_rol(t,5),ft(j,b,c,d)),safe_add(safe_add(e,w[j]),kt(j)));
                }

                e = d;
                d = c;
                c = safe_rol(b,30);
                b = a;
                a = t;
            }

            a = safe_add(t,olda);
            b = safe_add(b,oldb);
            c = safe_add(c,oldc);
            d = safe_add(d,oldd);
            e = safe_add(e,olde);
        }

        // Fix for 64-bit OS by Pada
        a = str_pad_left(dechex(a & 0xffffffff),8);
        b = str_pad_left(dechex(b & 0xffffffff),8);
        c = str_pad_left(dechex(c & 0xffffffff),8);
        d = str_pad_left(dechex(d & 0xffffffff),8);
        e = str_pad_left(dechex(e & 0xffffffff),8);
        return a + b + c + d + e;
    };
    // http://kevin.vanzonneveld.net
    var htmlspecialchars_decode = function(string,quote_style) {
        var optTemp = 0, i = 0, noquotes= false;
        if(typeof quote_style === "undefined") {
            quote_style = 2;
        }
        string = string.toString().replace(/</g,"<").replace(/>/g,">");
        var OPTS = {
            'ENT_NOQUOTES': 0,
            'ENT_HTML_QUOTE_SINGLE' : 1,
            'ENT_HTML_QUOTE_DOUBLE' : 2,
            'ENT_COMPAT': 2,
            'ENT_QUOTES': 3,
            'ENT_IGNORE' : 4
        };
        if(quote_style === 0) {
            noquotes = true;
        }
        if(typeof quote_style !== "number") {
            quote_style = [].concat(quote_style);
            for(i = 0; i < quote_style.length; i++) {
                // Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
                if (OPTS[quote_style[i]] === 0) {
                    noquotes = true;
                }
                else if (OPTS[quote_style[i]]) {
                    optTemp = optTemp | OPTS[quote_style[i]];
                }
            }
            quote_style = optTemp;
        }
        if(quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) {
             // PHP doesn't currently escape if more than one 0, but it should
            string = string.replace(/&#0*39;/g,"'");
            // This would also be useful here, but not a part of PHP
            // string = string.replace(/'|&#x0*27;/g,"'");
        }
        if(!noquotes) {
            string = string.replace(/"/g,'"');
        }
        // Put this in last place to avoid escape being double-decoded
        string = string.replace(/&/g,"&");

        return string;
    };
    // http://kevin.vanzonneveld.net
    var decbin = function(number) {
        if(number < 0) {
            number = 0xFFFFFFFF + number + 1;
        }
        return parseInt(number, 10).toString(2);
    };
    // http://kevin.vanzonneveld.net
    var bindec = function(binary_string) {
        binary_string = (binary_string + "").replace(/[^01]/gi,"");
        return parseInt(binary_string, 2);
    };
    // http://kevin.vanzonneveld.net
    var dechex = function(number) {
        if(number < 0) {
            number = 0xFFFFFFFF + number + 1;
        }
        return parseInt(number,10).toString(16);
    };
    return {
        get_hash: function(str,decode_html_entities) {
            return pvpgn_hash(str,decode_html_entities);
        }
    };
}();

C#

using System;
using System.IO;
using System.Text;

/**
* C# implementation of the PvPGN Password Hash Algorithm.
* Copyright 2011 HarpyWar (harpywar@gmail.com)
* http://harpywar.com
*
* Based on the MBNCSUtil XSha function
* This code is available under the GNU Lesser General Public License:
* http://www.gnu.org/licenses/lgpl.txt
*/

/// <summary>
/// Provides an implementation of Battle.net's "broken" (nonstandard) SHA-1 
/// implementation.  This class cannot be inherited.
/// </summary>
/// <remarks>
/// This class does not derive from the standard
/// .NET <see cref="System.Security.Cryptography.SHA1">SHA1</see>
/// class, and also does not provide adequate security for independent
/// security solutions.  See the System.Security.Cryptography 
/// namespace for more information.
/// </remarks>
/// <threadsafety>This type is safe for multithreaded operations.</threadsafety>
public static class PvpgnHash
{
    /**
     * Returns the 20 byte hash based on the passed in byte[] data.
     *
     * @param pass The data to hash.
     * @return The 20 bytes of hashed data.
     */
    public static byte[] GetHash(byte[] pass)
    {
        String tmp = Encoding.UTF8.GetString(pass);
        return CalculateHash(tmp);
    }

    /**
     * Returns hash based on the passed in String data.
     *
     * @param pass The data to hash.
     * @return The 40 symbols hex String of hashed data.
     */
    public static String GetHash(String pass)
    {
        byte[] tmp = CalculateHash(pass);
        return asHex(tmp);
    }


    /// <summary>
    /// Calculates the "broken" SHA-1 hash used by Battle.net.
    /// </summary>
    /// <param name="data">The data to hash.</param>
    /// <returns>A 20-byte array containing the hashed result.</returns>
    private static byte[] CalculateHash(string data)
    {
        byte[] input = Encoding.UTF8.GetBytes(toLowerUnicode(data).ToCharArray());

        if (input.Length > 1024) throw new ArgumentOutOfRangeException("data", "The input size must be less than 1024 bytes.");

        return SafeHash(input);
    }

    private static byte[] SafeHash(byte[] input)
    {
        byte[] data = new byte[1024];
        Array.Copy(input, 0, data, 0, input.Length);

        int i;
        MemoryStream mdata = new MemoryStream(data, true);
        BinaryReader br = new BinaryReader(mdata);
        BinaryWriter bw = new BinaryWriter(mdata);

        uint a, b, c, d, e, g;

        for (i = 0; i < 64; i++)
        {
            mdata.Seek((i * 4), SeekOrigin.Begin);
            // mdata now at ldata[i]
            uint expr_ldata_i = br.ReadUInt32();
            // mdata now at ldata[i+1]
            mdata.Seek(1 * 4, SeekOrigin.Current);
            // mdata now at ldata[i+2]
            uint expr_ldata_i_2 = br.ReadUInt32();
            // mdata now at ldata[i+3]
            mdata.Seek(5 * 4, SeekOrigin.Current);
            // mdata now at ldata[i+8]
            uint expr_ldata_i_8 = br.ReadUInt32();
            // mdata now at ldata[i+9]
            mdata.Seek(4 * 4, SeekOrigin.Current);
            // mdata now at ldata[i+13]
            uint expr_ldata_i_13 = br.ReadUInt32();
            // mdata now at ldata[i+14]
            int shiftVal = (int)((expr_ldata_i ^ expr_ldata_i_8 ^ expr_ldata_i_2 ^
                expr_ldata_i_13) & 0x1f);
            mdata.Seek(2 * 4, SeekOrigin.Current);
            // mdata now at ldata[i+16]
            bw.Write(ROL(1, shiftVal));
        }

        a = 0x67452301;
        b = 0xefcdab89;
        c = 0x98badcfe;
        d = 0x10325476;
        e = 0xc3d2e1f0;
        g = 0;

        mdata.Seek(0, SeekOrigin.Begin);

        // loop 1
        for (i = 0; i < 20; i++)
        {
            g = br.ReadUInt32() + ROL(a, 5) + e + ((b & c) | (~b & d)) + 0x5A827999;
            e = d;
            d = c;
            c = ROL(b, 30);
            b = a;
            a = g;
        }

        // loop 1
        for (i = 0; i < 20; i++)
        {
            g = (d ^ c ^ b) + e + ROL(g, 5) + br.ReadUInt32() + 0x6ed9eba1;
            e = d;
            d = c;
            c = ROL(b, 30);
            b = a;
            a = g;
        }

        // loop 3
        for (i = 0; i < 20; i++)
        {
            g = br.ReadUInt32() + ROL(g, 5) + e + ((c & b) | (d & c) | (d & b)) - 0x70E44324;
            e = d;
            d = c;
            c = ROL(b, 30);
            b = a;
            a = g;
        }

        // loop 4
        for (i = 0; i < 20; i++)
        {
            g = (d ^ c ^ b) + e + ROL(g, 5) + br.ReadUInt32() - 0x359d3e2a;
            e = d;
            d = c;
            c = ROL(b, 30);
            b = a;
            a = g;
        }

        br.Close();
        bw.Close();
        mdata.Close();

        byte[] result = new byte[20];
        mdata = new MemoryStream(result, 0, 20, true, true);
        bw = new BinaryWriter(mdata);
        unchecked
        {
            bw.Write(ReverseBytes(0x67452301 + a));
            bw.Write(ReverseBytes(0xefcdab89 + b));
            bw.Write(ReverseBytes(0x98badcfe + c));
            bw.Write(ReverseBytes(0x10325476 + d));
            bw.Write(ReverseBytes(0xc3d2e1f0 + e));
        }

        mdata.Close();
        bw.Close();

        return result;
    }

    private static uint ROL(uint val, int shift)
    {
        shift &= 0x1f;
        val = (val >> (0x20 - shift)) | (val << shift);
        return val;
    }


    // reverse byte order (16-bit)
    public static UInt16 ReverseBytes(UInt16 value)
    {
        return (UInt16)((value & 0xFFU) << 8 | (value & 0xFF00U) >> 8);
    }
    // reverse byte order (32-bit)
    public static UInt32 ReverseBytes(UInt32 value)
    {
        return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
               (value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
    }
    // reverse byte order (64-bit)
    public static UInt64 ReverseBytes(UInt64 value)
    {
        return (value & 0x00000000000000FFUL) << 56 | (value & 0x000000000000FF00UL) << 40 |
               (value & 0x0000000000FF0000UL) << 24 | (value & 0x00000000FF000000UL) << 8 |
               (value & 0x000000FF00000000UL) >> 8 | (value & 0x0000FF0000000000UL) >> 24 |
               (value & 0x00FF000000000000UL) >> 40 | (value & 0xFF00000000000000UL) >> 56;
    }

    /***
     * Converts byte array to hex string
     * @param buf
     * @return
     */
    private static String asHex(byte[] buf)
    {
        string hex = BitConverter.ToString(buf).Replace("-", string.Empty).ToLower();
        return hex;
    }

    /***
     * PvPGN hash is case insensitive but only for ASCII characters
     * @param str
     * @return
     */
    private static String toLowerUnicode(String str)
    {
        char c = new char();
        for (int i = 0; i < str.Length; i++)
        {
            c = str[i];
            if (c < 128)
            {
                str = str.Substring(0, i) + c.ToString().ToLower() + str.Substring(i + 1);
            }
        }
        return str;
    }

}

Пример использования:

String pass = "12345";
String hash = PvpgnHash.GetHash(pass);

// print 460e0af6c1828a93fe887cbe103d6ca6ab97a0e4
Console.WriteLine(hash);


Java

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays;

/**
* Java implementation of the PvPGN Password Hash Algorithm.
* Copyright 2011 HarpyWar (harpywar@gmail.com)
* http://harpywar.com
*
* This code is available under the GNU Lesser General Public License:
* http://www.gnu.org/licenses/lgpl.txt
*
* This code in general is based on:
* Copyright 2004 Aaron (aaron@pvpgn.org)
* PHP implementation of the PvPGN Password Hash Algorithm
* Copyright 2002 - 2003 Marcus Campbell
* http://www.tecknik.net/sha-1/
* Based on the JavaScript SHA-1 implementation by Paul Johnston
* http://pajhome.org.uk/
* the safe_rol function is taken from an PHP SHA-1 implementation
* written by Chris Monson (chris@bouncingchairs.net)
* Most recent version available on http://bouncingchairs.net
* (Based on the SHA algorithm as given in "Applied Cryptography")
*/
public final class PvpgnHash
{
    /**
     * Returns the 20 byte hash based on the passed in byte[] data.
     *
     * @param pass The data to hash.
     * @return The 20 bytes of hashed data.
     */
    public static byte[] GetHash(byte[] pass)
    {
        String tmp = new String(pass);
        return pvpgn_hash(tmp);
    }

    /**
     * Returns hash based on the passed in String data.
     *
     * @param pass The data to hash.
     * @return The 40 symbols hex String of hashed data.
     */
    public static String GetHash(String pass)
    {
        byte[] tmp = pvpgn_hash(pass);
        return asHex(tmp);
    }
    
    /**
     * Calculates the 20 byte hash based on the passed in byte[] data.
     *
     * @param pass The data to hash.
     * @return The 20 bytes of hashed data.
     */
    private static byte[] pvpgn_hash(String pass)
    {
        byte[] input = toLowerUnicode(pass).getBytes();
        
        if (input.length > 1024)
        {
            throw new IllegalArgumentException("The input size must be less than 1024 bytes.");
        }

        byte[] data = Arrays.copyOf(input, 1024);

        ByteBuffer dataByteBuf = ByteBuffer.wrap(data);
        dataByteBuf.order(ByteOrder.LITTLE_ENDIAN);
        IntBuffer dataBuf = dataByteBuf.asIntBuffer();

        for (int i = 0; i < 64; i++)
        {
            int xor = dataBuf.get(i) ^ dataBuf.get(i + 8) ^ dataBuf.get(i + 2)
                              ^ dataBuf.get(i + 13);
            int shiftVal = xor % 32;
            dataBuf.put(i + 16, rol_safe(1, shiftVal));
        }

        int a = 0x67452301;
        int b = 0xefcdab89;
        int c = 0x98badcfe;
        int d = 0x10325476;
        int e = 0xc3d2e1f0;
        int g = 0;

        /* Loop 1 - Let the compiler optimize this. */
        for (int i = 0; i < 20; i++)
        {
            g = dataBuf.get() + rol_safe(a, 5) + e + ((b & c) | (~b & d)) + 0x5A827999;
            e = d;
            d = c;
            c = rol_safe(b, 30);
            b = a;
            a = g;
        }

        /* Loop 2 - Let the compiler optimize this. */
        for (int i = 0; i < 20; i++)
        {
            g = (d ^ c ^ b) + e + rol_safe(g, 5) + dataBuf.get() + 0x6ed9eba1;
            e = d;
            d = c;
            c = rol_safe(b, 30);
            b = a;
            a = g;
        }

        /* Loop 3 - Let the compiler optimize this. */
        for (int i = 0; i < 20; i++)
        {
            g = dataBuf.get() + rol_safe(g, 5) + e + ((c & b) | (d & c) | (d & b)) - 0x70E44324;
            e = d;
            d = c;
            c = rol_safe(b, 30);
            b = a;
            a = g;
        }

        /* Loop 4 - Let the compiler optimize this. */
        for (int i = 0; i < 20; i++)
        {
            g = (d ^ c ^ b) + e + rol_safe(g, 5) + dataBuf.get() - 0x359d3e2a;
            e = d;
            d = c;
            c = rol_safe(b, 30);
            b = a;
            a = g;
        }

        byte[] result = new byte[20];
        ByteBuffer resultByteBuf = ByteBuffer.wrap(result);
        resultByteBuf.order(ByteOrder.BIG_ENDIAN);
        IntBuffer resultBuf = resultByteBuf.asIntBuffer();
        resultBuf.put((int)(0x67452301 + a));
        resultBuf.put((int)(0xefcdab89 + b));
        resultBuf.put((int)(0x98badcfe + c));
        resultBuf.put((int)(0x10325476 + d));
        resultBuf.put((int)(0xc3d2e1f0 + e));

        return result;
    }


    private static int rol_safe(int num, int amt) {
        int leftmask = 0xffff | (0xffff << 16);
        leftmask <<= 32 - amt;
        int rightmask = 0xffff | (0xffff << 16);
        rightmask <<= amt;
        rightmask = safe_not(rightmask);

        int remains = num & leftmask;
        remains >>= 32 - amt;
        remains &= rightmask;

        int res = (num << amt) | remains;
        return res;
    }

    private static int safe_not(int num) {
        int lsw = (~(num & 0xFFFF)) & 0xFFFF;
        int msw = (~(num >> 16)) & 0xFFFF;
        return (msw << 16) | lsw;
    }

    private static int ROL32(int x, int r) {
        return (x << r) | (x >>> (32 - r));
    }

    /***
     * Converts byte array to hex string
     * @param buf
     * @return
     */
    private static String asHex(byte[] buf)
    {
        String s = new BigInteger(1, buf).toString(16);
        return (s.length() % 2 == 0) ? s : "0" + s;
    }

    /***
     * PvPGN hash is case insensitive but only for ASCII characters
     * @param str
     * @return
     */
    private static String toLowerUnicode(String str)
    {
        for(int i = 0; i < str.length(); i++) {
            if(str.codePointAt(i) < 128) {
                str = str.substring(0, i) + Character.toString(str.charAt(i)).toLowerCase() + str.substring(i + 1);
            }
        }
        return str;
    }
}

Пример использования:

String pass = "12345";
String hash = PvpgnHash.GetHash(pass);

// print 460e0af6c1828a93fe887cbe103d6ca6ab97a0e4
System.out.println(hash);



C

Official PvPGN code (/src/common/bnethash.c)

/*
  * Copyright (C) 1999  Descolada (dyn1-tnt9-237.chicago.il.ameritech.net)
  * Copyright (C) 1999,2000  Ross Combs (rocombs@cs.nmsu.edu)
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * 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 General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/


static void hash_init(t_hash * hash);
static void do_hash(t_hash * hash, t_uint32 * tmp);
static void hash_set_16(t_uint32 * dst, unsigned char const * src, unsigned int count);


static void hash_init(t_hash * hash)
{
    (*hash)[0] = 0x67452301;
    (*hash)[1] = 0xefcdab89;
    (*hash)[2] = 0x98badcfe;
    (*hash)[3] = 0x10325476;
    (*hash)[4] = 0xc3d2e1f0;
}


static void do_hash(t_hash * hash, t_uint32 * tmp)
{
    unsigned int i;
    t_uint32     a,b,c,d,e,g;
    
    for (i=0; i<64; i++)
	tmp[i+16] = ROTL32(1,tmp[i] ^ tmp[i+8] ^ tmp[i+2] ^ tmp[i+13]);
    
    a = (*hash)[0];
    b = (*hash)[1];
    c = (*hash)[2];
    d = (*hash)[3];
    e = (*hash)[4];
    
    for (i=0; i<20*1; i++)
    {
	g = tmp[i] + ROTL32(a,5) + e + ((b & c) | (~b & d)) + 0x5a827999;
	e = d;
	d = c;
	c = ROTL32(b,30);
	b = a;
	a = g;
    }
    
    for (; i<20*2; i++)
    {
	g = (d ^ c ^ b) + e + ROTL32(g,5) + tmp[i] + 0x6ed9eba1;
	e = d;
	d = c;
	c = ROTL32(b,30);
	b = a;
	a = g;
    }
    
    for (; i<20*3; i++)
    {
	g = tmp[i] + ROTL32(g,5) + e + ((c & b) | (d & c) | (d & b)) - 0x70e44324;
	e = d;
	d = c;
	c = ROTL32(b,30);
	b = a;
	a = g;
    }
    
    for (; i<20*4; i++)
    {
	g = (d ^ c ^ b) + e + ROTL32(g,5) + tmp[i] - 0x359d3e2a;
	e = d;
	d = c;
	c = ROTL32(b,30);
	b = a;
	a = g;
    }
    
    (*hash)[0] += g;
    (*hash)[1] += b;
    (*hash)[2] += c;
    (*hash)[3] += d;
    (*hash)[4] += e;
}


/*
 * Fill 16 elements of the array of 32 bit values with the bytes from
 * dst up to count in little endian order. Fill left over space with
 * zeros
 */
static void hash_set_16(t_uint32 * dst, unsigned char const * src, unsigned int count)
{
    unsigned int i;
    unsigned int pos;
    
    for (pos=0,i=0; i<16; i++)
    {
	dst[i] = 0;
        if (pos<count)
	    dst[i] |= ((t_uint32)src[pos]);
	pos++;
        if (pos<count)
	    dst[i] |= ((t_uint32)src[pos])<<8;
	pos++;
        if (pos<count)
	    dst[i] |= ((t_uint32)src[pos])<<16;
	pos++;
        if (pos<count)
	    dst[i] |= ((t_uint32)src[pos])<<24;
	pos++;
    }
}


extern int bnet_hash(t_hash * hashout, unsigned int size, void const * datain)
{
    t_uint32              tmp[64+16];
    unsigned char const * data;
    unsigned int          inc;
    
    if (!hashout || !*hashout)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL hashout");
	return -1;
    }
    if (size>0 && !datain)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL datain with size=%u",size);
	return -1;
    }
    
    hash_init(hashout);
    
    data = datain;
    while (size>0)
    {
	if (size>64)
	    inc = 64;
	else
	    inc = size;
	
	hash_set_16(tmp,data,inc);
	do_hash(hashout,tmp);
	
	data += inc;
	size -= inc;
    }
    
    return 0;
}


extern int hash_eq(t_hash const h1, t_hash const h2)
{
    unsigned int i;
    
    if (!h1 || !h2)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL hash");
	return -1;
    }
    
    for (i=0; i<5; i++)
	if (h1[i]!=h2[i])
	    return 0;
    
    return 1;
}


extern char const * hash_get_str(t_hash const hash)
{
    static char  temp[8*5+1]; /* each of 5 ints to 8 chars + null */
    unsigned int i;
    
    if (!hash)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL hash");
	return NULL;
    }
    
    for (i=0; i<5; i++)
        sprintf(&temp[i*8],"%08x",hash[i]);
    
    return temp;
}


extern int hash_set_str(t_hash * hash, char const * str)
{
    unsigned int i;
    
    if (!hash)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL hash pointer");
        return -1;
    }
    if (!*hash)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL hash");
        return -1;
    }
    if (!str)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got NULL str");
        return -1;
    }
    if (strlen(str)!=5*8)
    {
	eventlog(eventlog_level_error,__FUNCTION__,"got string with length %u (should be %u)",strlen(str),5*8);
        return -1;
    }
    
    for (i=0; i<5; i++)
        if (sscanf(&str[i*8],"%8x",&(*hash)[i])!=1)
	{
	    eventlog(eventlog_level_error,__FUNCTION__,"got bad string");
	    return -1;
	}
    
    return 0;
}