Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Hex-Encoding A Binary Value / Byte Array In ColdFusion

By Ben Nadel on
Tags: ColdFusion

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).



Reader Comments