DUKPT Implementation process using Java and how to convert a hexadecimal string to a DES key (ByteArray)

6.4k Views Asked by At

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.

0

There are 0 best solutions below