Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
MD5 Cryptographic Hash Function
#1
Here's some code I write for QBasic or QuickBASIC that calculates MD5 hashes of strings, files or arbitrary in-memory data. It's painfully slow since I had to simulate modulo 32-bit addition with unsigned integers -- see MD5AddModulo32&. It could be sped up significantly by implementing MD5HashBlock and its helper routines, the "meat" of the algorithm, in assembly, but I figured it would be fun to try a pure QB solution.

This code is adapted from the reference implementation of The MD5 Message-Digest Algorithm (RFC 1321). It should be pretty easy to follow; I've tried to comment the parts that need explanation, but for more detailed information on the MD5 algorithm itself, see the RFC and the Wikipedia page. Hope someone finds this useful.

Code:
'' PUBLIC API

'' function: MD5HashString$
''  Returns the 32-character hexadecimal string representation of the
''  message digest of `message`.
declare function MD5HashString$ ( message as string )

'' function: MD5HashFile$
''  Returns the 32-character hexadecimal string representation of the
''  message digest of the file `fileName`.
declare function MD5HashFile$ ( fileName as string )

'' type: MD5HashState
''  Stores the state of a hash operation. Use MD5Initialize, followed by calls
''  to MD5Update/MD5UpdateAux for all message bytes, then
''  MD5Finalize/MD5FinalizeAux to retrieve the message digest. (See
''  implementation for MD5HashFile$.) All fields are considered private.
type MD5HashState
    bitCount as long
    bitCount2 as long
    a as long
    b as long
    c as long
    d as long
    block as string * 64
end type

'' sub: MD5Initialize
''  Prepares `state` for calls to Update.
declare sub MD5Initialize ( state as MD5HashState )

'' sub: MD5Update
''  Begins or continues a hashing operation on `state` with the bytes in
''  `source`.
declare sub MD5Update ( state as MD5HashState, source as string )

'' sub: MD5UpdateAux
''  Begins or continues a hashing operation on `state` with `sourceLength`
''  bytes starting at address `sourcePtr`.
declare sub MD5UpdateAux ( state as MD5HashState, sourcePtr as integer, sourceLength as integer )

'' sub: MD5Finalize
''  Finishes the hashing operation on `state`, allocating and placing the
''  16-byte message digest in `digest`.
declare sub MD5Finalize ( state as MD5HashState, digest as string )

'' sub: MD5FinalizeAux
''  Finishes the hashing operation on `state`, placing the 16-byte message
''  digest in the pre-allocated memory starting at `digestPtr`.
declare sub MD5FinalizeAux ( state as MD5HashState, digestPtr as integer )

'' function: MD5GetHexFromBytes$
''  Returns the (2 * `srcLen`)-character hexadecimal string representation of
''  the bytes starting at `srcPtr`.
declare function MD5GetHexFromBytes$ ( srcPtr as integer, srcLen as integer )

'' function: MD5GetStringDataPtr%
''  Helper to retrieve the near address of first character of variable-length
''  strings and string parameters.
declare function MD5GetStringDataPtr% ( s as string )

'' INTERNAL API

declare sub MD5HashBlock ( state as MD5HashState, blockPtr as integer )
declare function MD5RoundF& ( x as long, y as long, z as long )
declare function MD5RoundG& ( x as long, y as long, z as long )
declare function MD5RoundH& ( x as long, y as long, z as long )
declare function MD5RoundI& ( x as long, y as long, z as long )
declare sub MD5FF ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
declare sub MD5GG ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
declare sub MD5HH ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
declare sub MD5II ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
declare function MD5AddModulo32& ( a as long, b as long )
declare function MD5RotateLeft& ( n as long, s as integer )
declare sub MD5MemCopy ( dstPtr as integer, srcPtr as integer, length as integer )


        '' ::::: [demo code]
        
        ' d41d8cd98f00b204e9800998ecf8427e
        print MD5HashString$( "" )
        ' 0cc175b9c0f1b6a831c399e269772661
        print MD5HashString$( "a" )
        ' 900150983cd24fb0d6963f7d28e17f72
        print MD5HashString$( "abc" )
        ' f96b697d7cb7938d525a2f31aaf161d0
        print MD5HashString$( "message digest" )
        ' c3fcd3d76192e4007dfb496cca67e13b
        print MD5HashString$( "abcdefghijklmnopqrstuvwxyz" )
        ' d174ab98d277d9f5a5611c2c9f419d9f
        print MD5HashString$( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" )
        ' 57edf4a22be3c955ac49da2e2107b67a
        print MD5HashString$( "12345678901234567890123456789012345678901234567890123456789012345678901234567890" )
        end
        
        '' ::::: [/demo code]

deflng a-z
function MD5HashString$ ( message as string )

    dim state as MD5HashState
    dim digest as string
    
    MD5Initialize state
    MD5Update state, message
    MD5Finalize state, digest
    
    MD5HashString$ = MD5GetHexFromBytes$( MD5GetStringDataPtr%( digest ), len( digest ) )

end function

deflng a-z
function MD5HashFile$ ( fileName as string )

    dim fileNum as integer : fileNum = freefile
    open fileName for binary access read as #fileNum
    
    dim state as MD5HashState
    MD5Initialize state
    
    dim bytesLeft as long : bytesLeft = lof( fileNum )
    dim buffer as string : buffer = space$( 2048 )
    
    do while bytesLeft
        if ( bytesLeft < len( buffer ) ) then
            buffer = space$( bytesLeft )
        end if
        get #filenum, , buffer
        bytesLeft = bytesLeft - len( buffer )
        MD5Update state, buffer
    loop
    close #filenum

    dim digest as string
    MD5Finalize state, digest
    
    MD5HashFile$ = MD5GetHexFromBytes$( MD5GetStringDataPtr%( digest ), len( digest ) )

end function

deflng a-z
function MD5GetHexFromBytes$ ( srcPtr as integer, srcLen as integer )

    dim result as string * 32
    for i = 0 to srcLen - 1
        dim b as integer : b = peek( srcPtr + i )
        dim highNibble as integer : highNibble = ( b and &HF0 ) \ 16
        dim  lowNibble as integer :  lowNibble = ( b and &H0F )
        mid$( result, (i*2) + 1, 1 ) = hex$( highNibble )
        mid$( result, (i*2) + 2, 1 ) = hex$( lowNibble )
    next i
    MD5GetHexFromBytes$ = result

end function

deflng a-z
sub MD5Encode ( dstPtr as integer, srcPtr as integer, n as integer )
    dim i as integer
    dim j as integer
    
    do while j < n
        poke dstPtr + j    ,   peek( srcPtr + i + 0 )
        poke dstPtr + j + 1,   peek( srcPtr + i + 1 )
        poke dstPtr + j + 2,   peek( srcPtr + i + 2 )
        poke dstPtr + j + 3,   peek( srcPtr + i + 3 )
        i = i + 4
        j = j + 4
    loop
end sub

deflng a-z
sub MD5Decode ( dstPtr as integer, srcPtr as integer, n as integer )
    dim i as integer
    dim j as integer
    
    do while j < n
        poke dstPtr + j    ,   peek( srcPtr + i + 0 )
        poke dstPtr + j + 1,   peek( srcPtr + i + 1 )
        poke dstPtr + j + 2,   peek( srcPtr + i + 2 )
        poke dstPtr + j + 3,   peek( srcPtr + i + 3 )
        i = i + 4
        j = j + 4
    loop
end sub

deflng a-z
sub MD5Initialize ( state as MD5HashState )
    ' prime state variables with these magic numbers:
    state.a = &h67452301
    state.b = &hefcdab89
    state.c = &h98badcfe
    state.d = &h10325476
end sub

deflng a-z
sub MD5Update ( state as MD5HashState, bytes as string )
    MD5UpdateAux state, MD5GetStringDataPtr%( bytes ), len( bytes )
end sub

deflng a-z
sub MD5UpdateAux ( state as MD5HashState, srcPtr as integer, srcLen as integer )

    dim index as integer
    dim partLen as integer
    dim i as integer
    
    index = ( state.bitCount \ 8 ) and &H3f
    
    ' update bit count..
    state.bitCount = MD5AddModulo32&( state.bitCount, srcLen * 8 )
    if ( state.bitCount < srcLen * 8 ) then
        state.bitCount2 = state.bitCount2 + 1
        state.bitCount2 = MD5AddModulo32&( state.bitCount2, ( srcLen / 2& ^ 29& ) )
    end if
    
    partLen = 64 - index
    i = 0
    if ( srclen >= partlen ) then
        ' fill and hash buffer.
        MD5MemCopy varptr( state.block ) + index, srcPtr, partLen
        MD5HashBlock state, varptr( state.block )
        
        ' hash as many full-sized source blocks as possible.
        i = partlen
        do while i + 63 < srcLen
            MD5HashBlock state, srcPtr + i
            i = i + 64
        loop
        index = 0
    end if
    ' buffer remaining source.
    MD5MemCopy varptr( state.block ) + index, srcPtr + i, srcLen - i

end sub

deflng a-z
sub MD5Finalize ( state as MD5HashState, digest as string )
    digest = space$( 16 )
    MD5FinalizeAux state, MD5GetStringDataPtr%( digest )
end sub

deflng a-z
sub MD5FinalizeAux ( state as MD5HashState, digestPtr as integer )

    ' storage for arbitrary padding lengths:
    dim PADDING as string * 64
    mid$( PADDING, 1, 1 ) = chr$( &H80 )
    
    dim index as integer
    dim padLen as integer
    
    ' save bit count (up to padding bits)
    dim bitCountStorage as string * 8
    MD5Encode varptr( bitCountStorage ), varptr( state.bitCount ), 8
    
    ' 'append' and hash padding bytes (leaving 8 bytes for length).
    index = ( state.bitCount \ 8 ) and &H3f
    if ( index < 56 ) then
        padLen = 56 - index
    else
        padLen = 120 - index
    end if
    MD5UpdateAux state, varptr( PADDING ), padLen
    
    ' 'append' and hash the message length.
    MD5Update state, bitCountStorage
    
    ' state (abcd) now holds the final digest; MD5Encode to byte array.
    MD5Encode digestPtr, varptr( state.a ), 16
    
end function

deflng a-z
function MD5GetStringDataPtr% ( s as string )
    dim dataPtr as double
    dataPtr =           peek( varptr( s ) + 2 )
    dataPtr = dataPtr + peek( varptr( s ) + 3 ) * 256#
    ' unsigned to signed
    if ( dataPtr > 32767# ) then dataPtr = -(65535# - dataPtr + 1)
    
    MD5GetStringDataPtr% = cint( dataPtr )
    
end function

deflng a-z
sub MD5MemCopy ( dstPtr as integer, srcPtr as integer, length as integer )
    for i = 0 to length - 1
        poke dstPtr + i, peek( srcPtr + i )
    next i
end sub

deflng a-z

const T1 = &hd76aa478
const T2 = &he8c7b756
const T3 = &h242070db
const T4 = &hc1bdceee
const T5 = &hf57c0faf
const T6 = &h4787c62a
const T7 = &ha8304613
const T8 = &hfd469501
const T9 = &h698098d8
const T10 = &h8b44f7af
const T11 = &hffff5bb1
const T12 = &h895cd7be
const T13 = &h6b901122
const T14 = &hfd987193
const T15 = &ha679438e
const T16 = &h49b40821
const T17 = &hf61e2562
const T18 = &hc040b340
const T19 = &h265e5a51
const T20 = &he9b6c7aa
const T21 = &hd62f105d
const T22 = &h02441453
const T23 = &hd8a1e681
const T24 = &he7d3fbc8
const T25 = &h21e1cde6
const T26 = &hc33707d6
const T27 = &hf4d50d87
const T28 = &h455a14ed
const T29 = &ha9e3e905
const T30 = &hfcefa3f8
const T31 = &h676f02d9
const T32 = &h8d2a4c8a
const T33 = &hfffa3942
const T34 = &h8771f681
const T35 = &h6d9d6122
const T36 = &hfde5380c
const T37 = &ha4beea44
const T38 = &h4bdecfa9
const T39 = &hf6bb4b60
const T40 = &hbebfbc70
const T41 = &h289b7ec6
const T42 = &heaa127fa
const T43 = &hd4ef3085
const T44 = &h04881d05
const T45 = &hd9d4d039
const T46 = &he6db99e5
const T47 = &h1fa27cf8
const T48 = &hc4ac5665
const T49 = &hf4292244
const T50 = &h432aff97
const T51 = &hab9423a7
const T52 = &hfc93a039
const T53 = &h655b59c3
const T54 = &h8f0ccc92
const T55 = &hffeff47d
const T56 = &h85845dd1
const T57 = &h6fa87e4f
const T58 = &hfe2ce6e0
const T59 = &ha3014314
const T60 = &h4e0811a1
const T61 = &hf7537e82
const T62 = &hbd3af235
const T63 = &h2ad7d2bb
const T64 = &heb86d391

' signed/unsigned conversions:
'
' unsigned to signed:
'  if( value > MAX_SIGNED_VALUE ) then
'   value = -( MAX_UNSIGNED_VALUE - value + 1 )
'  end if
'
' signed to unsigned:
'  if( value < 0 ) then
'   value = MAX_UNSIGNED_VALUE + value + 1
'  end if

' The following is used instead of "a + b" where necessary to provide addition
' modulo 32-bit of unsigned integers; needed because
'  1) QuickBASIC/QB throws an error on additive overflow, and
'  2) QuickBASIC/QB has no unsigned integer types.
deflng a-z
function MD5AddModulo32& ( a as long, b as long )

    dim aa as double : aa = cdbl( a )
    dim bb as double : bb = cdbl( b )
    
    ' signed to unsigned
    if ( a < 0 ) then aa = 4294967295# + a + 1
    if ( b < 0 ) then bb = 4294967295# + b + 1
    
    dim cc as double : cc = aa + bb
    ' keep 32-bit portion ( cc = cc and &hFFFFFFFF )
    if ( cc > 4294967295# ) then cc = cc - 4294967296#
    ' unsigned to signed
    if ( cc > 2147483647# ) then cc = -(4294967295# - cc + 1)
    
    MD5AddModulo32& = clng( cc )

end function

'          31        23        15 s      7      0
' before: [hhhhhhhh][hhhhllll][llllllll][llllllll]
' after:  [llllllll][llllllll][llllhhhh][hhhhhhhh]
deflng a-z
function MD5RotateLeft& ( n as long, s as integer )
    dim ss as integer : ss = s mod 32
    
    if ss <= 0 then MD5RotateLeft& = n : exit function
    
    dim highMask as double : highMask = (2# ^ ss - 1) * (2# ^ (32# - ss))
    dim  lowMask as double :  lowMask = (2# ^ (32# - ss) - 1)
    ' unsigned to signed
    if ( highMask > 2147483647# ) then highMask = -(4294967295# - highMask + 1)
    if (  lowMask > 2147483647# ) then  lowMask = -(4294967295# - lowMask + 1)

    dim highBits as double : highBits = cdbl( n and clng( highMask ) )
    dim  lowBits as double :  lowBits = cdbl( n and clng( lowMask ) )
    ' signed to unsigned
    if ( highBits < 0 ) then highBits = 4294967295# + highBits + 1
    if (  lowBits < 0 ) then  lowBits = 4294967295# + lowBits + 1
    
    highBits = highBits / (2# ^ (32 - ss))
     lowBits =  lowBits * (2# ^ ss)
    ' unsigned to signed
    if ( highBits > 2147483647# ) then highBits = -(4294967295# - highBits + 1)
    if (  lowBits > 2147483647# ) then  lowBits = -(4294967295# - lowBits + 1)
    
    MD5RotateLeft& = clng( lowBits ) or clng( highBits )

end function

deflng a-z
function MD5RoundF& ( x as long, y as long, z as long )
    MD5RoundF& = (x and y) or ((not x) and z)
end function

deflng a-z
function MD5RoundG& ( x as long, y as long, z as long )
    MD5RoundG& = (x and z) or (y and not z)
end function

deflng a-z
function MD5RoundH& ( x as long, y as long, z as long )
    MD5RoundH& = x xor y xor z
end function

deflng a-z
function MD5RoundI& ( x as long, y as long, z as long )
    MD5RoundI& = y xor (x or not z)
end function

deflng a-z
sub MD5FF ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
    dim tmp as long
    
    ' a = MD5RotateLeft&( ( a + MD5RoundF&( b, c, d ) + k + T ), s ) + b
    tmp = MD5AddModulo32&( a, MD5RoundF&( b, c, d ) )
    tmp = MD5AddModulo32&( tmp, k )
    tmp = MD5AddModulo32&( tmp, T )
    tmp = MD5RotateLeft&( tmp, s )
    a = MD5AddModulo32&( tmp, b )

end sub

deflng a-z
sub MD5GG ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
    dim tmp as long
    
    tmp = MD5AddModulo32&( a, MD5RoundG&( b, c, d ) )
    tmp = MD5AddModulo32&( tmp, k )
    tmp = MD5AddModulo32&( tmp, T )
    tmp = MD5RotateLeft&( tmp, s )
    a = MD5AddModulo32&( tmp, b )
end sub

deflng a-z
sub MD5HH ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
    dim tmp as long
    
    tmp = MD5AddModulo32&( a, MD5RoundH&( b, c, d ) )
    tmp = MD5AddModulo32&( tmp, k )
    tmp = MD5AddModulo32&( tmp, T )
    tmp = MD5RotateLeft&( tmp, s )
    a = MD5AddModulo32&( tmp, b )
end sub

deflng a-z
sub MD5II ( a as long, b as long, c as long, d as long, k as long, s as integer, T as long )
    dim tmp as long
    
    tmp = MD5AddModulo32&( a, MD5RoundI&( b, c, d ) )
    tmp = MD5AddModulo32&( tmp, k )
    tmp = MD5AddModulo32&( tmp, T )
    tmp = MD5RotateLeft&( tmp, s )
    a = MD5AddModulo32&( tmp, b )
end sub

deflng a-z
sub MD5HashBlock ( state as MD5HashState, blockPtr as integer )

    dim X(0 to 15) as long
    
    ' The code
    '    MD5Decode varptr( X(0) ), blockPtr, 64
    ' doesn't work, so we do the following:
    for i = 0 to 15
        dim value as double
        
        value =         peek( blockPtr + (i*4)     )
        value = value + peek( blockPtr + (i*4) + 1 ) * 2# ^ 8#
        value = value + peek( blockPtr + (i*4) + 2 ) * 2# ^ 16#
        value = value + peek( blockPtr + (i*4) + 3 ) * 2# ^ 24#
        ' unsigned to signed
        if ( value > 2147483647# ) then value = -(4294967295# - value + 1)
        
        X(i) = clng( value )
    next i
    
    dim a as long : a = state.a
    dim b as long : b = state.b
    dim c as long : c = state.c
    dim d as long : d = state.d
    
    ' Round 1

    MD5FF a, b, c, d,  X(0),  7,  T1
    MD5FF d, a, b, c,  X(1), 12,  T2
    MD5FF c, d, a, b,  X(2), 17,  T3
    MD5FF b, c, d, a,  X(3), 22,  T4
    MD5FF a, b, c, d,  X(4),  7,  T5
    MD5FF d, a, b, c,  X(5), 12,  T6
    MD5FF c, d, a, b,  X(6), 17,  T7
    MD5FF b, c, d, a,  X(7), 22,  T8
    MD5FF a, b, c, d,  X(8),  7,  T9
    MD5FF d, a, b, c,  X(9), 12, T10
    MD5FF c, d, a, b, X(10), 17, T11
    MD5FF b, c, d, a, X(11), 22, T12
    MD5FF a, b, c, d, X(12),  7, T13
    MD5FF d, a, b, c, X(13), 12, T14
    MD5FF c, d, a, b, X(14), 17, T15
    MD5FF b, c, d, a, X(15), 22, T16

    
    ' Round 2
    MD5GG a, b, c, d,  X(1),  5, T17
    MD5GG d, a, b, c,  X(6),  9, T18
    MD5GG c, d, a, b, X(11), 14, T19
    MD5GG b, c, d, a,  X(0), 20, T20
    MD5GG a, b, c, d,  X(5),  5, T21
    MD5GG d, a, b, c, X(10),  9, T22
    MD5GG c, d, a, b, X(15), 14, T23
    MD5GG b, c, d, a,  X(4), 20, T24
    MD5GG a, b, c, d,  X(9),  5, T25
    MD5GG d, a, b, c, X(14),  9, T26
    MD5GG c, d, a, b,  X(3), 14, T27
    MD5GG b, c, d, a,  X(8), 20, T28
    MD5GG a, b, c, d, X(13),  5, T29
    MD5GG d, a, b, c,  X(2),  9, T30
    MD5GG c, d, a, b,  X(7), 14, T31
    MD5GG b, c, d, a, X(12), 20, T32


    ' Round 3
    MD5HH a, b, c, d,  X(5),  4, T33
    MD5HH d, a, b, c,  X(8), 11, T34
    MD5HH c, d, a, b, X(11), 16, T35
    MD5HH b, c, d, a, X(14), 23, T36
    MD5HH a, b, c, d,  X(1),  4, T37
    MD5HH d, a, b, c,  X(4), 11, T38
    MD5HH c, d, a, b,  X(7), 16, T39
    MD5HH b, c, d, a, X(10), 23, T40
    MD5HH a, b, c, d, X(13),  4, T41
    MD5HH d, a, b, c,  X(0), 11, T42
    MD5HH c, d, a, b,  X(3), 16, T43
    MD5HH b, c, d, a,  X(6), 23, T44
    MD5HH a, b, c, d,  X(9),  4, T45
    MD5HH d, a, b, c, X(12), 11, T46
    MD5HH c, d, a, b, X(15), 16, T47
    MD5HH b, c, d, a,  X(2), 23, T48

    ' Round 4
    MD5II a, b, c, d,  X(0),  6, T49
    MD5II d, a, b, c,  X(7), 10, T50
    MD5II c, d, a, b, X(14), 15, T51
    MD5II b, c, d, a,  X(5), 21, T52
    MD5II a, b, c, d, X(12),  6, T53
    MD5II d, a, b, c,  X(3), 10, T54
    MD5II c, d, a, b, X(10), 15, T55
    MD5II b, c, d, a,  X(1), 21, T56
    MD5II a, b, c, d,  X(8),  6, T57
    MD5II d, a, b, c, X(15), 10, T58
    MD5II c, d, a, b,  X(6), 15, T59
    MD5II b, c, d, a, X(13), 21, T60
    MD5II a, b, c, d,  X(4),  6, T61
    MD5II d, a, b, c, X(11), 10, T62
    MD5II c, d, a, b,  X(2), 15, T63
    MD5II b, c, d, a,  X(9), 21, T64

    ' update the state.
    state.a = MD5AddModulo32&( state.a, a )
    state.b = MD5AddModulo32&( state.b, b )
    state.c = MD5AddModulo32&( state.c, c )
    state.d = MD5AddModulo32&( state.d, d )

end sub
stylin:
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)