credit-card.com.unbound.common.crypto.CreditCard Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unbound-java-provider Show documentation
Show all versions of unbound-java-provider Show documentation
This is a collection of JAVA libraries that implement Unbound cryptographic classes for JAVA provider, PKCS11 wrapper, cryptoki, and advapi
package com.unbound.common.crypto;
import com.unbound.common.HEX;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import java.util.Arrays;
public class CreditCard
{
public static class CVV
{
public static String calculate(String key, String authData)
{
return fromMac(calculateMac(key, authData));
}
public static String calculate(byte[] key, String authData)
{
return fromMac(calculateMac(key, authData));
}
public static byte[] calculateMac(String key, String authData)
{
return calculateMac(HEX.from(key), authData);
}
public static byte[] prepareAuthData(String authData)
{
if (authData.length() > 32)
throw new IllegalArgumentException("authData length is " + authData.length() + ", must not longer than 32");
byte[] authValue = new byte[16];
for (int i = 0; i < authData.length(); i++)
{
char c = authData.charAt(i);
if (c < '0' || c > '9') throw new IllegalArgumentException("authData should contain only decimal characters");
if ((i & 1) == 0) authValue[i / 2] = (byte) ((c - '0') << 4);
else authValue[i / 2] |= (byte) (c - '0');
}
return authValue;
}
private static byte[] des(byte[] key, byte[] plain, boolean encrypt)
{
Cipher cipher = SystemProvider.Cipher.getInstance("DES/ECB/NoPadding");
try
{
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, new SecretKeySpec(key, "DES"));
return cipher.update(plain);
}
catch (InvalidKeyException e) { throw new ProviderException(e); }
}
private static byte[] calculateMac(byte[] key, byte[] authValue)
{
if (key.length!=16) throw new IllegalArgumentException("key length is " + key.length + ", must be 16");
byte[] k1 = Arrays.copyOfRange(key, 0, 8);
byte[] k2 = Arrays.copyOfRange(key, 8, 16);
byte[] mac = des(k1, Arrays.copyOfRange(authValue, 0, 8), true);
for (int i=0; i<8; i++) mac[i] ^= authValue[8+i];
mac = des(k1, mac, true);
mac = des(k2, mac, false);
mac = des(k1, mac, true);
return mac;
}
public static byte[] calculateMac(byte[] key, String authData)
{
return calculateMac(key, prepareAuthData(authData));
}
public static String fromMac(byte[] mac)
{
int outLen = 0;
char[] out = new char[3];
for (int i = 0; outLen < 3 && i < 16; i++)
{
byte b = (byte) (((i & 1) == 0 ? (mac[i / 2] >> 4) : mac[i / 2]) & 0x0f);
if (b <= 9) out[outLen++] = (char) ('0' + b);
}
for (int i = 0; outLen < 3 && i < 16; i++)
{
byte b = (byte) (((i & 1) == 0 ? (mac[i / 2] >> 4) : mac[i / 2]) & 0x0f);
if (b >= 10) out[outLen++] = (char) ('0' + b - 10);
}
return new String(out);
}
private static void test(String keyHex, String auth, String cvv)
{
String s = calculate(keyHex, auth);
boolean ok = s.equals(cvv);
if (!ok) throw new AssertionError("CVV test mismatch");
}
public static void test()
{
test("9AF3B414C39599952BFC814D14AFBDF6", "60190112345678900001250000002003", "659");
test("0123456789ABCDEF0123456789ABCDEF", "40000000000000021601201", "481");
test("0123456789ABCDEF0123456789ABCDEF", "40000000000000021601999", "177");
test("0123456789ABCDEF0123456789ABCDEF", "40000000000000021601000", "779");
}
}
public static class PIN
{
static final int MODE_3624 = 0;
static final int MODE_NL = 1;
static final int MODE_GE = 2;
public static String calculateNatural(int mode, String key, String PAN, String decimalization)
{
return calculateNatural(mode, HEX.from(key), PAN, decimalization, 4);
}
public static String calculateNatural(int mode, byte[] key, String PAN, String decimalization)
{
return fromMac(mode, calculateMac(key, PAN), decimalization, 4);
}
public static String calculateNatural(int mode, String key, String PAN, String decimalization, int pinLen)
{
return calculateNatural(mode, HEX.from(key), PAN, decimalization, pinLen);
}
public static String calculateNatural(int mode, byte[] key, String PAN, String decimalization, int pinLen)
{
return fromMac(mode, calculateMac(key, PAN), decimalization, pinLen);
}
public static byte[] calculateMac(byte[] key, String PAN)
{
return calculateMac(key, preparePAN(PAN));
}
public static byte[] preparePAN(String PAN)
{
if (PAN.length() > 16)
throw new IllegalArgumentException("PAN length is " + PAN.length() + ", must not longer than 16");
byte[] out = new byte[8];
for (int i = 0; i < PAN.length(); i++)
{
char c = PAN.charAt(i);
if (c < '0' || c > '9') throw new IllegalArgumentException("PAN should contain only decimal characters");
if ((i & 1) == 0) out[i / 2] = (byte) ((c - '0') << 4);
else out[i / 2] |= (byte) (c - '0');
}
return out;
}
public static byte[] calculateMac(byte[] key, byte[] PAN)
{
Cipher cipher = SystemProvider.Cipher.getInstance("DESede/ECB/NoPadding");
try
{
if (key.length==16)
{
byte[] fullKey = new byte[24];
System.arraycopy(key, 0, fullKey, 0, 16);
System.arraycopy(key, 0, fullKey, 16, 8);
key = fullKey;
}
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DESede"));
return cipher.update(PAN);
}
catch (InvalidKeyException e) { throw new ProviderException(e); }
}
public static String fromMac(int mode, byte[] mac, String decimalization, int pinLen)
{
char[] out = new char[pinLen];
int startOffset = 0;
if (mode==MODE_NL || mode==MODE_GE) startOffset = 1;
for (int i=0; i> 4;
out[i] = decimalization.charAt(x & 0x0f);
}
if (mode==MODE_GE && out[0]=='0') out[0] = '1';
return new String(out);
}
private static void test(int mode, String keyHex, String PAN, String decimalization, String pin)
{
String s = calculateNatural(mode, keyHex, PAN, decimalization, pin.length());
boolean ok = s.equals(pin);
if (!ok) throw new AssertionError("PIN test mismatch");
}
private static void test(int mode, String macHex, String decimalization, String pin)
{
String s = fromMac(mode, HEX.from(macHex), decimalization, pin.length());
boolean ok = s.equals(pin);
if (!ok) throw new AssertionError("PIN test mismatch");
}
public static void test()
{
test(MODE_3624, "E5C1BD67B66AE7C6", "8351296477461538", "391365");
test(MODE_NL, "8325A637B66EA7A8", "0123456789012345", "2506");
test(MODE_GE, "E5A4FD67B66AE7C6", "0123456789012345", "1453");
test(MODE_3624, "11111111111111111111111111111111", "7824464731112340", "0123456789012345", "2582");
test(MODE_3624, "0525FE78567E53454525AE785674B045", "2546573457787501", "0123456789012345", "8314");
}
}
}