Valhalla Legends Forums Archive | Battle.net Bot Development | [python] someone done password hashing? (0x3a)

AuthorMessageTime
aton
i am looking for the python code.

(before a flaming starts again, this is just a kind question, i am not expecting anyone to do anything for me. if nobody has done this yet, i will convert some c code on my own. no offense.)
June 29, 2009, 5:45 PM
HdxBmx27
Don't you already have a XSHA-1 Implementation in python?
It's not hard to hash the password:
XSHA1(ClientToken, ServerToken, XSHA1(Password)) [It may be Server, then client, but meh]
No conversion of anything needed just implementation.
June 29, 2009, 7:51 PM
aton
ah okay i was just looking at the bncsutil source and realised that its the same old bsha1 ;)
June 29, 2009, 9:32 PM
aton
my conversion of the bncsutil function:
[code]
import struct
import bsha1

def doubleHashPassword(password, clienttoken, servertoken):
    b1=bsha1.bsha()
    b1.update(password)
    b2=bsha1.bsha()
    b2.update(struct.pack("I", clienttoken)+struct.pack("I", servertoken)+b1.digest())
    return b2.digest()
[/code]

works with the bsha1 module i posted here:

https://davnit.net/bnet/vL/index.php?topic=17932.0
June 30, 2009, 11:32 AM
xpeh
Will some1 release the ready module? Even beta.
June 30, 2009, 11:50 PM
aton
i just posted a link to the ready module...
July 1, 2009, 1:00 AM
xpeh
i am sorry, but i dont see any download link :)

Can you post ready downloadable module with some examples?
July 1, 2009, 7:49 AM
xpeh
Actually, do some1 want to make global lib like BNCSUtil/MBNCSUtil, that can do almost anything, in python?
July 1, 2009, 7:53 AM
ThePro
[quote author=aton link=topic=17995.msg182996#msg182996 date=1246361547]
my conversion of the bncsutil function:
[code]
import struct
import bsha1

def doubleHashPassword(password, clienttoken, servertoken):
    b1=bsha1.bsha()
    b1.update(password)
    b2=bsha1.bsha()
    b2.update(struct.pack("I", clienttoken)+struct.pack("I", servertoken)+b1.digest())
    return b2.digest()
[/code]

works with the bsha1 module i posted here:

https://davnit.net/bnet/vL/index.php?topic=17932.0
[/quote]

Hm, this doesn't work for me.
That what I've done:
[code]
import crypt
import struct

b1 = crypt.bsha()
b1.update(password.lower())
b2 = crypt.bsha()
b2.update(struct.pack("I", client_token) + struct.pack("I", server_token) + b1.digest()
password_hash = b2.digest()

[/code]

I get a hash with a length of 20, but it is different from the hash of BNLS, so I always get an invalid password response from bnet.

My hash:
ClientToken: 45038296
ServerToken: 257613900
Password: funnypw

Hash from lib (which is wrong): 2bce05fd6ce14a7dc726b1a81fc9eb63a87c00dd
Aton, did you login successfully with that?
Do you get the same wrong hash?
If no, any Ideas what I made wrong?
July 5, 2009, 10:16 PM
HdxBmx27
Using those test values the final hash should be:
DF63B84BC7B60DAD0EFFBFD3094C6C376949CBE3
The first hash of the password should be:
DBF280AC6C8D1B82F4D4B54EFC6B024704A9B8E1

Can you confirm that the 1st hash is the same, if not it's the SHA1 at fault, if it is the same then look into pack (though that *should* work)
July 5, 2009, 11:31 PM
ThePro
[quote author=Hdx link=topic=17995.msg183039#msg183039 date=1246836714]
Using those test values the final hash should be:
DF63B84BC7B60DAD0EFFBFD3094C6C376949CBE3
The first hash of the password should be:
DBF280AC6C8D1B82F4D4B54EFC6B024704A9B8E1

Can you confirm that the 1st hash is the same, if not it's the SHA1 at fault, if it is the same then look into pack (though that *should* work)
[/quote]

Nope, its not:
[code]
b1 = crypt.bsha(True) #broken=True -> bsha1
b1.update("funnypw")
b1.hexdigest()
>>>  'ac80f2db821b8d6c4eb5d4f447026bfce1b8a904'
#Which is wrong. Is has to be DBF280AC6C8D1B82F4D4B54EFC6B024704A9B8E1

b1 = crypt.bsha(False) #broken=False -> sha1
b1.update("funnypw")
b1.hexdigest()

>>> '20edff4fc4edcc25e85b3f4e867008fb8ef8dac2'
#Which is correct, as you can see here: http://www.tech-faq.com/sha-1-generator.shtml
[/code]

It seems this lib doesen't work. Since aton was releasing the module I'm sure it was working for him, so thats the confisuing part o_O.
I'm using an intel 32Bit core dou processor, maybe he wrote this lib for a 64Bit processor!?

Here's the code of Atons pythonmodule I used:
[code]
import struct
import socket

def _long2bytesBigEndian(n, blocksize=0):
    """Convert a long integer to a byte string.

    If optional blocksize is given and greater than zero, pad the front
    of the byte string with binary zeros so that the length is a multiple
    of blocksize.
    """

    # After much testing, this algorithm was deemed to be the fastest.
    s = ''
    pack = struct.pack
    while n > 0:
        s = pack('>I', n & 0xffffffffL) + s
        n = n >> 32

    # Strip off leading zeros.
    for i in range(len(s)):
        if s[i] <> '\000':
            break
    else:
        # Only happens when n == 0.
        s = '\000'
        i = 0

    s = s[i:]

    # Add back some pad bytes. This could be done more efficiently
    # w.r.t. the de-padding being done above, but sigh...
    if blocksize > 0 and len(s) % blocksize:
        s = (blocksize - len(s) % blocksize) * '\000' + s

    return s


def _bytelist2longBigEndian(list):
    "Transform a list of characters into a list of longs."

    imax = len(list)/4
    hl = [0L] * imax

    j = 0
    i = 0
    while i < imax:
        b0 = long(ord(list[j])) << 24
        b1 = long(ord(list[j+1])) << 16
        b2 = long(ord(list[j+2])) << 8
        b3 = long(ord(list[j+3]))
        hl[i] = b0 | b1 | b2 | b3
        i = i+1
        j = j+4

    return hl


def _rotateLeft(x, n):
    "Rotate x (32 bit) left n bits circularly."

    return (x << n) | (x >> (32-n))

# ======================================================================
# The SHA transformation functions
#
# ======================================================================

def f0_19(B, C, D):
    return (B & C) | ((~ B) & D)

def f20_39(B, C, D):
    return B ^ C ^ D

def f40_59(B, C, D):
    return (B & C) | (B & D) | (C & D)

def f60_79(B, C, D):
    return B ^ C ^ D

f = [f0_19, f20_39, f40_59, f60_79]

# Constants to be used
K = [
    0x5A827999L, # ( 0 <= t <= 19)
    0x6ED9EBA1L, # (20 <= t <= 39)
    0x8F1BBCDCL, # (40 <= t <= 59)
    0xCA62C1D6L  # (60 <= t <= 79)
    ]

class bsha:
    "broken sha for blizzard stuff"

    digest_size = digestsize = 20

    def __init__(self, broken=True):
        "Initialisation."
        self.broken=broken
       
        # Initial message length in bits(!).
        #self.length = 0L
        self.count = [0, 0]

        # Initial empty message as a sequence of bytes (8 bit characters).
        self.input = []

        # Call a separate init function, that can be used repeatedly
        # to start from scratch on the same object.
        self.init()
       

    def init(self):
        "Initialize the message-digest and set all fields to zero."

        #self.length = 0L
        self.input = []

        # Initial 160 bit message digest (5 times 32 bit).
        self.H0 = 0x67452301L
        self.H1 = 0xEFCDAB89L
        self.H2 = 0x98BADCFEL
        self.H3 = 0x10325476L
        self.H4 = 0xC3D2E1F0L

    def _transform(self, W):
        if self.broken:
            for t in range(0, 16): W[t]=socket.htonl(W[t])
           
           
        for t in range(16, 80):
            if self.broken:
                W.append(_rotateLeft(1, (W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16])&31) & 0xffffffffL)
            else:
                W.append(_rotateLeft(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1) & 0xffffffffL)

        A = self.H0
        B = self.H1
        C = self.H2
        D = self.H3
        E = self.H4

        """
        This loop was unrolled to gain about 10% in speed
        for t in range(0, 80):
            TEMP = _rotateLeft(A, 5) + f[t/20] + E + W[t] + K[t/20]
            E = D
            D = C
            C = _rotateLeft(B, 30) & 0xffffffffL
            B = A
            A = TEMP & 0xffffffffL
        """

        for t in range(0, 20):
            TEMP = _rotateLeft(A, 5) + ((B & C) | ((~ B) & D)) + E + W[t] + K[0]
            E = D
            D = C
            C = _rotateLeft(B, 30) & 0xffffffffL
            B = A
            A = TEMP & 0xffffffffL

        for t in range(20, 40):
            TEMP = _rotateLeft(A, 5) + (B ^ C ^ D) + E + W[t] + K[1]
            E = D
            D = C
            C = _rotateLeft(B, 30) & 0xffffffffL
            B = A
            A = TEMP & 0xffffffffL

        for t in range(40, 60):
            TEMP = _rotateLeft(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]
            E = D
            D = C
            C = _rotateLeft(B, 30) & 0xffffffffL
            B = A
            A = TEMP & 0xffffffffL

        for t in range(60, 80):
            TEMP = _rotateLeft(A, 5) + (B ^ C ^ D)  + E + W[t] + K[3]
            E = D
            D = C
            C = _rotateLeft(B, 30) & 0xffffffffL
            B = A
            A = TEMP & 0xffffffffL


        self.H0 = (self.H0 + A) & 0xffffffffL
        self.H1 = (self.H1 + B) & 0xffffffffL
        self.H2 = (self.H2 + C) & 0xffffffffL
        self.H3 = (self.H3 + D) & 0xffffffffL
        self.H4 = (self.H4 + E) & 0xffffffffL
   

    # Down from here all methods follow the Python Standard Library
    # API of the sha module.

    def update(self, inBuf):
        """Add to the current message.

        Update the md5 object with the string arg. Repeated calls
        are equivalent to a single call with the concatenation of all
        the arguments, i.e. m.update(a); m.update(b) is equivalent
        to m.update(a+b).

        The hash is immediately calculated for all full blocks. The final
        calculation is made in digest(). It will calculate 1-2 blocks,
        depending on how much padding we have to add. This allows us to
        keep an intermediate value for the hash, so that we only need to
        make minimal recalculation if we call update() to add more data
        to the hashed string.
        """

        leninBuf = long(len(inBuf))

        # Compute number of bytes mod 64.
        index = (self.count[1] >> 3) & 0x3FL

        # Update number of bits.
        self.count[1] = self.count[1] + (leninBuf << 3)
        if self.count[1] < (leninBuf << 3):
            self.count[0] = self.count[0] + 1
        self.count[0] = self.count[0] + (leninBuf >> 29)

        partLen = 64 - index

        if leninBuf >= partLen:
            self.input[index:] = list(inBuf[:partLen])
            self._transform(_bytelist2longBigEndian(self.input))
            i = partLen
            while i + 63 < leninBuf:
                self._transform(_bytelist2longBigEndian(list(inBuf[i:i+64])))
                i = i + 64
            else:
                self.input = list(inBuf[i:leninBuf])
        else:
            i = 0
            self.input = self.input + list(inBuf)


    def digest(self):
        """Terminate the message-digest computation and return digest.

        Return the digest of the strings passed to the update()
        method so far. This is a 20-byte string which may contain
        non-ASCII characters, including null bytes.
        """

        H0 = self.H0
        H1 = self.H1
        H2 = self.H2
        H3 = self.H3
        H4 = self.H4
        input = [] + self.input
        count = [] + self.count
       
        index = (self.count[1] >> 3) & 0x3fL
       
        if index < 56:
            padLen = 56 - index
        else:
            padLen = 120 - index

        if self.broken:
            padding = ['\000'] * 64
        else:
            padding = ['\200'] + ['\000'] * 63
       
        self.update(padding[:padLen])

       
        if self.broken:
            bits = _bytelist2longBigEndian(self.input[:56]) + [0L,0L]
        else:
            # Append length
            bits = _bytelist2longBigEndian(self.input[:56])+count
       
        self._transform(bits)

        # Store state in digest.
        digest = _long2bytesBigEndian(self.H0, 4) + \
                _long2bytesBigEndian(self.H1, 4) + \
                _long2bytesBigEndian(self.H2, 4) + \
                _long2bytesBigEndian(self.H3, 4) + \
                _long2bytesBigEndian(self.H4, 4)

        self.H0 = H0
        self.H1 = H1
        self.H2 = H2
        self.H3 = H3
        self.H4 = H4
        self.input = input
        self.count = count

        return digest


    def hexdigest(self):
        """Terminate and return digest in HEX form.

        Like digest() except the digest is returned as a string of
        length 32, containing only hexadecimal digits. This may be
        used to exchange the value safely in email or other non-
        binary environments.
        """
        return ''.join(['%02x' % ord(c) for c in self.digest()])




# ======================================================================
# Mimic Python top-level functions from standard library API
# for consistency with the md5 module of the standard library.
# ======================================================================

# These are mandatory variables in the module. They have constant values
# in the SHA standard.

digest_size = digestsize = 20
blocksize = 1
[/code]
July 6, 2009, 12:14 AM
Ringo
[quote author=ThePro link=topic=17995.msg183040#msg183040 date=1246839263]
>>>  'ac80f2db821b8d6c4eb5d4f447026bfce1b8a904'
#Which is wrong. Is has to be DBF280AC6C8D1B82F4D4B54EFC6B024704A9B8E1
[/quote]

Looks fine, apart from each dword having reversed byte order.
July 6, 2009, 4:36 AM
xpeh
Instead of
[code]
    def hexdigest(self):
        return ''.join(['%02x' % ord(c) for c in self.digest()])
[/code]
you must use
[code]
    def hexdigest(self):
        return ''.join(['%08x' % i for i in self.getintarray()])
[/code]
There must be a way to get int array instead of byte array.
July 6, 2009, 6:32 AM
ThePro
[quote author=Ringo link=topic=17995.msg183043#msg183043 date=1246854987]
[quote author=ThePro link=topic=17995.msg183040#msg183040 date=1246839263]
>>>  'ac80f2db821b8d6c4eb5d4f447026bfce1b8a904'
#Which is wrong. Is has to be DBF280AC6C8D1B82F4D4B54EFC6B024704A9B8E1
[/quote]

Looks fine, apart from each dword having reversed byte order.
[/quote]
Ahhh Thanks alot, Ringo!
I must be an idiot that I didn't see that!

The solution was just changing ONE! byte in the sourcecode:
[code]
#original
def _long2bytesBigEndian(n, blocksize=0):
...
s = pack('>I', n 0xffffffffL) + s
...

#changed
def _long2bytesBigEndian(n, blocksize=0):
...
s = pack('<I', n 0xffffffffL) + s
...
[/code]

Now it works perfectly. :)
July 6, 2009, 11:18 AM
Yegg
The < states you want the result in little-endian. Before you used either, the struct module returns a value based on whichever endian your native system uses. Just curious, but what is your processor brand and its specs?
July 7, 2009, 8:16 AM
ThePro
[quote author=Yegg link=topic=17995.msg183048#msg183048 date=1246954596]
The < states you want the result in little-endian. Before you used either, the struct module returns a value based on whichever endian your native system uses. Just curious, but what is your processor brand and its specs?
[/quote]
I know, thats the weird thing.

Processors:
Notbook: Intel T2250
First Server: Intel Pentium 4
Second Server: Intel Xeon 3060


All are working with the LittleEndian method. Dont ask me why...
July 7, 2009, 10:55 AM
Explicit[nK]
It's not unusual to handle the byte ordering as the packets go both out onto the wire and back. The idea is to forego any endianness issues that may arise since your code will then be designed to handle the ordering natively on any system running it.
July 7, 2009, 3:43 PM
Myndfyr
[quote author=ThePro link=topic=17995.msg183049#msg183049 date=1246964132]
All are working with the LittleEndian method. Dont ask me why...
[/quote]
More than likely because the protocol uses little-endian byte ordering.
August 3, 2009, 8:04 AM

Search