Welcome to the new DelphiDabbler Code Library Documentation.

This is a new site that's currently running on alpha code. There are going to be bugs. If you discover any, please report them on the site's issues page (GitHub account required). Thanks.

Warning: Many URLs are going to change. Refer to the README file to discover which library project's documentation has been completed.

MD5 How-to: How To Get the MD5 Hash of a Record

Applies to: ~>1.0

In most cases you get the MD5 hash of a record by creating a new empty hash and adding the value of each field of the record to the hash. How you handle each field depends on its type. If one or more of the fields are also records then the technique is applied recursively.

Because we must add each field in turn to the hash we have to create a TPJMD5 instance and use the TPJMD5.Process method. We can’t use TPJMD5.Calculate.

For an explanation of the difference between TPJMD5.Calculate and TPJMD5.Process see here.

For example, if you have a record of type TFoo defined as:

type
  TFoo = record
    B: Byte;
    AStr: AnsiString;
    IA: array[1..4] of Integer;
    SA: TArray<string>;
    R: record
      F1: Double;
      F2: Extended;
    end;
  end;

then the following function can be used to get its hash:

function MD5OfTFoo(const Foo: TFoo): TPJMD5Digest;
var
  MD5: TPJMD5;
  S: string;
begin
  MD5 := TPJMD5.Create;
  try
    MD5.Process(Foo.B, SizeOf(Foo.B));                 // ordinal type
    MD5.Process(Foo.AStr);                             // ANSI string type
    MD5.Process(Foo.IA[Low(Foo.IA)], SizeOf(Foo.IA));  // static array type
    for S in Foo.SA do                                 // dynamic reference array type
      MD5.Process(S, TEncoding.UTF8);
    // add fields of embedded record
    MD5.Process(Foo.R.F1, SizeOf(Foo.R.F1));           // floating point type
    MD5.Process(Foo.R.F2, SizeOf(Foo.R.F2));           // floating point type
    // return required digest
    Result := MD5.Digest;
  finally
    MD5.Free;
  end;
end;

First we create our TPJMD5 instance then add each field in turn to the hash using various techniques, each of which is explained in other how-to topics (see the link list below).

Special Case

In the special case of a packed record containing only value fields of simple types we can use the untyped overload of TPJMD5.Calculate to get the MD5 hash of the whole record at once.

Assume you have this record:

type
  TBar = packed record
    B: Byte;
    W: Word;
    LW: LongWord;
  end;

then the following function will get the MD5 hash of a TBar variable:

function MD5OfTBar(const Bar: TBar): TPJMD5Digest;
begin
  Result := TPJMD5.Calculate(Bar, SizeOf(Bar));
end;

This works because the call to TPJMD5.Calculate gets the MD5 hash of all the bytes of Bar.

You should not use this technique with unpacked records because there will probably be padding bytes inserted in the record which may not be initialised to known values, which may change between executions. Since all the bytes of the record are processed the values of the padding bytes are included in the hash, giving unexpected results.

Furthermore the technique only works with value fields. Reference fields such as strings, objects and dynamic arrays only store pointers in the record, not the actual values. Therefore the pointers will be hashed, which is not what we want.

See Also