Interested in a ServiceNow event built for developers? Registration for now[dev]26 is officially open!

sachinbhasin11
Tera Guru

Bring Your Own Key to ServiceNow KMF — Part 3: Encrypting from Script

This is Part 3 of a three-part series on using your own encryption key inside ServiceNow's Key Management Framework. Part 1 covered preparing the instance; Part 2 covered importing your key. Here we encrypt with it.

 

Only one output format works

var op = new sn_kmf_ns.KMFCryp**Operation(module, 'SYMMETRIC_ENCRYPTION')
    .withInputFormat('KMFNONE')
    .withOutputFormat('FORMATTED')
    .withOutputType('STRING');
var ct = op.doOperation(JSON.stringify(payload));

⚠️ FORMATTED is the only output format that works for SYMMETRIC_ENCRYPTION. I tested all nine input/output combinations — KMFNONE and KMFBASE64 both throw "Currently doesn't support this combination". Decryption is the mirror: in FORMATTED, out KMFNONE.

 

Omit withInputFormat and KMF tries to base64-decode your plaintext, failing with "String to be decoded has special characters or is not valid Base64" — an error that points at the output when the problem is the input.

 

The envelope

FORMATTED wraps the ciphertext in ServiceNow's own container, delimited by Unicode markers:

 

Marker Contents
U+FDD2 key sys_id (32 hex chars)
U+FDD4 key version
U+FDED body — IV then ciphertext
U+FDEE end of body

 

Inside the body the IV and ciphertext are each separately base64-encoded, and the ciphertext uses base64url (- and _). That last detail is the single most likely thing to break on the receiving end, so convert to standard base64 before anything leaves the platform.

 

A reusable Script Include

Rather than parsing that at every call site, wrap it once. The full PayloadCryp** Script Include is on GitHub Gist — the core of it:

encryptRaw: function (payload) {
    var plain = (typeof payload === 'string') ? payload : JSON.stringify(payload);
    return new sn_kmf_ns.KMFCryp**Operation(this.module, 'SYMMETRIC_ENCRYPTION')
        .withInputFormat('KMFNONE')
        .withOutputFormat('FORMATTED')
        .withOutputType('STRING')
        .doOperation(plain);
},

encryptPayload: function (payload) {
    var raw  = this.encryptRaw(payload);
    var body = raw.substring(raw.indexOf(this.D_BODY) + 1, raw.indexOf(this.D_END));
    var ks   = raw.indexOf(this.D_KEY) + 1;

    var ivBytes  = this._decode(body.substring(0, this.IV_B64_LEN));
    var allBytes = this._decode(body.substring(this.IV_B64_LEN));
    var n = allBytes.length;

    return {
        keyId: raw.substring(ks, ks + 32),
        alg:   'AES-256-GCM',
        iv:    this._encode(ivBytes),
        data:  this._encode(allBytes.slice(0, n - this.TAG_LEN)),
        tag:   this._encode(allBytes.slice(n - this.TAG_LEN))
    };
}

 

Usage:

var pc = new PayloadCryp**('global.my_module');
var envelope = pc.encryptPayload({orderId: 'ORD-1001'});
// {keyId, alg, iv, data, tag} — all standard base64

Three design notes:

  • Take the module name as a constructor argument, backed by a system property. Swapping keys then becomes a config change, not a deployment.
  • encryptRaw/decryptRaw are the internal pair; encryptPayload is one-way outbound. Feeding envelope.data back into decryptRaw will not work — the envelope was deliberately stripped. Worse, KMF returns the input unchanged rather than erroring, so it looks like decryption silently did nothing.
  • The base64 helpers are hand-rolled because java.util.Arrays.copyOfRange is blocked in the script engine. So is all of javax.cryp**.

 

Two things that will save you a week

KMF's GCM IV is 16 bytes. Most implementations assume 12, and many accept nothing else. There is no setting for this — not in the API, not on the algorithm record. If your counterparty needs 12, GCM is off the table and CBC is the answer: its IV is 16 bytes by definition, so there is nothing to negotiate.

Establish this before building. Ask for a sample — a known plaintext plus their encrypted output. Byte lengths reveal the cipher, IV size and tag placement in minutes.

Emit a key identifier in your envelope from day one. Rotation without one means a flag-day cutover with your partner.


Tested on Yokohama. Corrections welcome — particularly if anyone has found a way to influence the GCM IV length.

 
 
 
Version history
Last update:
an hour ago
Updated by:
Contributors