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 buffer

  • len: 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