MD5
Returns the MD5 hash of the input value. Runtime-proven on a live Marketing Cloud Engagement CloudPage — including the fact that the bytes hashed are the UTF-8 encoding of the input, and that the encoding argument accepts far more names than either reference lists.
Syntax
MD5(stringToConvert[, charSet]) → string
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
stringToConvert |
string | number | date | Yes | The value to hash; a number or date is hashed as the text it renders as |
charSet |
string | No | Name of the character encoding applied before hashing; defaults to UTF-8 |
Example
%%[
VAR @digest
SET @digest = MD5("Hash probe 2026")
]%%
%%=v(@digest)=%%
Renders 2bfdbd320b3b56c0d8c4be16462a96b7.
The usual reason to reach for a hash is a stable, non-reversible key for a value you do not want to put in a URL:
%%[
VAR @email, @token
SET @email = "reader@example.org"
SET @token = MD5(Lowercase(Trim(@email)))
]%%
<a href="https://example.org/prefs?id=%%=v(@token)=%%">Manage preferences</a>
Normalise before hashing, as above — a digest of a differently-cased or space-padded value is a different digest entirely.
Return value
string — 32 lowercase hexadecimal characters with no separators, measured with Length() on the page rather than assumed.
There is no closed set of sentinel values to test for: every input that is accepted produces a digest, and every input that is rejected aborts the page instead of returning an error token.
Behaviour
The digest is the real MD5 of the input, not a look-alike. Every value on this page was compared character for character against the same digest computed independently outside Marketing Cloud, over the same bytes. MD5("Hash probe 2026") gave 2bfdbd320b3b56c0d8c4be16462a96b7, and the official reference’s own example value reproduced exactly.
The bytes hashed are the UTF-8 encoding of the input. That cannot be seen with an ASCII string, because ASCII text has only one plausible encoding. Hashing a string containing ß, € and ä gave 45e83c6ea8202aae452acf5f61f0d660, which is the digest of its UTF-8 bytes and not of the UTF-16 form the engine uses internally.
The second argument genuinely changes the value. The same input under UTF-16 gave 75300cf3b34da08a0cfd6958b26e1bae — a completely different digest, and the one you get from UTF-16 little-endian bytes. Encoding names are matched without regard to case: utf-8 and utf-16 behaved identically to their upper-case spellings.
The empty string is hashed, not refused. MD5("") returned d41d8cd98f00b204e9800998ecf8427e, the well-known digest of zero bytes.
A number is accepted and hashed as its decimal text. MD5(2026) gave c92a10324374fac681719d63979d00fe, which is the digest of the four characters 2026.
The encoding argument is stricter than it looks
ASCII is accepted, and it silently replaces every character it cannot represent with a question mark before hashing. The non-ASCII string above came back as 1a7a47e1752ce663f442793ac4b38476 — the digest of Grus? ??. Nothing warns you; the value is simply wrong for the input you passed.
An unrecognised name is not tolerated at all. banana, the dashless spelling UTF8, an empty string, and a number in that position each aborted the page with HTTP 422 rather than falling back to the default.
Show test script
%%[
VAR @b, @a, @nb
SET @b = RequestParameter("b")
SET @a = "Hash probe 2026"
SET @nb = Concat("Grus", Char(223), " ", Char(8364), Char(228))
/* known-good control: renders on every request, so a run of HTTP 422s
can be told apart from a broken deploy */
OutputLine(Concat("CTRL=[", MD5(@a), "]"))
/* the plain one-argument form, its length, and the empty-input digest */
IF @b == "safe" THEN
OutputLine(Concat("--- safe start ---"))
OutputLine(Concat("M1=[", MD5(@a), "]"))
OutputLine(Concat("ML=[", Length(MD5(@a)), "]"))
OutputLine(Concat("EM=[", MD5(""), "]"))
OutputLine(Concat("--- safe done ---"))
ENDIF
/* the second argument really changes the digest, and names are
matched without regard to case */
IF @b == "enc" THEN
OutputLine(Concat("--- enc start ---"))
OutputLine(Concat("M8=[", MD5(@a, "UTF-8"), "]"))
OutputLine(Concat("M16=[", MD5(@a, "UTF-16"), "]"))
OutputLine(Concat("CSLOW8=[", MD5(@nb, "utf-8"), "]"))
OutputLine(Concat("CSLOW16=[", MD5(@nb, "utf-16"), "]"))
OutputLine(Concat("--- enc done ---"))
ENDIF
/* the default encoding is the UTF-8 byte sequence, not the engine's
internal UTF-16 form - only a non-ASCII input can tell them apart */
IF @b == "nonascii" THEN
OutputLine(Concat("--- nonascii start ---"))
OutputLine(Concat("NBIN=[", @nb, "]"))
OutputLine(Concat("NM8=[", MD5(@nb), "]"))
OutputLine(Concat("NM16=[", MD5(@nb, "UTF-16"), "]"))
OutputLine(Concat("--- nonascii done ---"))
ENDIF
/* ASCII silently substitutes a question mark for anything it cannot
represent, which changes the digest without any signal */
IF @b == "csascii" THEN
OutputLine(Concat("--- csascii start ---"))
OutputLine(Concat("CSASCII=[", MD5(@nb, "ASCII"), "]"))
OutputLine(Concat("--- csascii done ---"))
ENDIF
/* a number is hashed as the text it renders as */
IF @b == "numin" THEN
OutputLine(Concat("--- numin start ---"))
OutputLine(Concat("NUMIN=[", MD5(2026), "]"))
OutputLine(Concat("--- numin done ---"))
ENDIF
/* each of the four branches below aborts the page - fetch alone */
IF @b == "csbad" THEN
OutputLine(Concat("--- csbad start ---"))
OutputLine(Concat("CSBAD=[", MD5(@a, "banana"), "]"))
ENDIF
IF @b == "csnodash" THEN
OutputLine(Concat("--- csnodash start ---"))
OutputLine(Concat("CSNODASH=[", MD5(@nb, "UTF8"), "]"))
ENDIF
IF @b == "csnum" THEN
OutputLine(Concat("--- csnum start ---"))
OutputLine(Concat("CSNUM=[", MD5(@a, 8), "]"))
ENDIF
IF @b == "csempty" THEN
OutputLine(Concat("--- csempty start ---"))
OutputLine(Concat("CSEMPTY=[", MD5(@a, ""), "]"))
ENDIF
IF @b == "a0" THEN
OutputLine(Concat("--- a0 start ---"))
OutputLine(Concat("A0=[", MD5(), "]"))
ENDIF
IF @b == "a3" THEN
OutputLine(Concat("--- a3 start ---"))
OutputLine(Concat("A3=[", MD5(@a, "UTF-8", "extra"), "]"))
ENDIF
]%%
A bare string literal passed to OutputLine renders an empty line while the page still returns HTTP 200, so the marker silently vanishes and the block looks like a function that produced no output. Always wrap it — OutputLine(Concat("--- safe start ---")) — even for a single argument.
When a case involves non-ASCII characters, print the input string alongside the digest. A mangled test string produces a perfectly valid digest of the wrong bytes, which is indistinguishable from a function defect unless the input is visible in the same output.
Availability
| Platform | Available |
|---|---|
| Marketing Cloud Engagement | Yes |
| Marketing Cloud Next | No |
See also
- SHA1 · SHA256 · SHA512 — the same signature, longer digests
- Official reference · ampscript.guide