I’m trying to generate a UUID from a string, I basically want to recreate what UUID.nameUUIDFromBytes does in Java. I found this article that works perfect outside of postman, Java's UUID.nameUUIDFromBytes to written in JavaScript? - Stack Overflow
but crypto is not available in Postman. I’ve also tried CryptoJS in postman and have gotten really close, the generated UUID is off by a single character…
function javaHash(test) {
var md5Bytes = CryptoJS.MD5(test);
console.log(md5Bytes);
md5Bytes[6] &= 0x0f; /* clear version */
md5Bytes[6] |= 0x30; /* set to version 3 */
md5Bytes[8] &= 0x3f; /* clear variant */
md5Bytes[8] |= 0x80; /* set to IETF variant */
console.log(md5Bytes);
return md5Bytes.toString(CryptoJS.enc.Hex).replace(/-/g, "").replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, "$1-$2-$3-$4-$5");
}
console.log(javaHash('123456789'));
From looking at the value in the console it doesnt look like the value is being changed by whatever magic (setting the version and variabnt) is supposed to happen in the middle of the method.
I’ve also tried importing crypto from here: https://cdnjs.com/libraries/crypto-js, with this method: Adding External Libraries in Postman | Postman Blog
function javaHash(test) {
eval(pm.collectionVariables.get("crypto_library"));
let md5Bytes = this.crypto.createHash('md5').update(test).digest();
console.log(md5Bytes);
md5Bytes[6] &= 0x0f; /* clear version */
md5Bytes[6] |= 0x30; /* set to version 3 */
md5Bytes[8] &= 0x3f; /* clear variant */
md5Bytes[8] |= 0x80; /* set to IETF variant */
console.log(md5Bytes);
return md5Bytes.toString(CryptoJS.enc.Hex).replace(/-/g, "").replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, "$1-$2-$3-$4-$5");
}
but I get an error “There was an error in evaluating the Pre-request Script:TypeError: Cannot read properties of undefined (reading ‘lib’)”
Any ideas?
The problem in your CryptoJS code is that md5Bytes
is not an array of bytes, so the subsequent byte manipulations fall flat.
CryptoJS internally uses the WordArray
type, i.e. an array consisting of 4-byte words (s. here). This type is also returned by CryptoJS.MD5()
. Because of the MD5 output size of 16 bytes the WordArray
consists of 4 words (a 4 bytes).
Since the UUID algorithm modifies individual bytes, it is necessary to access and modify the bytes within the words. This is feasible e.g. by converting WordArray
to Uint8Array
and vice versa:
var hashWA = CryptoJS.MD5('12345');
// Conversion WordArray -> Uint8Array
var binString = hashWA.toString(CryptoJS.enc.Latin1);
var md5Bytes = Uint8Array.from(binString, x => x.charCodeAt(0))
// Byte manipulations
md5Bytes[6] &= 0x0f;
md5Bytes[6] |= 0x30;
md5Bytes[8] &= 0x3f;
md5Bytes[8] |= 0x80;
// Conversion Uint8Array -> WordArray
var uuidWA = CryptoJS.lib.WordArray.create(md5Bytes);
var uuidHex = uuidWA.toString();
var uuidFormatted = uuidHex.replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, "$1-$2-$3-$4-$5");
console.log(uuidFormatted)
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
More efficient is an MD5 implementation that returns the hash as an array of bytes (Array
, ArrayBuffer
, Uint8Array
) so that the bytes can be accessed directly, e.g. js-md5:
var md5Bytes = md5.array('12345');
md5Bytes[6] &= 0x0f;
md5Bytes[6] |= 0x30;
md5Bytes[8] &= 0x3f;
md5Bytes[8] |= 0x80;
var uuidHex = Array.prototype.map.call(new Uint8Array(md5Bytes), x => ('00' + x.toString(16)).slice(-2)).join('');
var uuidFormatted = uuidHex.replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, "$1-$2-$3-$4-$5");
console.log(uuidFormatted)
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.7.3/src/md5.min.js"></script>
However, I am not sure if with Postman js-md5 is an option or if there are similar libraries.
Comparison with Java: UUID.nameUUIDFromBytes("12345".getBytes(StandardCharsets.UTF_8)).toString()
returns the same output: 827ccb0e-ea8a-306c-8c34-a16891f84e7b
.
Note that the hex encoded MD5 hash for 12345
is 827ccb0e-ea8a-706c-4c34-a16891f84e7b
(with equivalent formatting). The differences due to the byte manipulations are found at indexes 6 and 8 (0x30 instead of 0x70 and 0x8c instead of 0x4c).