Code: Select all
;
; https://oryx-embedded.com/doc/dir_a9aab978e0be629e504b25df915d67e8.html
;
; https://www.purebasic.fr/english/viewtopic.php?t=81310
CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
#PB_Cipher_MD4 = 14
; MD4 block size
#MD4_BLOCK_SIZE = 64
; MD4 digest size
#MD4_DIGEST_SIZE = 16
; Minimum length of the padding string
#MD4_MIN_PAD_SIZE = 9
Structure Md4Context
StructureUnion
h.l[4]
digest.a[16]
EndStructureUnion
StructureUnion
x.l[16]
buffer.a[64]
EndStructureUnion
size.i
totalSize.q
EndStructure
; MD4 auxiliary functions
Macro ROL32(x, n)
(((x) << (n)) | ((x) >> (32-(n))))
EndMacro
Macro F(x, y, z)
(((x) & (y)) | (~(x) & (z)))
EndMacro
Macro G(x, y, z)
(((x) & (y)) | ((x) & (z)) | ((y) & (z)))
EndMacro
Macro H(x, y, z)
((x) ! (y) ! (z))
EndMacro
Macro FF(a, b, c, d, x, s)
a + (F(b, c, d) + (x))
a & $FFFFFFFF
a = ROL32(a, s)
EndMacro
Macro GG(a, b, c, d, x, s)
a + (G(b, c, d) + (x) + $5A827999)
a & $FFFFFFFF
a = ROL32(a, s)
EndMacro
Macro HH(a, b, c, d, x, s)
a + (H(b, c, d) + (x) + $6ED9EBA1)
a & $FFFFFFFF
a = ROL32(a, s)
EndMacro
; MD4 padding
Global Dim MD4_Padding.a(63)
MD4_Padding(0) = $80
; Static const uint8_t padding[64] =
; {
; 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
; 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
; 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
; 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
; };
Procedure md4Init(*context.Md4Context)
; Set initial hash value
*context\h[0] = $67452301
*context\h[1] = $EFCDAB89
*context\h[2] = $98BADCFE
*context\h[3] = $10325476
; Number of bytes in the buffer
*context\size = 0
; Total length of the message
*context\totalSize = 0
EndProcedure
Procedure.l htole32(LongValue.l)
ProcedureReturn LongValue
EndProcedure
Procedure.l letoh32(LongValue.l)
ProcedureReturn LongValue
EndProcedure
Structure md4LongArray
v.l[0]
EndStructure
Procedure md4ProcessBlock(*context.Md4Context)
Protected.i i
Protected.q a, b, c, d
Protected *x.md4LongArray
; Initialize the 4 working registers
a = *context\h[0] & $FFFFFFFF
b = *context\h[1] & $FFFFFFFF
c = *context\h[2] & $FFFFFFFF
d = *context\h[3] & $FFFFFFFF
; Process message in 16-word blocks
*x = @*context\x[0]
; Convert from little-endian byte order To host byte order
For i = 0 To 15
*x\v[i] = letoh32(*x\v[i])
Next i
; Round 1
FF(a, b, c, d, *x\v[0], 3)
FF(d, a, b, c, *x\v[1], 7)
FF(c, d, a, b, *x\v[2], 11)
FF(b, c, d, a, *x\v[3], 19)
FF(a, b, c, d, *x\v[4], 3)
FF(d, a, b, c, *x\v[5], 7)
FF(c, d, a, b, *x\v[6], 11)
FF(b, c, d, a, *x\v[7], 19)
FF(a, b, c, d, *x\v[8], 3)
FF(d, a, b, c, *x\v[9], 7)
FF(c, d, a, b, *x\v[10], 11)
FF(b, c, d, a, *x\v[11], 19)
FF(a, b, c, d, *x\v[12], 3)
FF(d, a, b, c, *x\v[13], 7)
FF(c, d, a, b, *x\v[14], 11)
FF(b, c, d, a, *x\v[15], 19)
; Round 2
GG(a, b, c, d, *x\v[0], 3)
GG(d, a, b, c, *x\v[4], 5)
GG(c, d, a, b, *x\v[8], 9)
GG(b, c, d, a, *x\v[12], 13)
GG(a, b, c, d, *x\v[1], 3)
GG(d, a, b, c, *x\v[5], 5)
GG(c, d, a, b, *x\v[9], 9)
GG(b, c, d, a, *x\v[13], 13)
GG(a, b, c, d, *x\v[2], 3)
GG(d, a, b, c, *x\v[6], 5)
GG(c, d, a, b, *x\v[10], 9)
GG(b, c, d, a, *x\v[14], 13)
GG(a, b, c, d, *x\v[3], 3)
GG(d, a, b, c, *x\v[7], 5)
GG(c, d, a, b, *x\v[11], 9)
GG(b, c, d, a, *x\v[15], 13)
; Round 3
HH(a, b, c, d, *x\v[0], 3)
HH(d, a, b, c, *x\v[8], 9)
HH(c, d, a, b, *x\v[4], 11)
HH(b, c, d, a, *x\v[12], 15)
HH(a, b, c, d, *x\v[2], 3)
HH(d, a, b, c, *x\v[10], 9)
HH(c, d, a, b, *x\v[6], 11)
HH(b, c, d, a, *x\v[14], 15)
HH(a, b, c, d, *x\v[1], 3)
HH(d, a, b, c, *x\v[9], 9)
HH(c, d, a, b, *x\v[5], 11)
HH(b, c, d, a, *x\v[13], 15)
HH(a, b, c, d, *x\v[3], 3)
HH(d, a, b, c, *x\v[11], 9)
HH(c, d, a, b, *x\v[7], 11)
HH(b, c, d, a, *x\v[15], 15)
; Update the hash value
*context\h[0] = (*context\h[0] + a) & $FFFFFFFF
*context\h[1] = (*context\h[1] + b) & $FFFFFFFF
*context\h[2] = (*context\h[2] + c) & $FFFFFFFF
*context\h[3] = (*context\h[3] + d) & $FFFFFFFF
EndProcedure
Procedure md4Update(*context.Md4Context, *Data, length.i)
Protected.i n
; Process the incoming Data
While length > 0
; The buffer can hold at most 64 bytes
If length < 64 - *context\size
n = length
Else
n = 64 - *context\size
EndIf
; Copy the Data To the buffer
CopyMemory(*Data, @*context\buffer[0] + *context\size, n)
; Update the MD4 context
*context\size + n
*context\totalSize + n
; Advance the Data pointer
*Data = *Data + n
; Remaining bytes To process
length - n
; Process message in 16-word blocks
If *context\size = 64
; Transform the 16-word block
md4ProcessBlock(*context)
; Empty the buffer
*context\size = 0
EndIf
Wend
EndProcedure
Procedure md4Final(*context.Md4Context, *digest.Ascii)
Protected.i i, paddingSize
Protected.q totalSize
; Length of the original message (before padding)
totalSize = *context\totalSize * 8
; Pad the message so that its length is congruent To 56 modulo 64
If *context\size < 56
paddingSize = 56 - *context\size
Else
paddingSize = 64 + 56 - *context\size
EndIf
; Append padding
md4Update(*context, @MD4_padding(0), paddingSize)
; Append the length of the original message
*context\x[14] = htole32(totalSize & $FFFFFFFF)
*context\x[15] = htole32(totalSize >> 32)
; Calculate the message digest
md4ProcessBlock(*context)
; Convert from host byte order To little-endian byte order
; For i = 0 To 3
; *context\h[i] = htole32(*context\h[i])
; Next i
; Copy the resulting digest
If *digest
CopyMemory(@*context\digest[0], *digest, #MD4_DIGEST_SIZE)
EndIf
EndProcedure
Procedure.s FingerPrintMD4(*buffer, size.i)
Protected.i i
Protected Context.Md4Context
Protected Hash$
Protected Dim Digest.a(15)
If *buffer And size > 0
md4Init(@Context)
md4Update(@Context, *buffer, size)
md4Final(@Context, @Digest(0))
For i = 0 To 15
Hash$ + LCase(RSet(Hex(Digest(i)), 2, "0"))
Next i
EndIf
ProcedureReturn Hash$
EndProcedure
Procedure.s StringFingerprintMD4(String$)
Protected *UTF8
Protected Hash$
*UTF8 = UTF8(String$)
If *UTF8
Hash$ = FingerPrintMD4(*UTF8, MemorySize(*UTF8) - 1)
FreeMemory(*UTF8)
EndIf
ProcedureReturn Hash$
EndProcedure
Procedure.s FileFingerprintMD4(Filename$, Offset.i=0, Length.i=0)
Protected.i i, File, Size
Protected Hash$
Protected *Buffer
File = ReadFile(#PB_Any, Filename$)
If File
If Offset > 0
FileSeek(File, Offset)
EndIf
If Length
Size = Length
Else
Size = Lof(File)
If Offset
Size - Offset
EndIf
EndIf
*Buffer = AllocateMemory(Size, #PB_Memory_NoClear)
If *Buffer
If ReadData(File, *Buffer, Size) = Size
Hash$ = FingerPrintMD4(*Buffer, Size)
EndIf
FreeMemory(*Buffer)
EndIf
CloseFile(File)
EndIf
ProcedureReturn Hash$
EndProcedure
CompilerIf #PB_Compiler_IsMainFile
;-Demo
Debug "MD4: " + StringFingerprintMD4("Hello World 1234567890")
Debug "Should be: bb51e309cdbbd13434bd1f7dde51f44d"
CompilerEndIf