Journal Journal: MD5 in 80x86 (MASM)
You'll have to assemble this one with MASM. I don't feel like adapting it for debug...
How to assemble: Copy and paste into notepad, save as md5.asm, and assemble with MASM to create md5.exe.
; +-- MD5 -- d9dfaaaf50701ed5851a58123cbaf925284218aee788f4cc3c11e1d7d2f96721 -->
; | --> started 22.aug.08 -- completed 24.aug.08 <--
; | The MD5 function implemented in 80x86 assembly language
; | Calculates the hash of an input string MSG of length LEN.
; | The MD5 procedure stores the hash as a 16-byte binary value beginning at H0.
; | The PRINT procedure displays the 16-bit binary hash as 32-digit hexadecimal.
; +----------------------------------------------------------------------------->
; This is the maximum string length.
; It has to be 0-255 because INT 10h can't input longer strings than 255 bytes.
; If you're using the MD5 proc to hash longer data, you'll have to input it somehow
; and store the data length in LEN, which is a word (allowing an input data size
; up to 64k, if you change the.MODEL directive).
MAX_LEN EQU 255
; -- Macro definitions -----------------------------
FnF MACRO i
MOV AX, VC ; f = d xor (b and (c xor d))
MOV BX, VC+2
XOR AX, VD
XOR BX, VD+2
AND AX, VB
AND BX, VB+2
XOR AX, VD
XOR BX, VD+2
MOV VF, AX
MOV VF+2, BX
MOV BYTE PTR VG, i ; g = i
ENDM
FnG MACRO i
MOV AX, VB ; f = c xor (d and (b xor c))
MOV BX, VB+2
XOR AX, VC
XOR BX, VC+2
AND AX, VD
AND BX, VD+2
XOR AX, VC
XOR BX, VC+2
MOV VF, AX
MOV VF+2, BX
MOV CL, i ; g = (5*i + 1) mod 16
SHL CL, 1
SHL CL, 1
ADD CL, i
INC CL
AND CL, 0Fh
MOV VG, CL
ENDM
FnH MACRO i
MOV AX, VB ; f = b xor c xor d
MOV BX, VB+2
XOR AX, VC
XOR BX, VC+2
XOR AX, VD
XOR BX, VD+2
MOV VF, AX
MOV VF+2, BX
MOV CL, i ; g = (3*i + 5) mod 16
SHL CL, 1
ADD CL, i
ADD CL, 5
AND CL, 0Fh
MOV VG, CL
ENDM
FnI MACRO i
MOV AX, VD ; f = c xor (b or (not d))
MOV BX, VD+2
NOT AX
NOT BX
OR AX, VB
OR BX, VB+2
XOR AX, VC
XOR BX, VC+2
MOV VF, AX
MOV VF+2, BX
MOV CX, i ; g = (7*i) mod 16
SHL CX, 1 ; 16-bit. 63*8 doesn't fit in 8 bits.
SHL CX, 1
SHL CX, 1
SUB CX, i
AND CL, 0Fh
MOV VG, CL
ENDM
UpdVars MACRO Rn, Kn_L, Kn_H
LOCAL LOOP
MOV AX, VD ; temp = d
MOV TEMP, AX
MOV AX, VD+2
MOV TEMP+2, AX
MOV AX, VC ; d = c
MOV VD, AX
MOV AX, VC+2
MOV VD+2, AX
MOV AX, VB ; c = b
MOV VC, AX
MOV AX, VB+2
MOV VC+2, AX
MOV AX, VA ; BX:AX = (a + f + Kn + w[g])
MOV BX, VA+2
MOV CX, VF
ADD AX, CX
MOV CX, VF+2
ADC BX, CX ; Add with carry
ADD AX, Kn_L
ADC BX, Kn_H
MOV SI, DI ; SI points to the beginning of this chunk
XOR CH, CH ; Clear CH
MOV CL, VG
SHL CX, 1
SHL CX, 1 ; Multiply VG*4 to find the offset of w[g]
ADD SI, CX ; SI points to w[g]
MOV CX, [SI]
ADD AX, CX
MOV CX, [SI]+2
ADC BX, CX
; Now rotate BX:AX to the left Rn times
MOV CL, Rn
LOOP: SHL AX, 1 ; Bit shifted out of low word goes into carry
RCL BX, 1 ; Shift left & shift carry into LSB of high word
ADC AL, 0 ; Bring carry bit back into LSB of low byte
DEC CL
JNZ LOOP ; Loop Rn times
ADD VB, AX ; VB = VB + BX:AX
ADC VB+2, BX
MOV AX, TEMP ; a = temp
MOV VA, AX
MOV AX, TEMP+2
MOV VA+2, AX
ENDM
; Compares two 16-byte values. I'm not using this but it might be useful in the future.
; DS:SI = address of first value, ES:DI = address of 2nd value.
; Return: ZR if equal, NZ if unequal
Comp MACRO
MOV CX, 16
REPE CMPSB
ENDM
; -- End macro definitions -------------------------
.MODEL SMALL .STACK 64
.DATA
; MAX_L, ACTUAL, and MSG must be adjacent in this order. Don't mess with them.
MAX_L DB MAX_LEN ; Don't change this, change the MAX_LEN EQU instead.
ACTUAL DB ? ; This is where DOS will put the actual string length
MSG DB MAX_LEN DUP (?) ; Holds the string that we'll be hashing
DB 8 DUP (?), 64 DUP (?) ; Space to pad the message. Don't change this either.
LEN DW ? ; ACTUAL gets copied here. MD5 uses this, not ACTUAL.
PROMPT DB "Enter string: $"
; These 4 dwords will hold the MD5 hash as it's being calculated and the final result
H0 DW ?,?
H1 DW ?,?
H2 DW ?,?
H3 DW ?,?
; These will hold intermediate values
VA DW ?,?
VB DW ?,?
VC DW ?,?
VD DW ?,?
VF DW ?,?
VG DB ?
TEMP DW ?,?
; Un-comment the appropriate line to give upper/lowercase hex when printing an MD5 hash.
HEX DB "0123456789abcdef"
;HEX DB "0123456789ABCDEF"
; Initial values for H0-H3 and VA-VD.
; Loaded in 16-bit words because we can't address a dword.
H0_H EQU 6745h
H0_L EQU 2301h
H1_H EQU 0EFCDh
H1_L EQU 0AB89h
H2_H EQU 98BAh
H2_L EQU 0DCFEh
H3_H EQU 1032h
H3_L EQU 5476h
; Array of K dword values: k[i] = floor(abs(sin(i + 1)) * (2^32))
K0_H EQU 0D76Ah
K0_L EQU 0A478h
K1_H EQU 0E8C7h
K1_L EQU 0B756h
K2_H EQU 2420h
K2_L EQU 70DBh
K3_H EQU 0C1BDh
K3_L EQU 0CEEEh
K4_H EQU 0F57Ch
K4_L EQU 0FAFh
K5_H EQU 4787h
K5_L EQU 0C62Ah
K6_H EQU 0A830h
K6_L EQU 4613h
K7_H EQU 0FD46h
K7_L EQU 9501h
K8_H EQU 6980h
K8_L EQU 98D8h
K9_H EQU 8B44h
K9_L EQU 0F7AFh
K10_H EQU 0FFFFh
K10_L EQU 5BB1h
K11_H EQU 895Ch
K11_L EQU 0D7BEh
K12_H EQU 6B90h
K12_L EQU 1122h
K13_H EQU 0FD98h
K13_L EQU 7193h
K14_H EQU 0A679h
K14_L EQU 438Eh
K15_H EQU 49B4h
K15_L EQU 0821h
K16_H EQU 0F61Eh
K16_L EQU 2562h
K17_H EQU 0C040h
K17_L EQU 0B340h
K18_H EQU 265Eh
K18_L EQU 5A51h
K19_H EQU 0E9B6h
K19_L EQU 0C7AAh
K20_H EQU 0D62Fh
K20_L EQU 105Dh
K21_H EQU 0244h
K21_L EQU 1453h
K22_H EQU 0D8A1h
K22_L EQU 0E681h
K23_H EQU 0E7D3h
K23_L EQU 0FBC8h
K24_H EQU 21E1h
K24_L EQU 0CDE6h
K25_H EQU 0C337h
K25_L EQU 07D6h
K26_H EQU 0F4D5h
K26_L EQU 0D87h
K27_H EQU 455Ah
K27_L EQU 14EDh
K28_H EQU 0A9E3h
K28_L EQU 0E905h
K29_H EQU 0FCEFh
K29_L EQU 0A3F8h
K30_H EQU 676Fh
K30_L EQU 02D9h
K31_H EQU 8D2Ah
K31_L EQU 4C8Ah
K32_H EQU 0FFFAh
K32_L EQU 3942h
K33_H EQU 8771h
K33_L EQU 0F681h
K34_H EQU 6D9Dh
K34_L EQU 6122h
K35_H EQU 0FDE5h
K35_L EQU 380Ch
K36_H EQU 0A4BEh
K36_L EQU 0EA44h
K37_H EQU 4BDEh
K37_L EQU 0CFA9h
K38_H EQU 0F6BBh
K38_L EQU 4B60h
K39_H EQU 0BEBFh
K39_L EQU 0BC70h
K40_H EQU 289Bh
K40_L EQU 7EC6h
K41_H EQU 0EAA1h
K41_L EQU 27FAh
K42_H EQU 0D4EFh
K42_L EQU 3085h
K43_H EQU 0488h
K43_L EQU 1D05h
K44_H EQU 0D9D4h
K44_L EQU 0D039h
K45_H EQU 0E6DBh
K45_L EQU 99E5h
K46_H EQU 1FA2h
K46_L EQU 7CF8h
K47_H EQU 0C4ACh
K47_L EQU 5665h
K48_H EQU 0F429h
K48_L EQU 2244h
K49_H EQU 432Ah
K49_L EQU 0FF97h
K50_H EQU 0AB94h
K50_L EQU 23A7h
K51_H EQU 0FC93h
K51_L EQU 0A039h
K52_H EQU 655Bh
K52_L EQU 59C3h
K53_H EQU 8F0Ch
K53_L EQU 0CC92h
K54_H EQU 0FFEFh
K54_L EQU 0F47Dh
K55_H EQU 8584h
K55_L EQU 5DD1h
K56_H EQU 6FA8h
K56_L EQU 7E4Fh
K57_H EQU 0FE2Ch
K57_L EQU 0E6E0h
K58_H EQU 0A301h
K58_L EQU 4314h
K59_H EQU 4E08h
K59_L EQU 11A1h
K60_H EQU 0F753h
K60_L EQU 7E82h
K61_H EQU 0BD3Ah
K61_L EQU 0F235h
K62_H EQU 2AD7h
K62_L EQU 0D2BBh
K63_H EQU 0EB86h
K63_L EQU 0D391h
; Per-round shift amounts.
; Every 4 values repeat to fill gaps, i.e. 4-15 are 7,12,17,22,7,12,17,22,7,12,17,22.
R0 EQU 7
R1 EQU 12
R2 EQU 17
R3 EQU 22
R16 EQU 5
R17 EQU 9
R18 EQU 14
R19 EQU 20
R32 EQU 4
R33 EQU 11
R34 EQU 16
R35 EQU 23
R48 EQU 6
R49 EQU 10
R50 EQU 15
R51 EQU 21
.CODE
MAIN PROC FAR
; Set up the segment registers
MOV AX, @DATA
MOV DS, AX
MOV ES, AX
; Code
MOV AH, 09h
MOV DX, OFFSET PROMPT
INT 21h ; Display a prompt
MOV AH, 0Ah
MOV DX, OFFSET MAX_L
INT 21h ; Get user-entered string to hash
MOV AH, 03h
MOV BH, 0
INT 10h ; Get the cursor position
MOV AH, 02h
MOV DL, 79
INT 10h ; Move the cursor to column 80
MOV AH, 02h
MOV DL, " "
INT 21h ; Print a space to move to the next row
XOR AH, AH
MOV AL, ACTUAL
MOV LEN, AX ; Store length in LEN as 16-bit
MOV AX, OFFSET MSG
MOV BX, LEN
PUSH AX ; Push the arguments onto the stack
PUSH BX
CALL MD5 ; Calculate the MD5 of the string
ADD SP, 4 ; Clear the arguments from the stack
MOV AX, OFFSET H0 ; Get a pointer to the MD5 hash
PUSH AX
CALL PRINT ; Print the result
ADD SP, 2
; Return to DOS
DONE: MOV AH, 4Ch
INT 21h
MAIN ENDP
; -- Procedures ---------------------------------------------------------------
; Calculates the MD5 hash of a string.
; Pass a 16-bit pointer to the string and the string length as a 16-bit number.
; Arguments must be passed by pushing them onto the stack in that order.
MD5 PROC
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH BP
MOV BP, SP
; Arguments:
; [BP]+12: string length (16-bit)
; [BP]+14: pointer to string (16-bit)
; Initialize variables:
; copy H0_H:H0_L to H0
MOV DI, OFFSET H0
MOV AX, H0_L
MOV BX, H0_H
MOV [DI], AX
MOV [DI]+2, BX
; copy H1_H:H1_L to H1
MOV DI, OFFSET H1
MOV AX, H1_L
MOV BX, H1_H
MOV [DI], AX
MOV [DI]+2, BX
; copy H2_H:H2_L to H2
MOV DI, OFFSET H2
MOV AX, H2_L
MOV BX, H2_H
MOV [DI], AX
MOV [DI]+2, BX
; copy H3_H:H3_L to H3
MOV DI, OFFSET H3
MOV AX, H3_L
MOV BX, H3_H
MOV [DI], AX
MOV [DI]+2, BX
; Pre-processing:
MOV DI, [BP]+14 ; DI points to the string to hash
MOV BX, [BP]+12 ; BX holds the length of the string
AND BX, 0FFC0h ; BX = INT(BX/64)*64
ADD DI, BX ; DI points to the last 64-byte chunk
MOV BX, [BP]+12
AND BX, 003Fh ; BX = length MOD 64, last chunk length
MOV BYTE PTR [BX+DI], 80h ; Pad with binary 10000000
PP0: INC BX ; Increment chunk length
AND BX, 003Fh ; MOD 64 again
JNZ PP1
ADD DI, 0040h ; Add 64 to the chunk pointer
PP1: CMP BX, 0038h ; Stop if we've reached the 57th byte
JZ PP2
MOV BYTE PTR [BX+DI], 00h ; Pad with more 0's
JMP PP0 ; Loop until chunk is 56 bytes long
PP2: MOV AX, [BP]+12 ; AX holds the un-padded string length
MOV CX, 8 ; bits per byte
MUL CX ; DX:AX = original string length in bits
MOV [BX+DI], AX ; Append length (64-bit little endian)
MOV [BX+DI]+2, DX
MOV WORD PTR [BX+DI]+4, 0000h
MOV WORD PTR [BX+DI]+6, 0000h
ADD BX, 8 ; Increase the string length
ADD BX, DI
MOV DI, [BP]+14 ; Point to the beginning of the string
SUB BX, DI
MOV DX, BX ; DX = total string length (in bytes)
CHUNK: ; Initialize hash value for this chunk:
MOV AX, H0 ; Set VA = H0
MOV VA, AX
MOV AX, H0+2
MOV VA+2, AX
MOV AX, H1 ; Set VB = H1
MOV VB, AX
MOV AX, H1+2
MOV VB+2, AX
MOV AX, H2 ; Set VC = H2
MOV VC, AX
MOV AX, H2+2
MOV VC+2, AX
MOV AX, H3 ; Set VD = H3
MOV VD, AX
MOV AX, H3+2
MOV VD+2, AX
; ~~ Loops 0-15/63 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
FnF 0 ; f = d xor (b and (c xor d)), g = i
UpdVars R0, K0_L, K0_H
FnF 1
UpdVars R1, K1_L, K1_H
FnF 2
UpdVars R2, K2_L, K2_H
FnF 3
UpdVars R3, K3_L, K3_H
FnF 4
UpdVars R0, K4_L, K4_H
FnF 5
UpdVars R1, K5_L, K5_H
FnF 6
UpdVars R2, K6_L, K6_H
FnF 7
UpdVars R3, K7_L, K7_H
FnF 8
UpdVars R0, K8_L, K8_H
FnF 9
UpdVars R1, K9_L, K9_H
FnF 10
UpdVars R2, K10_L, K10_H
FnF 11
UpdVars R3, K11_L, K11_H
FnF 12
UpdVars R0, K12_L, K12_H
FnF 13
UpdVars R1, K13_L, K13_H
FnF 14
UpdVars R2, K14_L, K14_H
FnF 15
UpdVars R3, K15_L, K15_H
; ~~ Loops 16-31/63 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
FnG 16
UpdVars R16, K16_L, K16_H
FnG 17
UpdVars R17, K17_L, K17_H
FnG 18
UpdVars R18, K18_L, K18_H
FnG 19
UpdVars R19, K19_L, K19_H
FnG 20
UpdVars R16, K20_L, K20_H
FnG 21
UpdVars R17, K21_L, K21_H
FnG 22
UpdVars R18, K22_L, K22_H
FnG 23
UpdVars R19, K23_L, K23_H
FnG 24
UpdVars R16, K24_L, K24_H
FnG 25
UpdVars R17, K25_L, K25_H
FnG 26
UpdVars R18, K26_L, K26_H
FnG 27
UpdVars R19, K27_L, K27_H
FnG 28
UpdVars R16, K28_L, K28_H
FnG 29
UpdVars R17, K29_L, K29_H
FnG 30
UpdVars R18, K30_L, K30_H
FnG 31
UpdVars R19, K31_L, K31_H
; ~~ Loops 32-47/63 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
FnH 32
UpdVars R32, K32_L, K32_H
FnH 33
UpdVars R33, K33_L, K33_H
FnH 34
UpdVars R34, K34_L, K34_H
FnH 35
UpdVars R35, K35_L, K35_H
FnH 36
UpdVars R32, K36_L, K36_H
FnH 37
UpdVars R33, K37_L, K37_H
FnH 38
UpdVars R34, K38_L, K38_H
FnH 39
UpdVars R35, K39_L, K39_H
FnH 40
UpdVars R32, K40_L, K40_H
FnH 41
UpdVars R33, K41_L, K41_H
FnH 42
UpdVars R34, K42_L, K42_H
FnH 43
UpdVars R35, K43_L, K43_H
FnH 44
UpdVars R32, K44_L, K44_H
FnH 45
UpdVars R33, K45_L, K45_H
FnH 46
UpdVars R34, K46_L, K46_H
FnH 47
UpdVars R35, K47_L, K47_H
; ~~ Loops 48-63/63 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
FnI 48
UpdVars R48, K48_L, K48_H
FnI 49
UpdVars R49, K49_L, K49_H
FnI 50
UpdVars R50, K50_L, K50_H
FnI 51
UpdVars R51, K51_L, K51_H
FnI 52
UpdVars R48, K52_L, K52_H
FnI 53
UpdVars R49, K53_L, K53_H
FnI 54
UpdVars R50, K54_L, K54_H
FnI 55
UpdVars R51, K55_L, K55_H
FnI 56
UpdVars R48, K56_L, K56_H
FnI 57
UpdVars R49, K57_L, K57_H
FnI 58
UpdVars R50, K58_L, K58_H
FnI 59
UpdVars R51, K59_L, K59_H
FnI 60
UpdVars R48, K60_L, K60_H
FnI 61
UpdVars R49, K61_L, K61_H
FnI 62
UpdVars R50, K62_L, K62_H
FnI 63
UpdVars R51, K63_L, K63_H
; Add the hash values for this 64-byte chunk
MOV AX, VA ; h0 = h0 + a
ADD H0, AX
MOV AX, VA+2
ADC H0+2, AX
MOV AX, VB ; h1 = h1 + b
ADD H1, AX
MOV AX, VB+2
ADC H1+2, AX
MOV AX, VC ; h2 = h2 + c
ADD H2, AX
MOV AX, VC+2
ADC H2+2, AX
MOV AX, VD ; h3 = h3 + d
ADD H3, AX
MOV AX, VD+2
ADC H3+2, AX
ADD DI, 64 ; Increase the chunk pointer
SUB DX, 64 ; Decrease the string length
JNZ CHUNK ; Hash the next chunk if not done
POP BP
POP DX
POP CX
POP BX
POP AX
RET
MD5 ENDP
PRINT PROC
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH BP
MOV BP, SP
; Arguments:
; [BP]+12: pointer to the 16-byte binary hash
MOV AH, 2 ; Set AH for INT 21h (output character)
XOR BH, BH ; This should always = 0
MOV SI, OFFSET HEX
MOV DI, [BP]+12 ; DI points to the first byte
MOV CL, 16 ; Set up the loop counter
PRN_BYTE: MOV BL, [DI] ; Get the next byte
SHR BL, 1 ; Get high nibble by shifting right 4x
SHR BL, 1
SHR BL, 1
SHR BL, 1
MOV DL, [SI+BX] ; Get the ASCII hex digit
INT 21h ; Print it
MOV BL, [DI] ; Get the current byte again
AND BL, 0Fh ; Get the low nibble
MOV DL, [SI+BX] ; Get the hex digit
INT 21h ; And print it
INC DI ; Point to the next byte
DEC CL
JNZ PRN_BYTE ; Loop back until we're done
POP BP
POP DX
POP CX
POP BX
POP AX
RET
PRINT ENDP
END MAIN