Skip to content

Instantly share code, notes, and snippets.

@sloanlance
Last active October 7, 2026 02:18
Show Gist options
  • Select an option

  • Save sloanlance/af3b85bb3647a61c2617db3279975003 to your computer and use it in GitHub Desktop.

Select an option

Save sloanlance/af3b85bb3647a61c2617db3279975003 to your computer and use it in GitHub Desktop.
Apigee crypto SHA function tests — Testing to see which `crypto` SHA functions are available in the Google Apigee environment
Apigee crypto SHA function tests
// ============================================================
// TEMPORARY DIAGNOSTIC. Remove before production.
//
// Probes Apigee's JS crypto object against known test vectors.
// Verdicts are derived from the recorded results, so a missing
// test reports 'TEST MISSING' rather than a false negative.
// ============================================================
var INPUT = 'abc';
// Standard published vectors for the ASCII string "abc".
var EXPECTED = {
md5: '900150983cd24fb0d6963f7d28e17f72',
sha1: 'a9993e364706816aba3e25717850c26c9cd0d89d',
sha256: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
sha512: 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a' +
'2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'
};
// SHA-256 of the empty string.
var SHA256_EMPTY =
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
var results = {};
// Records one probe. 'expected' may be null for informational tests.
function record(label, expected, fn) {
var entry = {};
try {
var out = fn();
entry.value = (out === null || out === undefined)
? '<null/undefined>'
: String(out);
} catch (e) {
entry.error = 'ERROR: ' + (e.message || String(e));
}
if (entry.value !== undefined) {
entry.length = entry.value.length;
entry.isLowerHex = /^[0-9a-f]+$/.test(entry.value);
}
if (expected !== null && expected !== undefined) {
entry.expected = String(expected);
if (entry.error) {
entry.match = 'error';
} else if (entry.value === entry.expected) {
entry.match = 'exact';
} else if (entry.value.toLowerCase() === entry.expected.toLowerCase()) {
entry.match = 'case-insensitive-only';
} else {
entry.match = 'MISMATCH';
}
} else {
entry.match = 'informational';
}
results[label] = entry;
}
// Reads a verdict back out of 'results'. Guards against typo'd labels.
function matchOf(label) {
if (!Object.prototype.hasOwnProperty.call(results, label)) {
return 'TEST MISSING (' + label + ')';
}
return results[label].match || 'no-verdict';
}
// ---------- environment ----------------------------------------------------
results['_env.crypto.typeof'] = { value: typeof crypto, match: 'informational' };
if (typeof crypto === 'undefined') {
results['_env.fatal'] = {
value: 'crypto object is not available',
match: 'informational'
};
} else {
var members = [];
var types = [];
try {
for (var k in crypto) {
members.push(k);
try { types.push(k + ':' + (typeof crypto[k])); }
catch (e1) { types.push(k + ':<unreadable>'); }
}
} catch (e2) {
members.push('<not enumerable>');
}
results['_env.members'] = { value: members.join(','), match: 'informational' };
results['_env.memberTypes'] = { value: types.join(','), match: 'informational' };
// ---------- core: digest() hex, all four algorithms --------------------
record('md5.digest', EXPECTED.md5, function () {
var h = crypto.getMD5(); h.update(INPUT); return h.digest();
});
record('sha1.digest', EXPECTED.sha1, function () {
var h = crypto.getSHA1(); h.update(INPUT); return h.digest();
});
record('sha256.digest', EXPECTED.sha256, function () {
var h = crypto.getSHA256(); h.update(INPUT); return h.digest();
});
record('sha512.digest', EXPECTED.sha512, function () {
var h = crypto.getSHA512(); h.update(INPUT); return h.digest();
});
// ---------- alternate encodings (informational; App needs hex) -----
record('sha256.digest64', null, function () {
var h = crypto.getSHA256(); h.update(INPUT); return h.digest64();
});
record('sha512.digest64', null, function () {
var h = crypto.getSHA512(); h.update(INPUT); return h.digest64();
});
// ---------- update() semantics ----------------------------------------
// If update() appends, two calls should equal the single-call digest.
record('sha256.updateSplit', EXPECTED.sha256, function () {
var h = crypto.getSHA256(); h.update('a'); h.update('bc'); return h.digest();
});
// Three-part split, to confirm it is not just a two-call special case.
record('sha512.updateSplit3', EXPECTED.sha512, function () {
var h = crypto.getSHA512();
h.update('a'); h.update('b'); h.update('c');
return h.digest();
});
// digest() with no update() at all.
record('sha256.noUpdate', SHA256_EMPTY, function () {
var h = crypto.getSHA256(); return h.digest();
});
// update('') then digest().
record('sha256.emptyUpdate', SHA256_EMPTY, function () {
var h = crypto.getSHA256(); h.update(''); return h.digest();
});
// ---------- object lifecycle ------------------------------------------
// Two independent objects, same input: both must be correct.
record('sha512.freshA', EXPECTED.sha512, function () {
var h = crypto.getSHA512(); h.update(INPUT); return h.digest();
});
record('sha512.freshB', EXPECTED.sha512, function () {
var h = crypto.getSHA512(); h.update(INPUT); return h.digest();
});
// Calling digest() twice on one object: stable, reset, or error?
record('sha256.digestTwice', null, function () {
var h = crypto.getSHA256();
h.update(INPUT);
var first = h.digest();
var second = h.digest();
return 'first=' + first + ' second=' + second +
' same=' + (first === second);
});
// Reusing an object after digest(): does state carry over?
record('sha256.reuseAfterDigest', null, function () {
var h = crypto.getSHA256();
h.update(INPUT);
var first = h.digest();
h.update(INPUT);
var second = h.digest();
return 'first=' + first + ' second=' + second +
' secondIsAbcHash=' + (second === EXPECTED.sha256);
});
// ---------- getHash() alternate factory (informational) ---------------
record('getHash.SHA-512', null, function () {
var h = crypto.getHash('SHA-512'); h.update(INPUT); return h.digest();
});
record('getHash.SHA512', null, function () {
var h = crypto.getHash('SHA512'); h.update(INPUT); return h.digest();
});
// ---------- App usage pattern -------------------------------------
// Shape only; these are not real credentials.
var FAKE_SESSION_ID = 'ZjM0NWY2NzhhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmMDF8MTAwfA==';
var FAKE_TOKEN = '0123456789abcdef0123456789abcdef';
record('app.sha256.concat', null, function () {
var h = crypto.getSHA256(); h.update(FAKE_SESSION_ID + FAKE_TOKEN); return h.digest();
});
record('app.sha512.concat', null, function () {
var h = crypto.getSHA512(); h.update(FAKE_SESSION_ID + FAKE_TOKEN); return h.digest();
});
// Same inputs via split updates: must match the concatenated form.
record('app.sha512.split', null, function () {
var h = crypto.getSHA512();
h.update(FAKE_SESSION_ID); h.update(FAKE_TOKEN);
var split = h.digest();
var h2 = crypto.getSHA512();
h2.update(FAKE_SESSION_ID + FAKE_TOKEN);
return 'split=' + split + ' matchesConcat=' + (split === h2.digest());
});
}
// ---------- verdicts (derived from the records above) ----------------------
var verdicts = {};
verdicts['md5.digest'] = matchOf('md5.digest');
verdicts['sha1.digest'] = matchOf('sha1.digest');
verdicts['SHA256 hex digest'] = matchOf('sha256.digest');
verdicts['SHA512 hex digest'] = matchOf('sha512.digest');
verdicts['update() appends (256)'] = matchOf('sha256.updateSplit');
verdicts['update() appends (512)'] = matchOf('sha512.updateSplit3');
verdicts['fresh objects A'] = matchOf('sha512.freshA');
verdicts['fresh objects B'] = matchOf('sha512.freshB');
// Collect every failure across all tests that had an expectation.
var failures = [];
for (var label in results) {
if (!Object.prototype.hasOwnProperty.call(results, label)) { continue; }
var m = results[label].match;
if (m === 'MISMATCH' || m === 'error' || m === 'case-insensitive-only') {
failures.push(label + '=' + m);
}
}
// Overall go/no-go for the App appToken use case.
var sha256ok = (matchOf('sha256.digest') === 'exact');
var sha512ok = (matchOf('sha512.digest') === 'exact');
verdicts['_OVERALL'] =
(sha256ok && sha512ok)
? 'GO: SHA-256 and SHA-512 both produce correct lowercase hex via digest()'
: 'NO-GO: sha256=' + matchOf('sha256.digest') +
' sha512=' + matchOf('sha512.digest');
verdicts['_failures'] = failures.length ? failures.join(' | ') : 'none';
context.setVariable('cryptocheck.report',
JSON.stringify({ verdicts: verdicts, results: results }, null, 2));
{
"verdicts": {
"md5.digest": "exact",
"sha1.digest": "exact",
"SHA256 hex digest": "exact",
"SHA512 hex digest": "exact",
"update() appends (256)": "exact",
"update() appends (512)": "exact",
"fresh objects A": "exact",
"fresh objects B": "exact",
"_OVERALL": "GO: SHA-256 and SHA-512 both produce correct lowercase hex via digest()",
"_failures": "none"
},
"results": {
"_env.crypto.typeof": {
"value": "object",
"match": "informational"
},
"_env.members": {
"value": "getMD5,getSHA1,getSHA256,getSHA512,getHash,base64,wsSecRsaSign,wsSecRsaValidate,asBytes,dateFormat",
"match": "informational"
},
"_env.memberTypes": {
"value": "getMD5:function,getSHA1:function,getSHA256:function,getSHA512:function,getHash:function,base64:function,wsSecRsaSign:function,wsSecRsaValidate:function,asBytes:function,dateFormat:function",
"match": "informational"
},
"md5.digest": {
"value": "900150983cd24fb0d6963f7d28e17f72",
"length": 32,
"isLowerHex": true,
"expected": "900150983cd24fb0d6963f7d28e17f72",
"match": "exact"
},
"sha1.digest": {
"value": "a9993e364706816aba3e25717850c26c9cd0d89d",
"length": 40,
"isLowerHex": true,
"expected": "a9993e364706816aba3e25717850c26c9cd0d89d",
"match": "exact"
},
"sha256.digest": {
"value": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"length": 64,
"isLowerHex": true,
"expected": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"match": "exact"
},
"sha512.digest": {
"value": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"length": 128,
"isLowerHex": true,
"expected": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"match": "exact"
},
"sha256.digest64": {
"value": "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=",
"length": 44,
"isLowerHex": false,
"match": "informational"
},
"sha512.digest64": {
"value": "3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==",
"length": 88,
"isLowerHex": false,
"match": "informational"
},
"sha256.updateSplit": {
"value": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"length": 64,
"isLowerHex": true,
"expected": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"match": "exact"
},
"sha512.updateSplit3": {
"value": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"length": 128,
"isLowerHex": true,
"expected": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"match": "exact"
},
"sha256.noUpdate": {
"value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"length": 64,
"isLowerHex": true,
"expected": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"match": "exact"
},
"sha256.emptyUpdate": {
"value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"length": 64,
"isLowerHex": true,
"expected": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"match": "exact"
},
"sha512.freshA": {
"value": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"length": 128,
"isLowerHex": true,
"expected": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"match": "exact"
},
"sha512.freshB": {
"value": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"length": 128,
"isLowerHex": true,
"expected": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"match": "exact"
},
"sha256.digestTwice": {
"value": "first=ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad second=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 same=false",
"length": 153,
"isLowerHex": false,
"match": "informational"
},
"sha256.reuseAfterDigest": {
"value": "first=ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad second=ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad secondIsAbcHash=true",
"length": 163,
"isLowerHex": false,
"match": "informational"
},
"getHash.SHA-512": {
"value": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"length": 128,
"isLowerHex": true,
"match": "informational"
},
"getHash.SHA512": {
"value": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
"length": 128,
"isLowerHex": true,
"match": "informational"
},
"app.sha256.concat": {
"value": "11327fe57b85813a7b6f4b69d10fb90678315f1413ce6ed6379c7c44467ee3c0",
"length": 64,
"isLowerHex": true,
"match": "informational"
},
"app.sha512.concat": {
"value": "dc3fd032ed73ba15a05dc67e6db166be448ff5c78e243b0547064614047523de428b95f85c6f0ee074f7a4158c0f5f6c597064ccc91b15dad38e8249fb940a08",
"length": 128,
"isLowerHex": true,
"match": "informational"
},
"app.sha512.split": {
"value": "split=dc3fd032ed73ba15a05dc67e6db166be448ff5c78e243b0547064614047523de428b95f85c6f0ee074f7a4158c0f5f6c597064ccc91b15dad38e8249fb940a08 matchesConcat=true",
"length": 153,
"isLowerHex": false,
"match": "informational"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment