4

My company is working on a project that will put card readers in the field. The readers use DUKPT TripleDES encryption, so we will need to develop software that will decrypt the card data on our servers.

I have just started to scratch the surface on this one, but I find myself stuck on a seemingly simple problem... In trying to generate the IPEK (the first step to recreating the symmetric key).

The IPEK's a 16 byte hex value created by concatenating two triple DES encrypted 8 byte hex strings.

I have tried ECB and CBC (zeros for IV) modes with and without padding, but the result of each individual encoding is always 16 bytes or more (2 or more blocks) when I need a result that's the same size as the input. In fact, throughout this process, the cyphertexts should be the same size as the plaintexts being encoded.

<cfset x = encrypt("FFFF9876543210E0",binaryEncode(binaryDecode("0123456789ABCDEFFEDCBA98765432100123456789ABCDEF", "hex"), "base64") ,"DESEDE/CBC/PKCS5Padding","hex",BinaryDecode("0000000000000000","hex"))>

Result: 3C65DEC44CC216A686B2481BECE788D197F730A72D4A8CDD

If you use the NoPadding flag, the result is:

3C65DEC44CC216A686B2481BECE788D1

I have also tried encoding the plaintext hex message as base64 (as the key is). In the example above that returns a result of:

DE5BCC68EB1B2E14CEC35EB22AF04EFC.

If you do the same, except using the NoPadding flag, it errors with "Input length not multiple of 8 bytes."

I am new to cryptography, so hopefully I'm making some kind of very basic error here. Why are the ciphertexts generated by these block cipher algorithms not the same lengths as the plaintext messages?

For a little more background, as a "work through it" exercise, I have been trying to replicate the work laid out here:

https://www.parthenonsoftware.com/blog/how-to-decrypt-magnetic-stripe-scanner-data-with-dukpt/

GumbyG
  • 488
  • 4
  • 8
  • I'm curious to see some dialog on this. I have an interest in what you are doing here and Coldfusion is my language. If you figure out a good solution please edit/update your question and link to a blog post on what you did. – Frank Tudor Dec 09 '14 at 15:43
  • @FrankTudor I will be glad to provide an accounting of some of the technical hurdles if/when I overcome them. Of note, my first task in this journey was to create a simple Coldfusion function that would apply a hex mask to a hex value, bitwise. Given that's a basic need for this particular endeavor, I suspect CF may not be suited to this task, and the solution may have to be written in Java. – GumbyG Dec 09 '14 at 17:27
  • Maybe for that particular part....You can always invoke battle tested Java 'stuff' into Coldfusion. Coldfusion is good for most things, but I agree, when you hit something so specific, you may want to see if you `createObject()` to some java library/package thing that may handle this better, and register it with Coldfusion so you can make it part of your library and keep trucking along with your CF development. – Frank Tudor Dec 09 '14 at 17:37
  • BTW, kudos for an interesting (and well researched) question. – Leigh Dec 09 '14 at 20:26

2 Answers2

2

I'm not sure if it is related and it may not be the answer you are looking for, but I spent some time testing bug ID 3842326. When using different attributes CF is handling seed and salt differently under the hood. For example if you pass in a variable as the string to encrypt rather than a constant (hard coded string in the function call) the resultant string changes every time. That probably indicates different method signatures - in your example with one flag vs another flag you are seeing something similar.

Adobe's response is, given that the resulting string can be unecrypted in either case this is not really a bug - more of a behavior to note. Can your resultant string be unencrypted?

Mark A Kruger
  • 7,183
  • 20
  • 21
  • I know if you specify CBC mode and don't provide an IVor salt, CF will helpfully generate one for you at random, not tell you what it is, and employ it to encrypt your plaintext. I think that's what 'explains' this behavior. Why CF does that.. just another thing I've added to the increasingly long list of things I don't understand. – GumbyG Dec 09 '14 at 19:27
  • I don't have any randomness problems when I specify an IV, or use ECB mode. Decrypt() works just fine to return the ciphertexts to plaintext, as long as I apply the same conditions. – GumbyG Dec 09 '14 at 19:34
  • 1
    @GumbyG - *RE: CF will helpfully generate one for you* Yep. Though you can derive the random `iv` value from the encrypted text. *I think that's what 'explains' this behavior* Actually, Mark was referring to a slightly different issue/bug. When using CBC mode, with the exact same arguments, the results differ depending on whether you hard code the "key" value or pass it into the function as a variable. That should not happen. Definitely a bug IMO. However, that is the not the cause of your problem here. – Leigh Dec 09 '14 at 22:25
  • Ah - @Mark I see what you're getting at. I didn't run into that in my process, but I'm sure I would have eventually. Thanks for the heads up. That would be a real head scratcher for someone like me who is just figuring out how to use cryptography functions. Leigh - thanks for your clarification. – GumbyG Dec 10 '14 at 15:21
1

The problem is encrypt() expects the input to be a UTF-8 string. So you are actually encrypting the literal characters F-F-F-F-9.... rather than the value of that string when decoded as hexadecimal.

Instead, you need to decode the hex string into binary, then use the encryptBinary() function. (Note, I did not see an iv mentioned in the link, so my guess is they are using ECB mode, not CBC.) Since the function also returns binary, use binaryEncode to convert the result to a more friendly hex string.

Edit: Switching to ECB + "NoPadding" yields the desired result:

ksnInHex = "FFFF9876543210E0";
bdkInHex = "0123456789ABCDEFFEDCBA98765432100123456789ABCDEF";
ksnBytes = binaryDecode(ksnInHex, "hex");
bdkBase64 = binaryEncode(binaryDecode(bdkInHex, "hex"), "base64");
bytes = encryptBinary(ksnBytes, bdkBase64, "DESEDE/ECB/NoPadding");
leftRegister = binaryEncode(bytes, "hex");

... which produces:

6AC292FAA1315B4D 

In order to do this we want to start with our original 16 byte BDK ... and XOR it with the following mask ....

Unfortunately, most of the CF math functions are limited to 32 bit integers. So you probably cannot do that next step using native CF functions alone. One option is to use java's BigInteger class. Create a large integer from the hex strings and use the xor() method to apply the mask. Finally, use the toString(radix) method to return the result as a hex string:

bdkText ="0123456789ABCDEFFEDCBA9876543210";
maskText = "C0C0C0C000000000C0C0C0C000000000";

// use radix=16 to create integers from the hex strings
bdk = createObject("java", "java.math.BigInteger").init(bdkText, 16);
mask = createObject("java", "java.math.BigInteger").init(maskText, 16);
// apply the mask and convert the result to hex (upper case)
newKeyHex = ucase( bdk.xor(mask).toString(16) );
WriteOutput("<br>newKey="& newKeyHex);
writeOutput("<br>expected=C1E385A789ABCDEF3E1C7A5876543210");

That should be enough to get you back on track. Given some of CF's limitations here, java would be a better fit IMO. If you are comfortable with it, you could write a small java class and invoke that from CF instead.

Leigh
  • 28,765
  • 10
  • 55
  • 103
  • This is exactly it. I'm surprised about dropping the least significant digits, but it works, and the process seems to work in iteration, according to the example. @FrankTudor, I would give you my hexMask() function, but the process used by Leigh above is much more elegant. The Parthenon blog entry makes for pretty good pseudocode, except when it comes to iterating over the Future keys (10=A?). I have a working model, but I am not sure it matches the real world. I'll post back when I know more. – GumbyG Dec 10 '14 at 13:29