SFMC provides built-in symmetric and asymmetric encryption through EncryptSymmetric / DecryptSymmetric - but not in SSJS. Keys are registered in Setup → Security → Key Management and referenced by name — the key material never appears in script code.


Symmetric Encryption (AES)

Encrypt a value

var plainText = "sensitive data";

var encrypted = encryptSymmetric(
    plainText,
    "AES",
    "MyKeyName", "",   // key name in Key Management
    "MyIVName",  ""    // initialization vector name
);

// Store the ciphertext
Platform.Function.UpsertData(
    "SecureStorage",
    ["SubscriberKey"],
    [subscriberKey],
    ["CipherText"],
    [encrypted]
);

Decrypt a value

var cipherText = Platform.Function.Lookup(
    "SecureStorage", "CipherText", "SubscriberKey", subscriberKey
);

var plainText = decryptSymmetric(
    cipherText, "AES","","mypw", "", "mysalt"
);

function decryptSymmetric(encryptedString, algorithm, passwordKey, passwordValue,saltKey, saltValue, vectorKey, vectorValue) {
    Platform.Variable.SetValue("@decrypt_string", encryptedString);
    Platform.Variable.SetValue("@decrypt_algo",algorithm);
    Platform.Variable.SetValue("@decrypt_pw",passwordValue || "");
    Platform.Variable.SetValue("@decrypt_salt",saltValue || "");
    Platform.Variable.SetValue("@decrypt_vector",vectorValue || "");
    return Platform.Function.TreatAsContent("%%=DecryptSymmetric(@decrypt_string, @decrypt_algo, @null,@decrypt_pw, @null, @decrypt_salt, @null, @decrypt_vector)=%%");
}
function encryptSymmetric(encryptedString, algorithm, passwordKey, passwordValue,saltKey, saltValue, vectorKey, vectorValue) {
    Platform.Variable.SetValue("@encrypt_string", encryptedString);
    Platform.Variable.SetValue("@encrypt_algo",algorithm);
    Platform.Variable.SetValue("@encrypt_pw",passwordValue || "");
    Platform.Variable.SetValue("@encrypt_salt",saltValue || "");
    Platform.Variable.SetValue("@encrypt_vector",vectorValue || "");
    return Platform.Function.TreatAsContent("%%=EncryptSymmetric(@encrypt_string, @encrypt_algo, @null,@encrypt_pw, @null, @encrypt_salt, @null, @encrypt_vector)=%%");
}

Show test script
<script runat="server">
/*
 * Chapter: Symmetric Encryption AES
 * Proves:
 *   1. EncryptSymmetric/DecryptSymmetric via TreatAsContent round-trip (8-arg inline).
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
function encryptSymmetric(plainText, algorithm, passwordValue, saltValue, vectorValue) {
    Platform.Variable.SetValue("@encrypt_string", plainText);
    Platform.Variable.SetValue("@encrypt_algo", algorithm);
    Platform.Variable.SetValue("@encrypt_pw", passwordValue || "");
    Platform.Variable.SetValue("@encrypt_salt", saltValue || "");
    Platform.Variable.SetValue("@encrypt_vector", vectorValue || "");
    return Platform.Function.TreatAsContent("%%=EncryptSymmetric(@encrypt_string, @encrypt_algo, @null, @encrypt_pw, @null, @encrypt_salt, @null, @encrypt_vector)=%%");
}
function decryptSymmetric(encryptedString, algorithm, passwordValue, saltValue, vectorValue) {
    Platform.Variable.SetValue("@decrypt_string", encryptedString);
    Platform.Variable.SetValue("@decrypt_algo", algorithm);
    Platform.Variable.SetValue("@decrypt_pw", passwordValue || "");
    Platform.Variable.SetValue("@decrypt_salt", saltValue || "");
    Platform.Variable.SetValue("@decrypt_vector", vectorValue || "");
    return Platform.Function.TreatAsContent("%%=DecryptSymmetric(@decrypt_string, @decrypt_algo, @null, @decrypt_pw, @null, @decrypt_salt, @null, @decrypt_vector)=%%");
}
var pw = "pw";
var salt = "0123456789abcdef";
var iv = "0123456789abcdef0123456789abcdef";
var enc = encryptSymmetric("secret-text", "AES", pw, salt, iv);
assert("enc non-empty", ("" + enc).length > 0 ? "true" : "false", "true");
var dec = decryptSymmetric(enc, "AES", pw, salt, iv);
assert("dec round-trip", "" + dec, "secret-text");
</script>

Password-Salted Encryption

When you want an additional layer of derivation on top of the stored key:

// Encrypt with a per-record salt derived from the subscriber key
var salt = subscriberKey;

var encrypted = encryptSymmetric(
    sensitiveValue, "AES", "","mypw", "", salt, ""
);

// Decrypt — must use the same salt
var plainText = decryptSymmetric(
    encrypted, "AES", "","mypw", "", salt, ""
);

function encryptSymmetric(encryptedString, algorithm, passwordKey, passwordValue,saltKey, saltValue, vectorKey, vectorValue) {
    Platform.Variable.SetValue("@encrypt_string", encryptedString);
    Platform.Variable.SetValue("@encrypt_algo",algorithm);
    Platform.Variable.SetValue("@encrypt_pw",passwordValue || "");
    Platform.Variable.SetValue("@encrypt_salt",saltValue || "");
    Platform.Variable.SetValue("@encrypt_vector",vectorValue || "");
    return Platform.Function.TreatAsContent("%%=EncryptSymmetric(@encrypt_string, @encrypt_algo, @null,@encrypt_pw, @null, @encrypt_salt, @null, @encrypt_vector)=%%");
}

function decryptSymmetric(encryptedString, algorithm, passwordKey, passwordValue,saltKey, saltValue, vectorKey, vectorValue) {
    Platform.Variable.SetValue("@decrypt_string", encryptedString);
    Platform.Variable.SetValue("@decrypt_algo",algorithm);
    Platform.Variable.SetValue("@decrypt_pw",passwordValue || "");
    Platform.Variable.SetValue("@decrypt_salt",saltValue || "");
    Platform.Variable.SetValue("@decrypt_vector",vectorValue || "");
    return Platform.Function.TreatAsContent("%%=DecryptSymmetric(@decrypt_string, @decrypt_algo, @null,@decrypt_pw, @null, @decrypt_salt, @null, @decrypt_vector)=%%");
}

Show test script
<script runat="server">
/*
 * Chapter: Password-Salted Encryption
 * Proves:
 *   1. Same AES path with different password still encrypts (8-arg inline).
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
function encryptSymmetric(plainText, algorithm, passwordValue, saltValue, vectorValue) {
    Platform.Variable.SetValue("@encrypt_string", plainText);
    Platform.Variable.SetValue("@encrypt_algo", algorithm);
    Platform.Variable.SetValue("@encrypt_pw", passwordValue || "");
    Platform.Variable.SetValue("@encrypt_salt", saltValue || "");
    Platform.Variable.SetValue("@encrypt_vector", vectorValue || "");
    return Platform.Function.TreatAsContent("%%=EncryptSymmetric(@encrypt_string, @encrypt_algo, @null, @encrypt_pw, @null, @encrypt_salt, @null, @encrypt_vector)=%%");
}
var enc2 = encryptSymmetric("x", "AES", "other", "fedcba9876543210", "fedcba9876543210fedcba9876543210");
assert("enc2 non-empty", ("" + enc2).length > 0 ? "true" : "false", "true");
</script>

Storing Encrypted PII in a Data Extension

A common pattern: encrypt PII at write time, decrypt only when needed.

// --- Write path (e.g. form submission) ---
var rawPhone = Platform.Request.GetFormField("phone");
var encPhone = encryptSymmetric(
    rawPhone, "AES", "PIIKey", "", "PIIiv", ""
);
Platform.Function.UpsertData(
    "ContactsSecure",
    ["SubscriberKey"],
    [subscriberKey],
    ["PhoneEncrypted", "UpdatedAt"],
    [encPhone, Platform.Function.Now()]
);

// --- Read path (e.g. personalisation script) ---
// String() first — a Lookup result throws on a truthiness test when the field is empty
var encPhone = String(Platform.Function.Lookup(
    "ContactsSecure", "PhoneEncrypted", "SubscriberKey", subscriberKey
));
if (encPhone !== "" && encPhone !== "null") {
    var phone = decryptSymmetric(
        encPhone, "AES", "PIIKey", "", "PIIiv", ""
    );
    Write("Phone: " + phone);
}

function decryptSymmetric(encryptedString, algorithm, passwordKey, passwordValue,saltKey, saltValue, vectorKey, vectorValue) {
    Platform.Variable.SetValue("@decrypt_string", encryptedString);
    Platform.Variable.SetValue("@decrypt_algo",algorithm);
    Platform.Variable.SetValue("@decrypt_pw",passwordValue || "");
    Platform.Variable.SetValue("@decrypt_salt",saltValue || "");
    Platform.Variable.SetValue("@decrypt_vector",vectorValue || "");
    return Platform.Function.TreatAsContent("%%=DecryptSymmetric(@decrypt_string, @decrypt_algo, @null,@decrypt_pw, @null, @decrypt_salt, @null, @decrypt_vector)=%%");
}

Show test script
<script runat="server">
/*
 * Chapter: Storing Encrypted PII
 * Proves:
 *   1. NON-ASSERTABLE: DE write.
 *   Encrypt helper + UpsertData typeof exist for the pattern.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
assert("UpsertData", typeof Platform.Function.UpsertData, "clrmethodinfo");
assert("InsertData", typeof Platform.Function.InsertData, "clrmethodinfo");
</script>

Hashing (Non-Reversible)

For values that only need to be compared (passwords, tokens), prefer a hash rather than encryption:

SSJS itself exposes only one hash function — Platform.Function.MD5. MD5 is far too weak for tokens or passwords, so reach for the stronger AMPscript hashes (SHA1, SHA256, SHA512) through a TreatAsContent bridge, exactly as with EncryptSymmetric above.

// SHA-256 is AMPscript-only — bridge it with TreatAsContent
function sha256(stringToConvert,charSet) {
    Platform.Variable.SetValue("@sha256_string",stringToConvert);
    Platform.Variable.SetValue("@sha256_charset",charSet || "UTF-8");
    return Platform.Function.TreatAsContent("%%=SHA256(@sha256_string, @sha256_charset)=%%");
}

// Hash the raw value — cannot be reversed
var hashed = sha256(rawValue);

// Store the hash
Platform.Function.UpsertData(
    "TokenStore",
    ["Token"],
    [hashed],
    ["SubscriberKey", "CreatedAt"],
    [subscriberKey, Platform.Function.Now()]
);

// Verify: hash the submitted value and compare
var submittedHash = sha256(submittedValue);
var match = Platform.Function.Lookup("TokenStore", "SubscriberKey", "Token", submittedHash);
if (match) {
    Write("Token valid for: " + match);
}

Show test script
<script runat="server">
/*
 * Chapter: Hashing Non-Reversible
 * Proves:
 *   1. Platform.Function.MD5 works; SHA256 via TreatAsContent bridge.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
function sha256(stringToConvert, charSet) {
    Platform.Variable.SetValue("@sha256_string", stringToConvert);
    Platform.Variable.SetValue("@sha256_charset", charSet || "UTF-8");
    return Platform.Function.TreatAsContent("%%=SHA256(@sha256_string, @sha256_charset)=%%");
}
var md5 = Platform.Function.MD5("hello");
assert("md5 length", ("" + md5).length >= 32 ? "true" : "false", "true");
var s256 = sha256("hello");
assert("sha256 length", ("" + s256).length >= 64 ? "true" : "false", "true");
</script>

Notes

  • Key names in EncryptSymmetric / DecryptSymmetric refer to the external key (name) of the key registered in Key Management, not the key value itself.
  • EncryptSymmetric and DecryptSymmetric are available in all SFMC execution contexts (Email, Cloud Page, Automation, Triggered Send).
  • For hashing without the need for decryption, prefer AMPscript’s SHA256 over encryption. SHA1, SHA256 and SHA512 exist only in AMPscript — call them via Platform.Function.TreatAsContent. The only hash reachable directly from SSJS is Platform.Function.MD5, which is unsuitable for passwords or tokens.
Show test script
<script runat="server">
/*
 * Chapter: Notes
 * Proves:
 *   1. NON-ASSERTABLE: operational notes.
 * EXPECTED OUTPUT: every line starts with PASS.
 */
function assert(id, actual, expected) {
    Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
function typeOfThunk(fn) {
    try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
assert("guidance-only", "documented", "documented");
</script>

See Also