MD5 Hash
Unit: MD5
RFC 1321 compliant MD5 implementation for Turbo Pascal 7.0 (1992 era-appropriate).
Overview
MD5 produces a 128-bit (16-byte) hash from arbitrary input data. Perfect for:
Asset integrity verification
Save game checksums
File integrity checks
Quick content identification
Basic Usage
Hash a String
uses MD5;
var
hash: String;
begin
hash := MD5String('hello world');
WriteLn(hash); { Outputs: 5eb63bbbe01eeed093cb22bb8f5acdc3 }
end;
Hash a File
uses MD5;
var
digest: TMD5Digest;
begin
if MD5File('DATA\FONT.PCX', digest) then
WriteLn('Hash: ', MD5DigestToHex(digest))
else
WriteLn('Error reading file');
end;
Incremental Hashing
For large data processed in chunks:
uses MD5;
var
ctx: TMD5Context;
digest: TMD5Digest;
buffer: array[0..1023] of Byte;
begin
MD5Init(ctx);
{ Process data in chunks }
MD5Update(ctx, @buffer, 1024);
MD5Update(ctx, @buffer, 1024);
{ Finalize and get result }
MD5Final(digest, ctx);
WriteLn(MD5DigestToHex(digest));
end;
API Reference
Types
TMD5Digest
TMD5Digest = array[0..15] of Byte;
128-bit hash result (16 bytes).
TMD5Context
TMD5Context = record
State: array[0..3] of LongInt;
Count: array[0..1] of LongInt;
Buffer: array[0..63] of Byte;
end;
Internal state for incremental hashing.
Core Functions
MD5Init
procedure MD5Init(var ctx: TMD5Context);
Initialize context for hashing. Call before first MD5Update.
MD5Update
procedure MD5Update(var ctx: TMD5Context; buf: Pointer; len: Word);
Add data to hash. Can be called multiple times.
buf: Pointer to data bufferlen: Number of bytes to hash (max 65535)
MD5Final
procedure MD5Final(var digest: TMD5Digest; var ctx: TMD5Context);
Finalize hash and produce digest. Call after all MD5Update calls.
Convenience Functions
MD5String
function MD5String(const s: String): String;
Hash a Pascal string, return 32-character hex result.
MD5File
function MD5File(const path: String; var digest: TMD5Digest): Boolean;
Hash entire file. Returns True on success, False on I/O error.
MD5DigestToHex
function MD5DigestToHex(const digest: TMD5Digest): String;
Convert 16-byte digest to 32-character lowercase hex string.
MD5DigestEqual
function MD5DigestEqual(const a, b: TMD5Digest): Boolean;
Compare two digests for equality.
Practical Examples
Asset Verification
procedure VerifyAssets;
const
ExpectedHash = 'abc123def456...';
var
digest: TMD5Digest;
begin
if MD5File('DATA\FONT.PCX', digest) then
begin
if MD5DigestToHex(digest) = ExpectedHash then
WriteLn('Asset verified OK')
else
WriteLn('WARNING: Asset corrupted or modified!');
end;
end;
Save Game Checksum
type
TSaveGame = record
PlayerX, PlayerY: Integer;
Score: LongInt;
Level: Byte;
Checksum: TMD5Digest;
end;
procedure SaveGame(const save: TSaveGame);
var
f: File of TSaveGame;
ctx: TMD5Context;
temp: TSaveGame;
begin
temp := save;
{ Calculate checksum of everything except checksum field }
MD5Init(ctx);
MD5Update(ctx, @temp, SizeOf(TSaveGame) - SizeOf(TMD5Digest));
MD5Final(temp.Checksum, ctx);
{ Write to file }
Assign(f, 'SAVE.DAT');
Rewrite(f);
Write(f, temp);
Close(f);
end;
function LoadGame(var save: TSaveGame): Boolean;
var
f: File of TSaveGame;
ctx: TMD5Context;
checksum: TMD5Digest;
begin
LoadGame := False;
Assign(f, 'SAVE.DAT');
{$I-} Reset(f); {$I+}
if IOResult <> 0 then Exit;
Read(f, save);
Close(f);
{ Verify checksum }
MD5Init(ctx);
MD5Update(ctx, @save, SizeOf(TSaveGame) - SizeOf(TMD5Digest));
MD5Final(checksum, ctx);
LoadGame := MD5DigestEqual(save.Checksum, checksum);
end;
Creating an Asset Manifest
procedure CreateManifest;
var
f: Text;
digest: TMD5Digest;
begin
Assign(f, 'MANIFEST.TXT');
Rewrite(f);
if MD5File('DATA\FONT.PCX', digest) then
WriteLn(f, 'FONT.PCX=', MD5DigestToHex(digest));
if MD5File('DATA\MUSIC.HSC', digest) then
WriteLn(f, 'MUSIC.HSC=', MD5DigestToHex(digest));
Close(f);
end;
Performance
Measured on 286 @ 12 MHz (typical target system):
Input Size |
Time |
|---|---|
32 bytes (short string) |
< 1ms |
256 bytes (string) |
~5ms |
4 KB (config file) |
~50ms |
64 KB (full screen) |
~800ms |
Recommendations:
Use at startup for asset verification (not in game loop)
Hash small data (strings, configs) anytime
For large files, show “Loading…” message
Consider hashing during install, not runtime
Test Vectors (RFC 1321)
These are the official MD5 test vectors. Use MD5TEST.PAS to verify:
MD5("") = d41d8cd98f00b204e9800998ecf8427e
MD5("a") = 0cc175b9c0f1b6a831c399e269772661
MD5("abc") = 900150983cd24fb0d6963f7d28e17f72
MD5("message digest") = f96b697d7cb7938d525a2f31aaf161d0
MD5("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
Implementation Notes
Era-appropriate: MD5 was published in April 1992 (RFC 1321), fitting perfectly with the 1994 engine era
Algorithm: Four rounds of operations on 512-bit blocks
Constants: 64 T-table values derived from sine function
Endianness: Little-endian encoding (x86 native)
Bit rotation: Implemented using shifts (no native ROL in Pascal)
Max block size: 65535 bytes per MD5Update call (Word limitation)
Security Note
MD5 is cryptographically broken (since 2004) and should NOT be used for:
Password hashing (use PBKDF2 or bcrypt)
Digital signatures
Security-critical applications
MD5 is SAFE for:
Checksums and data integrity (non-adversarial)
Asset verification
Quick content identification
File deduplication
For this retro game engine, MD5 is perfect for detecting accidental corruption or modification of game assets.
Files
UNITS\MD5.PAS - Main implementation
TESTS\MD5TEST.PAS - RFC 1321 test vectors
TESTS\MD5DEMO.PAS - Practical usage examples
TESTS\CMD5TEST.BAT - Compile test
TESTS\CMD5DEMO.BAT - Compile demo
References
RFC 1321: The MD5 Message-Digest Algorithm (April 1992)
Ron Rivest, MIT Laboratory for Computer Science