5

I am trying to implement the VISA DUKPT algorithm to generate a unique key per transaction from a transaction KSN. I have followed step by step the information provided by the ANS X9.24-1:2009 but the IPEK that I am getting is not the same as the one provided in the example. For encryption/Decryption/encryption I am using the bouncy castle API. The key (BDK) that is provided in the examples is 0123456789ABCDEFFEDCBA9876543210 I understand that this is a double encryption key length meaning that

  • key1 (encryption DES) = 0123456789ABCDEF
  • key2 (decryption DES) = FEDCBA9876543210
  • key3 (encryption DES) = key1 = 0123456789ABCDEF

I know that while using DES you only can use a 8 byte key so that 16 hexadecimal character string should be converted to a 8 byte array. (I have my doubts if I am doing something right here. I get this portion of code from a tutorial)

public byte[] hexStringToByteArray(String hexstring) {
        int i = 0;
        if (hexstring == null || hexstring.length() <= 0) {
            return null;
        }
        String stringvector = "0123456789ABCDEF";
        byte[] bytevector = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                             0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
        byte[] out = new byte[hexstring.length() / 2];
        while (i < hexstring.length() - 1) {
            byte ch = 0x00;
            //Convert high nibble charater to a hex byte
            ch = (byte) (ch | bytevector[stringvector.indexOf(hexstring.charAt(i))]);
            ch = (byte) (ch << 4); //move this to the high bit

            //Convert the low nibble to a hexbyte
            ch = (byte) (ch | bytevector[stringvector.indexOf(hexstring.charAt(i + 1))]); //next hex value
            out[i / 2] = ch;
            i++;
            i++;
        }
        return out;
    } 

This is the only part where I have doubts (How to convert a Hexadecimal String to a DES key (ByteArray) in Java) at this moment. For the implementation of TripleDES I am using bouncy castle.

The description of the process to calculate the IPEK is as follow:

Derivation of Initial Key (IPEK) from Base Derivation Key (BDK).

The initial PIN Entry Device key (the key initially loaded into the PIN Entry Device) is generated by the following process:

  • Copy the entire key serial number, including the 21-bit encryption counter, right- justified into a 10-byte register. If the key serial number is less than 10 bytes, pad to the left with hex “FF” bytes.
  • Set the 21 least-significant bits of this 10-byte register to zero.
  • Take the eight most-significant bytes of this 10-byte register, and encrypt/decrypt/encrypt these eight bytes using the double-length derivation key. Use the ciphertext produced by Step 3 as the left half of the Initial Key.
  • Take the 8 most-significant bytes from the 10-byte register of Step 2 and encrypt/decrypt/encrypt these 8 bytes using as the key the double-length derivation key XORed with hexadecimal C0C0 C0C0 0000 0000 C0C0 C0C0 0000 0000.
  • Use the ciphertext produced by Step 5 as the right half of the Initial Key.

I have followed the word by word the previous explanation by I am getting for the left half of the key

67450505DF3A84FF

The expected value according to the standard is

6AC292FAA1315B4D

The KSN provided is 9876543210E00000

After follow steps 1-3 before start encryption/decryption/encryption the text that is going to be processed is: FFFF9876543210E0

My TripleDES Implementation is:

import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

/**
 *
 * @author aealvarenga
 */
public class TripleDesCipherFromDES {

    public byte[] desEncryptionECBCipher(String key, String text) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
        Security.addProvider(new BouncyCastleProvider());        
        SecretKey keySpec = new SecretKeySpec(this.hexStringToByteArray(key), "DES");
        final Cipher encrypter = Cipher.getInstance("DES/ECB/ZeroBytePadding", "BC");
        encrypter.init(Cipher.ENCRYPT_MODE, keySpec);
        final byte[] plainTextBytes = text.getBytes("utf-8");
        final byte[] cipherText = encrypter.doFinal(plainTextBytes);
        return cipherText;
    }

    public String desDecriptionECBCipher(String key, byte[] cipherText) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, UnsupportedEncodingException, BadPaddingException {
        Security.addProvider(new BouncyCastleProvider());
        SecretKey keySpec = new SecretKeySpec(this.hexStringToByteArray(key), "DES");
        final Cipher decrypter = Cipher.getInstance("DES/ECB/ZeroBytePadding", "BC");        
        decrypter.init(Cipher.DECRYPT_MODE, keySpec);
        final byte[] plainText = decrypter.doFinal(cipherText);
        return new String(plainText, "UTF-8");
    }

    public byte[] desEncryptionCBCCipher(String key, String text) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        Security.addProvider(new BouncyCastleProvider());
        byte[] iv = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        IvParameterSpec ivSpec = new IvParameterSpec(iv);


        SecretKey keySpec = new SecretKeySpec(this.hexStringToByteArray(key), "DES");
        final Cipher encrypter = Cipher.getInstance("DES/CBC/ZeroBytePadding", "BC");
        encrypter.init(Cipher.ENCRYPT_MODE, keySpec,ivSpec);
        final byte[] plainTextBytes = text.getBytes("utf-8");
        final byte[] cipherText = encrypter.doFinal(plainTextBytes);
        return cipherText;
    }    

    public String desDecriptionCBCCipher(String key, byte[] cipherText) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, UnsupportedEncodingException, BadPaddingException, InvalidAlgorithmParameterException {
        Security.addProvider(new BouncyCastleProvider());
        byte[] iv = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        IvParameterSpec ivSpec = new IvParameterSpec(iv);        
        SecretKey keySpec = new SecretKeySpec(this.hexStringToByteArray(key), "DES");
        final Cipher decrypter = Cipher.getInstance("DES/CBC/ZeroBytePadding", "BC");        
        decrypter.init(Cipher.DECRYPT_MODE, keySpec,ivSpec);
        final byte[] plainText = decrypter.doFinal(cipherText);
        return new String(plainText, "UTF-8");
    }

    public String asciiToHex(String ascii) {
        StringBuilder hex = new StringBuilder();
        for (int i = 0; i < ascii.length(); i++) {
            hex.append(Integer.toHexString(ascii.charAt(i)));
        }
        return hex.toString();
    }

    public byte[] hexStringToByteArray(String hexstring) {
        int i = 0;
        if (hexstring == null || hexstring.length() <= 0) {
            return null;
        }
        String stringvector = "0123456789ABCDEF";
        byte[] bytevector = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
        byte[] out = new byte[hexstring.length() / 2];
        while (i < hexstring.length() - 1) {
            byte ch = 0x00;
            //Convert high nibble charater to a hex byte
            ch = (byte) (ch | bytevector[stringvector.indexOf(hexstring.charAt(i))]);
            ch = (byte) (ch << 4); //move this to the high bit

            //Convert the low nibble to a hexbyte
            ch = (byte) (ch | bytevector[stringvector.indexOf(hexstring.charAt(i + 1))]); //next hex value
            out[i / 2] = ch;
            i++;
            i++;
        }
        return out;
    }    

    public String tdesedeECBCipher(String text, String doubleLenghtKey) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
        //key definition
        String key1 = doubleLenghtKey.substring(0, 16);
        String key2 = doubleLenghtKey.substring(16, 32);
        String key3 = key1;

        byte[] codedText = new TripleDesCipherFromDES().desEncryptionECBCipher(key1, text);
        String decodedText = new TripleDesCipherFromDES().desDecriptionECBCipher(key2, codedText);
        byte[] codedTextFinal = new TripleDesCipherFromDES().desEncryptionECBCipher(key3, decodedText);

      return new String(Hex.encode(codedTextFinal));
    }

    public String tdesedeCBCCipher(String text, String doubleLenghtKey) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        //key definition
        String key1 = doubleLenghtKey.substring(0, 16);
        String key2 = doubleLenghtKey.substring(16, 32);
        String key3 = key1;

        byte[] codedText = new TripleDesCipherFromDES().desEncryptionCBCCipher(key1, text);
        String decodedText = new TripleDesCipherFromDES().desDecriptionCBCCipher(key2, codedText);
        byte[] codedTextFinal = new TripleDesCipherFromDES().desEncryptionCBCCipher(key3, decodedText);

       return new String(Hex.encode(codedTextFinal));
    }    

    public static void main(String[] args) throws Exception {
        String text = "FFFF9876543210E0";        
        String key =  "0123456789ABCDEFFEDCBA9876543210";

        System.out.println(new TripleDesCipherFromDES().tdesedeECBCipher(text,key));
        System.out.println(new TripleDesCipherFromDES().tdesedeCBCCipher(text,key));
    }
}

AS you can see I try using ECB mode as the standard recommend and also CBC mode with a IV of 00000000 but neither of both approaches seems to work.

Please I need an advice.

sarnold
  • 102,305
  • 22
  • 181
  • 238
aeal
  • 59
  • 1
  • 2
  • Welcome new user. Your hexStringToByteArray() seems OK. Don't you have any worked examples to debug through? This is tricky stuff, one wrong bit and the answer will be useless random trap. – Maarten Bodewes Jun 04 '12 at 20:41
  • WTF, where is that VISA DUKPT implementation? I only see some (pretty badly programmed) helper functions. What gives? Ammend or I'll flag it. – Maarten Bodewes Jun 04 '12 at 20:44
  • I appreciate your comment but I didnt included a whole impl as I go step by step. I described ANS9.24 and included an implementation of Triple Des using Bouncy Castle. In the main function I have two string variables text and key. The text string is the result of going the first 3 steps suggested by the standard I have mention that in the description. The key string variable contain the double length key I also have explain that. What I am asking for is why I have follow the standard the result doesnt match. If you read the description I never say that this is complete and I have mention it – aeal Jun 04 '12 at 21:54
  • OK, but the way it is currently worded, nobody is going to be able to help you. It's not clear which steps you are performing, and there is little to no way we can compare the methods. Furthermore, you seem to lack understanding of crypto in general like ECB and CBC, as for a single block and IV = all zero, CBC == ECB. That's not a bad thing - you can learn, but it makes it hard to go get into the question. – Maarten Bodewes Jun 04 '12 at 23:51

0 Answers0