It appears you have not registered with our community. To register please click here ...

May 22, 2013, 02:50:59 PM

Author Topic: [RESOLVED] Sha-1, Broken Sha-1 and Lockdown Sha-1 for python  (Read 637 times)

ThePro

  • Newbian
  • *
  • Posts: 3
    • View Profile
I already posted this on the vl forum but it seems to be dead so I will try my luck here again:

Hey, I know the botdev scene is almost dead but I am working on a general battle.net client library written in python. First I want to support all bnet 1.0 games and later one or more of the bnet 2.0 games, so I can chat with friends without beeing logged on with a game to battle.net. When it's done I make it open source of course.

I already got a working client written in python, but it is based on BNLS. That's ok but I'd like to write my own python based checkRevision to pass the logon to be independent from BNLS.
I am almost done with it but my only Problem is the lockdown sha-1 hashing.
As far as I know it is exactly the same like standard sha-1 except that it is using little-endian byte order instead of big endian.

I also read this thread by hdx, which seemed to be the key I was searching for but for some reason the hash is still wrong.
I also tried to compile the c-sourcecode he pasted in the thread but unfortunately it segfaults due to a stack issue when calling the sha1_checksum() function.
Calling the digest() function directly does work but the result is some bullshit hash.

As a reference I am using a c-sourcecode by rob which works great and generates the correct hash. I prevouisly compiled it as shared library and loaded it into python which also worked great... until I tried this on a x64 machine... guess what, it crashed, since the lib is written for 32bit. One more reason to write the hashing stuff in pure python.

This are the hashes I get for the String: "The quick brown fox jumps over the lazy dog":
Sha-1: 2f d4 e1 c6 7a 2d 28 fc ed 84 9e e1 bb 76 e7 39 1b 93 eb 12
xSha-1: a0 db 6e 70 61 60 33 a7 b5 fd da 37 ce e2 d4 3f 2d a1 02 88
lSha-1: a2 55 59 d6 d7 d9 a5 18 c7 a1 06 3d b7 d4 d2 67 ed 6b db a0 (which is wrong!)
Robs lSha code calculates this hash: a8 68 fb 6c 0d 95 c4 8d 03 7e 9f 08 ce 6e 42 00 fd 43 5f a4 (which is correct)

This is the python source code of my sha-1 library. At the bottom you can see an example of usage.
I am very sure there is only one little thing missing but I can't find it. :( Maybe someone can help me, you are my last hope.

Bsha.py
Code: (python) [Select]
import struct
import socket


# This library implements blizzards broken sha

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 _long2bytesLittleEndian(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 _bytelist2longLittleEndian(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+0]))       #<< 24
        b1 = long(ord(list[j+1])) << 8  #<< 16
        b2 = long(ord(list[j+2])) << 16 #<< 8
        b3 = long(ord(list[j+3])) << 24
        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 and lockdown sha for blizzard stuff"

    digest_size = digestsize = 20

    def __init__(self, broken=True, lockdown=False):
        "Initialisation."
        self.broken = broken
self.lockdown = lockdown

        # Initial message length in bits(!).
        self.length = 0L
        self.count = [0, 0] # bitlen

        # 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.H = [0x67452301L,
                  0xEFCDAB89L,
                  0x98BADCFEL,
                  0x10325476L,
                  0xC3D2E1F0L,
                 ]

    def _transform(self, W):
        # Lockdown uses the littlendian byte to int array, and standard ROL.
        # XSHA1 uses both littlendian and the reversed ROL.
        if self.broken or self.lockdown:
            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.H[0]
        B = self.H[1]
        C = self.H[2]
        D = self.H[3]
        E = self.H[4]




        """
        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.H[0] = (self.H[0] + A) & 0xffffffffL
        self.H[1] = (self.H[1] + B) & 0xffffffffL
        self.H[2] = (self.H[2] + C) & 0xffffffffL
        self.H[3] = (self.H[3] + D) & 0xffffffffL
        self.H[4] = (self.H[4] + E) & 0xffffffffL


    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.
        """

       
        data = inBuf
        leninBuf = long(len(inBuf))

        c = leninBuf >> 29
        b = leninBuf << 3

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

        # Update number of bits.
        if self.count[1] +b < self.count[1] or self.count[1] + b < b :
            self.count[0] += 1
        self.count[1] = self.count[1] + b
        self.count[0] = self.count[0] + c

     
        partLen = 64 - a

        if leninBuf >= partLen:
            self.input[a:] = 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.
        """

        H     = [] + self.H
        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]
        elif self.lockdown:
            bits = _bytelist2longLittleEndian(self.input[:56]) + [count[1], count[0]]
        else:
            # Append length
            bits = _bytelist2longBigEndian(self.input[:56])+count

        self._transform(bits)

       
        # lockdown and Xsha are using little endian
        if self.broken or self.lockdown:
            # Store state in digest.
            digest = _long2bytesLittleEndian(self.H[0], 4) + \
                     _long2bytesLittleEndian(self.H[1], 4) + \
                     _long2bytesLittleEndian(self.H[2], 4) + \
                     _long2bytesLittleEndian(self.H[3], 4) + \
                     _long2bytesLittleEndian(self.H[4], 4)
        else:
            # Store state in digest.
            digest = _long2bytesBigEndian(self.H[0], 4) + \
                     _long2bytesBigEndian(self.H[1], 4) + \
                     _long2bytesBigEndian(self.H[2], 4) + \
                     _long2bytesBigEndian(self.H[3], 4) + \
                     _long2bytesBigEndian(self.H[4], 4)

       

        self.H[0] = H[0]
        self.H[1] = H[1]
        self.H[2] = H[2]
        self.H[3] = H[3]
        self.H[4] = H[4]
        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()])




def main():
    # Bsha supports 3 modes:
    # Standard Sha-1: set broken=False, lockdown=False
    # Broken Sha1-1:  set broken=True, lockdown=False
    # Lockdown Sha1:  set broken=False, lockdown=True (This doesen't work correctly yet!)

    b1 = Bsha(broken=False, lockdown=True)
    b1.update("The quick brown fox jumps over the lazy dog")
    print "reference hash: a868fb6c0d95c48d037e9f08ce6e4200fd435fa4"
    print "calulated hash: %s" % b1.hexdigest()


if __name__ == '__main__':
    main()

« Last Edit: July 30, 2012, 07:04:03 PM by ThePro »

ThePro

  • Newbian
  • *
  • Posts: 3
    • View Profile
Re: Sha-1, Broken Sha-1 and Lockdown Sha-1 for python
« Reply #1 on: July 30, 2012, 11:28:32 AM »
Finally I found the mistake. :)

I was going to port robs c-code and while porting the transform function I found the bug in the python code.
For some reason on broken sha the first 16 bytes of the data string get endian converted. On lockdown sha its not, so the information of hdx is wrong or i missunderstood it. :o

Code: (python) [Select]
def _transform(self, W):
        # Lockdown uses the littlendian byte to int array, and standard ROL.
        # XSHA1 uses both littlendian and the reversed ROL.
        if self.broken or self.lockdown: # <--- This is wrong! This has only to be done for broken sha
            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)

[...]

Whatever, since there may be other python developers hwo are looking for pure python based local hashing functions here is the sourcecode of my working python library for sha, broken sha and lockdown sha with a usage example at the bottom of the code:

Code: [Select]
EDIT: I removed this, because it still contained a bug! Read the next post!
« Last Edit: July 30, 2012, 07:03:39 PM by ThePro »

ThePro

  • Newbian
  • *
  • Posts: 3
    • View Profile
Re: [RESOLVED] Sha-1, Broken Sha-1 and Lockdown Sha-1 for python
« Reply #2 on: July 30, 2012, 03:01:52 PM »
There was still another bug in the code so strings with a length greater then 56 got wrong hashed.
The update() function had still to be fixed, so the information that only digest has to be fixed is also wrong:

Code: (python) [Select]
[...]
if leninBuf >= partLen:
            self.input[a:] = list(inBuf[:partLen])

            if self.mode == 'lockdown':
                self._transform(_bytelist2longLittleEndian(self.input)) <--- added
            else:
                self._transform(_bytelist2longBigEndian(self.input))
            i = partLen
            while i + 63 < leninBuf:
                if self.mode == 'lockdown':
                    self._transform(_bytelist2longLittleEndian(list(inBuf[i:i+64]))) <--- added
                else:
                    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)
[...]

I rechecked it and and everything works fine now.
This is 100% working code:

Bsha.py
Code: (python) [Select]
#!/usr/bin/env python
# -*- coding: iso-8859-1

import struct
import socket
from ctypes import *


""" This library implements Blizzards Broken-Sha,
    Lockdown-Sha and Standard-Sha (Warden) using pure python
    Made by ThePro on 30. July 2012.


    This module is based on the framework adapted from Dinu Gherman's MD5
    implementation by J. Hallén and L. Creighton. SHA-1 implementation based
    directly on the text of the NIST standard FIPS PUB 180-1.
"""

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 _long2bytesLittleEndian(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+0])) << 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 _bytelist2longLittleEndian(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+0]))       
        b1 = long(ord(list[j+1])) << 8 
        b2 = long(ord(list[j+2])) << 16
        b3 = long(ord(list[j+3])) << 24
        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, lockdown sha and standard sha for authentication stuff"
    The 3 modes are:
   
    'standard': The standard sha-1 used by Warden
    'broken':   Broken Sha-1 also known as xSha used by password hashing
    'lockdown':  Another broken Sha-1 used by lockdown in checkRevision()
    """

    digest_size = digestsize = 20

    def __init__(self, mode='broken'):
        "Initialisation."

        if not (mode == 'standard' or mode == 'broken' or mode == 'lockdown'):
            raise Exception("unsupported mode: '%s. Supported modes are: 'standard', 'broken' or 'lockdown'" % mode)

        self.mode = mode
       
        # Initial message length in bits(!).
        self.length = 0L
        self.count = [0, 0] # bitlen

        # 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.H = [0x67452301L,
                  0xEFCDAB89L,
                  0x98BADCFEL,
                  0x10325476L,
                  0xC3D2E1F0L,
                 ]

    def _transform(self, W):
        if self.mode == 'broken':
            for t in range(0, 16): W[t]=socket.htonl(W[t])


        for t in range(16, 80):
            if self.mode == '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.H[0]
        B = self.H[1]
        C = self.H[2]
        D = self.H[3]
        E = self.H[4]


        """
        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.H[0] = (self.H[0] + A) & 0xffffffffL
        self.H[1] = (self.H[1] + B) & 0xffffffffL
        self.H[2] = (self.H[2] + C) & 0xffffffffL
        self.H[3] = (self.H[3] + D) & 0xffffffffL
        self.H[4] = (self.H[4] + E) & 0xffffffffL


    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.
        """
       
        data = inBuf
        leninBuf = long(len(inBuf))

        c = leninBuf >> 29
        b = leninBuf << 3

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

        # Update number of bits.
        if self.count[1] + b < self.count[1] or self.count[1] + b < b :
            self.count[0] += 1
        self.count[1] = self.count[1] + b
        self.count[0] = self.count[0] + c

     
        partLen = 64 - a

        if leninBuf >= partLen:
            self.input[a:] = list(inBuf[:partLen])

            if self.mode == 'lockdown':
                self._transform(_bytelist2longLittleEndian(self.input))
            else:
                self._transform(_bytelist2longBigEndian(self.input))
            i = partLen
            while i + 63 < leninBuf:
                if self.mode == 'lockdown':
                    self._transform(_bytelist2longLittleEndian(list(inBuf[i:i+64])))
                else:
                    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.
        """

        H     = [] + self.H
        input = [] + self.input
        count = [] + self.count

index = (self.count[1] >> 3) & 0x3fL

        if index < 56:
            padLen = 56 - index
        else:
            padLen = 120 - index


        if self.mode == 'broken':
            padding = ['\000'] * 64
        else:
            padding = ['\200'] + ['\000'] * 63 # called 'mystery buffer' sometimes


        self.update(padding[:padLen])


        if self.mode == 'broken':
            bits = _bytelist2longBigEndian(self.input[:56]) + [0L,0L]
        elif self.mode == 'lockdown':
            bits = _bytelist2longLittleEndian(self.input[:56]) + [count[1], count[0]]
        else:
            # Append length
            bits = _bytelist2longBigEndian(self.input[:56]) + count

        self._transform(bits)


        # lockdown and Xsha are using little endian
        # standard-sha (Warden) uses big endian
        if self.mode == 'broken' or self.mode == 'lockdown':
            digest = _long2bytesLittleEndian(self.H[0], 4) + \
                     _long2bytesLittleEndian(self.H[1], 4) + \
                     _long2bytesLittleEndian(self.H[2], 4) + \
                     _long2bytesLittleEndian(self.H[3], 4) + \
                     _long2bytesLittleEndian(self.H[4], 4)
        else:
            digest = _long2bytesBigEndian(self.H[0], 4) + \
                     _long2bytesBigEndian(self.H[1], 4) + \
                     _long2bytesBigEndian(self.H[2], 4) + \
                     _long2bytesBigEndian(self.H[3], 4) + \
                     _long2bytesBigEndian(self.H[4], 4)

       

        self.H[0] = H[0]
        self.H[1] = H[1]
        self.H[2] = H[2]
        self.H[3] = H[3]
        self.H[4] = H[4]
        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()])


# example usage
def main():
    data = "The quick brown fox jumps over the lazy dog"

    xSha = Bsha(mode = 'broken')
    lSha = Bsha(mode = 'lockdown')
    wSha = Bsha(mode = 'standard')

    xSha.update(data)
    lSha.update(data)
    wSha.update(data)

    print "Hashed data: '%s'" % data
    print "Broken   Sha-1: %s" % xSha.hexdigest()
    print "Lockdown Sha-1: %s" % lSha.hexdigest()
    print "Standard Sha-1: %s" % wSha.hexdigest()


if __name__ == '__main__':
    main()

Maybe this information help other bot developers porting checkRevision from C to another language. ;)
« Last Edit: July 30, 2012, 07:03:26 PM by ThePro »