DUKPT Explained with examples


Derived Unique Key Per Transaction (DUKPT) process that’s described in Annex A of ANS X9.24-2004.

It’s generally considered to be complex, but I’ve simplified it slightly with the help of online resources.

Key Management

Here’s a basic outline of the technique:

  1. You’re given a Base Derivation Key (BDK), which you assign to a swiper (note that the same BDK can be assigned to multiple swipers).
  2. You’ll use the BDK along with the device’s own unique Key Serial Number (KSN) to generate an Initial PIN Encryption Key (IPEK) for the device.
  3. You’ll assign this IPEK to a swiper, which uses it to irreversibly generate a list of future keys, which it’ll use to encrypt its messages.
  4. The swiper’s KSN is used along with one of its future keys to encrypt a message, and after each swipe it’ll increment the value of its KSN.
  5. Whenever a swiper takes a card it formats the card’s information into a series of tracks, each track having a particular set of information (e.g. card number, holder’s name, expiration date).
  6. The swiper usually encrypts these tracks using one of its generated future keys (called the “Session Key”) along with its current KSN. It’ll then increment the value of its KSN and discard the future key it used.
  7. At this point you’ll probably have an encrypted track along with the KSN the swiper used to encrypt it.
  8. It’s your responsibility to determine what BDK was used to initialize this device, and from there you’ll use the BDK and KSN to rederive the IPEK, which is used to rederive the Session Key, which is finally used to decrypt the message.

There’s a lot of technical information to be said about key management, but this isn’t the place for that. In some cases your provider/manufacturer (e.g. MagTek) will supply you with swipers that need to be initialized with an IPEK, and your supplier will usually have a manual that walks you through that process. If you’re doing encryption/decryption through a third party who also supplies swipers, they may have already loaded the devices with that information; what’s more is they may not even given you the BDK that belongs to your device in order to reduce the risk of security threats.


Note: Key management is beyond the scope of this explanation. Whatever you do with your keys, just make sure it’s secure.


One methodology I’ve seen that’ll allow you to associate a particular KSN to a BDK is to take the current KSN you’ve been given, mask it to retrieve the Initial Key Serial Number (IKSN), and look up the BDK in a table that maps IKSNs to BDKs:

Example:

ksn = FFFF9876543210E00008
iksn = ksn & FFFFFFFFFFFFFFE00000 // FFFF9876543210E00000

You’d then have a table that looks like:

IKSN BDK
0xFFFF9876543210E00000 0123456789ABCDEFFEDCBA9876543210

From which you could easily grab the BDK 0123456789ABCDEFFEDCBA9876543210.

Algorithm


Note: Assume that all numeric values are hexadecimal numbers, or the representation of a sequence of bytes as a hexadecimal number.


The following are the BDK, KSN, and encrypted track message (cryptogram) we’ve been given:

bdk = 0123456789ABCDEFFEDCBA9876543210
ksn = FFFF9876543210E00008
cryptogram = C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12

Here’s an example of the unencrypted track 1 data (cryptogram above), and below that is its value in hex; this is what we’ll get after successfully decrypting the cryptogram:

%B5452300551227189^HOGAN/PAUL      ^08043210000000725000000?
2542353435323330303535313232373138395E484F47414E2F5041554C2020202020205E30383034333231303030303030303732353030303030303F00000000

Note: As you’re probably already aware, this algorithm is best described using big numbers, which can’t be represented as literals in some programming languages (like Java or C#). However, many languages have classes that allow you to represent big numbers in other ways (e.g., java.math.BigInteger, System.Numerics.BigInteger). It’s your job to adapt this algorithm so that it can be represented in your language of choice. Two small problems I encountered were ensuring the correct endianness and signedness were being used (this algorithm requires the byte order to be big endian and that unsigned integers are used). I made a utility class called BigInt to do this for me.


First, let’s define a few standard functions:

  • DES and Triple DES refer to their respective cryptographic algorithms. Most programming languages have access to some implementation of these ciphers either through OpenSSL or Bouncy Castle. These ciphers are initialized with a zeroed out IV of 8 bytes, they’re zero-padded, and use Cipher-Block Chaining (CBC). Let’s define the signatures for these standard functions that’ll be used throughout this algorithm:
    • DesEncrypt(key, message) -> returns cryptogram
    • DesDecrypt(key, cryptogram) -> returns message
    • TripleDesEncrypt(key, message) -> returns cryptogram
    • TripleDesDecrypt(key, cryptogram) -> returns message

First we must create the IPEK given then KSN and BDK:

CreateIpek(ksn, bdk) {
    return TripleDesEncrypt(bdk, (ksn & KsnMask) >> 16) << 64 
         | TripleDesEncrypt(bdk ^ KeyMask, (ksn & KsnMask) >> 16)
}

Now we can get the IPEK:

ipek = CreateIpek(ksn, bdk)
     = CreateIpek(FFFF9876543210E00008, 0123456789ABCDEFFEDCBA9876543210)
     = 6AC292FAA1315B4D858AB3A3D7D5933A

After that we need a way to get the Session Key (this one is more complicated):

CreateSessionKey(ipek, ksn) {
    return DeriveKey(ipek, ksn) ^ FF00000000000000FF
}

The DeriveKey method finds the IKSN and generates session keys until it gets to the one that corresponds to the current KSN. We define this method as:

DeriveKey(ipek, ksn) {
    ksnReg = ksn & FFFFFFFFFFE00000
    curKey = ipek
    for (shiftReg = 0x100000; shiftReg > 0; shiftReg >>= 1)
        if ((shiftReg & ksn & 1FFFFF) > 0)
            curKey = GenerateKey(curKey, ksnReg |= shiftReg)
    return curKey
}

Where the GenerateKey method looks like:

GenerateKey(key, ksn) {
    return EncryptRegister(key ^ KeyMask, ksn) << 64 
         | EncryptRegister(key, ksn)
}

And EncryptRegister looks like:

EncryptRegister(key, reg) {
    return (key & FFFFFFFFFFFFFFFF) ^ DesEncrypt((key & FFFFFFFFFFFFFFFF0000000000000000) >> 64, 
                                                  key & FFFFFFFFFFFFFFFF ^ reg)
}

Then you can generate the Session Key given the IPEK and KSN:

key = CreateSessionKey(ipek, ksn)
    = CreateSessionKey(6AC292FAA1315B4D858AB3A3D7D5933A, FFFF9876543210E00008)
    = 27F66D5244FF621EAA6F6120EDEB427F

Which can be used to decrypt the cryptogram:

message = TripleDesDecrypt(key, cryptogram)
        = TripleDesDecrypt(27F66D5244FF621EAA6F6120EDEB427F, C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12)
        = 2542353435323330303535313232373138395E484F47414E2F5041554C2020202020205E30383034333231303030303030303732353030303030303F00000000
        = %B5452300551227189^HOGAN/PAUL      ^08043210000000725000000?

That’s it, you’re done!

18 Comments

  1. Niall says:

    Arthur, first off: incredible article, thank you.

    Two questions:
    1. You supply a single key to the TripleDesEncrypt – can you provide any insight into how the 3 (or 2?) keys for the three-part DES encryption are generated? do i need to care?
    2. What’s the value of the KeyMask you use to xor against the BDK during one of the TripleDesEncrypt steps?

    1. Hi Niall, here is the C# example for the article. Mask is hardcoded and TDES function is used from the Microsoft crypto library.

      using System;
      using System.Linq;
      using System.Numerics;
      using System.Security.Cryptography;

      public static class Dukpt
      {
      private static readonly BigInteger Reg3Mask = BigInt.FromHex("1FFFFF");
      private static readonly BigInteger ShiftRegMask = BigInt.FromHex("100000");
      private static readonly BigInteger Reg8Mask = BigInt.FromHex("FFFFFFFFFFE00000");
      private static readonly BigInteger Ls16Mask = BigInt.FromHex("FFFFFFFFFFFFFFFF");
      private static readonly BigInteger Ms16Mask = BigInt.FromHex("FFFFFFFFFFFFFFFF0000000000000000");
      private static readonly BigInteger KeyMask = BigInt.FromHex("C0C0C0C000000000C0C0C0C000000000");
      private static readonly BigInteger PekMask = BigInt.FromHex("FF00000000000000FF");
      private static readonly BigInteger KsnMask = BigInt.FromHex("FFFFFFFFFFFFFFE00000");
      private static readonly BigInteger DekMask = BigInt.FromHex("0000000000FF00000000000000FF0000");

      public static BigInteger CreateBdk(BigInteger key1, BigInteger key2)
      {
      return key1 ^ key2;
      }

      public static BigInteger CreateIpek(BigInteger ksn, BigInteger bdk)
      {
      return Transform("TripleDES", true, bdk, (ksn & KsnMask) >> 16) <> 16);
      }

      public static BigInteger CreateSessionKey(BigInteger ipek, BigInteger ksn)
      {
      return DeriveKey(ipek, ksn) ^ PekMask;
      }

      // Added in the handling for decrypting IDTech tracks
      public static BigInteger CreateSessionKeyIdTech(BigInteger ipek, BigInteger ksn) {
      var key = DeriveKey(ipek, ksn) ^ DekMask;
      return Transform("TripleDES", true, key, (key & Ms16Mask) >> 64) < 0; shiftReg >>= 1)
      if ((shiftReg & ksn & Reg3Mask) > 0)
      curKey = GenerateKey(curKey, ksnReg |= shiftReg);
      return curKey;
      }

      public static BigInteger GenerateKey(BigInteger key, BigInteger ksn)
      {
      return EncryptRegister(key ^ KeyMask, ksn) <> 64, (curKey & Ls16Mask ^ reg8));
      }

      public static BigInteger Transform(string name, bool encrypt, BigInteger key, BigInteger message)
      {
      using (var cipher = SymmetricAlgorithm.Create(name))
      {
      var k = key.GetBytes();

      // gets the next multiple of 8
      cipher.Key = new byte[Math.Max(0, GetNearestWholeMultiple(k.Length, 8) - k.Length)].Concat(key.GetBytes()).ToArray();
      cipher.IV = new byte[8];
      cipher.Mode = CipherMode.CBC;
      cipher.Padding = PaddingMode.Zeros;
      using (var crypto = encrypt ? cipher.CreateEncryptor() : cipher.CreateDecryptor())
      {
      var data = message.GetBytes();
      // Added the GetNearestWholeMultiple here.
      data = new byte[Math.Max(0, GetNearestWholeMultiple(data.Length, 8) - data.Length)].Concat(message.GetBytes()).ToArray();
      return BigInt.FromBytes(crypto.TransformFinalBlock(data, 0, data.Length));
      }
      }
      }

      // Gets the next multiple of 8
      // Works with both scenarios, getting 7 bytes instead of 8 and works when expecting 16 bytes and getting 15.
      private static int GetNearestWholeMultiple(decimal input, int multiple)
      {
      var output = Math.Round(input / multiple);
      if (output == 0 && input > 0) output += 1;
      output *= multiple;
      return (int)output;
      }

      public static byte[] Encrypt(string bdk, string ksn, byte[] track)
      {
      return Transform("TripleDES", true, CreateSessionKey(CreateIpek(
      BigInt.FromHex(ksn), BigInt.FromHex(bdk)), BigInt.FromHex(ksn)), BigInt.FromBytes(track)).GetBytes();
      }

      public static byte[] Decrypt(string bdk, string ksn, byte[] track)
      {
      return Transform("TripleDES", false, CreateSessionKey(CreateIpek(
      BigInt.FromHex(ksn), BigInt.FromHex(bdk)), BigInt.FromHex(ksn)), BigInt.FromBytes(track)).GetBytes();
      }

      public static byte[] DecryptIdTech(string bdk, string ksn, byte[] track)
      {
      return Transform("TripleDES", false, CreateSessionKeyIdTech(CreateIpek(
      BigInt.FromHex(ksn), BigInt.FromHex(bdk)), BigInt.FromHex(ksn)), BigInt.FromBytes(track)).GetBytes();
      }
      }
      public static class BigInt
      {

      public static BigInteger FromHex(string hex)
      {
      return BigInteger.Parse("00" + hex, System.Globalization.NumberStyles.HexNumber);
      }

      public static BigInteger FromBytes(byte[] bytes)
      {
      return new BigInteger(bytes.Reverse().Concat(new byte[] { 0 }).ToArray());
      }

      public static byte[] GetBytes(this BigInteger number)
      {
      return number.ToByteArray().Reverse().SkipWhile(b => b == 0).ToArray();
      }
      }
      }

  2. example of the usage of the code:
    var test = “%B5452300551227189^HOGAN/PAUL ^08043210000000725000000?\0\0\0\0”;

    // Decrypting
    var bdk = “0123456789ABCDEFFEDCBA9876543210”;
    var ksn = “FFFF9876543210E00008”;
    var track = “C25C1D1197D31CAA87285D59A892047426D9182EC11353C051ADD6D0F072A6CB3436560B3071FC1FD11D9F7E74886742D9BEE0CFD1EA1064C213BB55278B2F12”;
    var decBytes = Dukpt.Decrypt(bdk, ksn, BigInt.FromHex(track).GetBytes());
    var decrypted = Encoding.UTF8.GetString(decBytes);
    Console.WriteLine(decrypted == test); // True

    // Encrypting
    var encBytes = Dukpt.Encrypt(bdk, ksn, decBytes);
    var encrypted = BitConverter.ToString(encBytes).Replace(“-“, “”);
    Console.WriteLine(encrypted == track); // True

    1. Niall says:

      You complete legend, thank you very much indeed!
      epic kupos to you – this gear is a total head melt but you’ve explained it incredibly well.

  3. Pankaj Patil's Blog says:

    Can you please provide the Java Implementation of the Same.

    1. Yash Jain says:

      Did you get the Java implementation of the same then please share the same.

    2. yashcomplex says:

      In case if you have got the java implementation of the same then please share with me @email yashcomplex@gmail.com

      1. Sorry, You would need to make your own Java implementation.

    3. Ashish Kumar says:

      brother if u got the implementation of java code of the same .then plz share me.
      Thanks in advance.

  4. Great Article.
    Can you please help regarding DUKPT PINBLOCK Decryption.

    1. Hi Krishna, cannot at the moment unfortunately have to much work.

      1. Anonymous says:

        Thanks anyway. I have successfully decrypted DUKPT PIN.

      2. Krishna Telgave says:

        Thanks anyway. i have successfully decrypted DUKPT Pinblock.

  5. Can you please help regarding DUKPT PINBLOCK Decryption.

  6. Do An says:

    i just got an pay shield 9000 could u show me how i save a BDK to my HSM? Thanks you.

    1. You will need to generate a BDK (A0 Command), and export it under the LMK. You do not save it to the HSM.

  7. Harold P says:

    Hi Arthur,
    very useful post, thanks. I have a doubt, my boss want to Obtain BDK from a IPEK and KSN, Is it Possible ? Do you have any reference about this?

    thanks a lot bro.

    1. This is not possible. The BDK encrypts the KSN; the ciphertext is the IPEK. i.e. $E_{BDK}(KSN) = IPEK$. Retrieving the BDK is equivalent to breaking the encryption scheme and deriving the key.

Leave a Comment