Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Ashwini Achar
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Ashwini Achar

Hex-Encoding A Binary Value / Byte Array In ColdFusion

By
Published in

When you start dealing with 3rd-party APIs, hex-encoding a byte array becomes a surprisingly common task. This is because 3rd-party APIs often require you to "sign" the request using your secret Key to produce a Hashed Message Authentication Code (Hmac). EmailYak uses the MD5 hashing algorithm; Pusher uses the Sha-256 hashing algorithm; and Twilio uses the Sha-1 hashing algorithm. Each of these hashes is returned as a byte array that has to be Hex-encoded before it can be attached to the API request. I've used a number of Hex-encoding approaches; but yesterday, I (re-)discovered that ColdFusion provides a super simple, built-in method for Hex-encoding binary values: binaryEncode().

NOTE: ColdFusion 10 now provides a built-in hmac() function for creating Hashed Message Authentication Codes; but that is beyond the scope of this post.

ColdFusion has so many built-in functions that if you don't use them on a regular basis, you can quickly forget that they exist. Such is the case with binaryEncode(). I don't believe I've used in about 6 years. As it turns out, though, it makes hex-encoding a binary value a one-line task.

Having re-discovered this yesterday, I wanted to quickly perform a functional comparison of binaryEncode() to some of the other hex-encoding approaches that I have used in the past. In the following code, I'm using a manual approach, a BigInteger approach, and a binaryEncode() approach to hex-encode a single byte array. This way, I can make sure that they all produce the same output:

<cfscript>


	// I encode the binary value / byte array as a HEX value by
	// manually looping over the array, encoding each byte
	// individually.
	function encodeManually( Any bytes ){

		// Create a buffer to hold each encoded byte.
		var hexBuffer = [];

		// Get the length of the array so we don't have to keep
		// evaluating it for each loop iteration.
		var byteCount = arrayLen( bytes );

		// Use an index loop rather than a for-in loop because
		// ColdFusion seems to have some trouble with navigating a
		// non-stanard array.
		for (var i = 1 ; i <= byteCount ; i++){

			// Strip off any sign information - let's work with
			// only the last 8 bits of information.
			var unsignedByte = bitAnd( bytes[ i ], 255 );

			var hexValue = formatBaseN( unsignedByte, 16 );

			// Of any byte value less than or equal to 15, we only
			// need one-digit of HEX encoding; however, we want all
			// our encoding values to be 2-digits.
			if (unsignedByte <= 15){

				hexValue = ("0" & hexValue);

			}

			arrayAppend( hexBuffer, hexValue );

		}

		// Join the hex buffer and return the full hex-encoding.
		return(
			arrayToList( hexBuffer, "" )
		);

	}


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// I encode the binary value / byte array as a HEX value using
	// the BigInteger class.
	function encodeWithBigInt( Any bytes ){

		// Let's pretend that our byte array represents the bytes of
		// enormous integer value.
		var bigInt = createObject( "java", "java.math.BigInteger" ).init(
			javaCast( "int", 1 ),
			bytes
		);

		// Cast our enormous integer to a base16 string.
		var hexEncoding = bigInt.toString( javaCast( "int", 16 ) );

		// When converting to a HEX value, BigInteger will strip off
		// the leading zero. However, we want all of our HEX values to
		// be represented as a 2-digit hex. If the first byte is less
		// than or equal to 15, let's prepend the leading zero.
		var firstByte = bitAnd( bytes[ 1 ], 255 );

		if (firstByte <= 15){

			hexEncoding = ("0" & hexEncoding);

		}

		// Return the hex-encoding.
		return( hexEncoding );

	}


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// I encode the binary value / byte array as a HEX value using
	// the built-in binaryEncode() ColdFusion metho.
	function encodeWithBinaryEncode( Any bytes ){

		// Convert the binary to a hex-encoded string.
		var hexEncoding = binaryEncode( bytes, "hex" );

		// Lower-case the HEX value since none of the other methods
		// above use upper-case value (while ColdFusion does).
		return(
			lcase( hexEncoding )
		);

	}


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //
	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// I return a byte array. The byte array is built using values
	// at the low end of the ASCII table. This is done to ensure that
	// we run into a situation where our first HEX value is less than
	// 16. This will test the leading-zero situation.
	function getBytes(){

		// Let's built up an array of characters that will be
		// converted to binary.
		var charBuffer = [];

		// Build the array up using ASCII decimal values.
		for (var i = 9 ; i <= 32 ; i++){

			arrayAppend( charBuffer, chr( i ) );

		}

		// Collapse the character array.
		var textValue = arrayToList( charBuffer, "" );

		// Return the string as binary data.
		return(
			toBinary( toBase64( textValue ) )
		);

	}


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	bytes = getBytes();

	// Test the various encoding approaches.
	writeOutput( "Hex: " & encodeManually( bytes ) );
	writeOutput( "<br />" );
	writeOutput( "Hex: " & encodeWithBigInt( bytes ) );
	writeOutput( "<br />" );
	writeOutput( "Hex: " & encodeWithBinaryEncode( bytes ) );


</cfscript>

As you can see, each of these approaches is less complex than the one before it, with binaryEncode() being the only one-liner. When we run the three algorithms in sequence, we get the following output:

Hex: 090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
Hex: 090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
Hex: 090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20

In this case, I am using lcase() to convert the binaryEncode() value to lower-case. While case does not matter for HEX values, the first two approaches use lower-case characters. As such, I used lcase() simply to make them all look the same - it was easier to compare the outputs.

As you can see above, all three approaches produce the same Hex-encoding. And, obviously, ColdFusion's built-in binaryEncode() is by far the most straightforward approach. It's a shame that I forgot this function existed - it will definitely make dealing with 3rd party APIs much easier (when not using ColdFusion 10).

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel