Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with:

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