All Downloads are FREE. Search and download functionalities are using the official Maven repository.

credit-card.com.unbound.common.crypto.CreditCard Maven / Gradle / Ivy

Go to download

This is a collection of JAVA libraries that implement Unbound cryptographic classes for JAVA provider, PKCS11 wrapper, cryptoki, and advapi

There is a newer version: 42761
Show newest version
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");
    }

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy