I am trying to implement the VISA DUKPT algorithm to create a unique key for each transaction from a KSN transaction. I followed the information provided by ANS X9.24-1: 2009 step by step, but the IPEC I receive does not match the information presented in this example. For encryption / decryption / encryption, I use the bouncy castle API. The key (BDK) shown in the examples is 0123456789ABCDEFFEDCBA9876543210 I understand that this is the double length of the encryption key, meaning that
- key1 (DES encryption) = 0123456789ABCDEF
- key2 (DES decryption) = FEDCBA9876543210
- key3 (DES encryption) = key1 = 0123456789ABCDEF
I know that when using DES you can only use an 8-byte key so that 16 hexadecimal characters are converted to an 8-byte array. (I have doubts if I do something here. I get this piece of code from the 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;
This is the only part where I doubt (how to convert a hexadecimal string to a DES key (ByteArray) in Java) at the moment. I use a bouncy castle to implement TripleDES.
The description of the IPEK calculation process is as follows:
The derivation of the initial key (IPEK) from the basic derivation key (BDK).
The initial PIN key (the key originally downloaded to the PIN input device) is generated by the following process:
- Copy the entire key serial number, including the 21-bit encryption counter, legitimately justified in a 10-byte register. If the key serial number is less than 10 bytes, press the left button with the hexadecimal byte βFFβ.
- Set the 21 least significant bit 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 with a double-length division key. Use the encrypted text created in 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 the XORed double-length derivation key with the hexadecimal C0C0 C0C0 0000 0000 C0C0 C0C0 0000 0000 as the key.
- Use the encrypted text created in step 5 as the right half of the initial key.
I followed the word previous explanation that I get for the left half of the key
67450505DF3A84FF
Expected value in accordance with the standard
6AC292FAA1315B4D
Provided by KSN - 9876543210E00000
After completing steps 1-3, before starting encryption / decryption / encryption, the text to be processed is as follows: FFFF9876543210E0
My implementation of TripleDES:
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; 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;
As you can see, I am trying to use ECB mode as standard, as well as CBC mode with IV 00000000, but neither of both approaches seems to work.
Please, I need some advice.