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

common.crypto.FPE 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.Bits;
import com.unbound.common.Converter;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

// Format preserving encryption
public final class FPE
{
  static void aesFeistelEncrypt(AES aes, byte[] plain, byte[] encrypted, int bits)
  {
    assert((bits + 7) / 8 == plain.length);
    int half = bits/2;
    assert(half*2 == bits);
    assert(half<=64);
    byte[] left = new byte[8];
    byte[] right = new byte[8];

    Bits.copy(left, 0, plain, 0, half);
    Bits.copy(right, 0, plain, half, half);

    byte[] buf = new byte[16];

    int rounds = 36;
    if (bits>=10) rounds = 30;
    if (bits>=14) rounds = 24;
    if (bits>=20) rounds = 18;
    if (bits>=32) rounds = 12;

    for (int i=0; i=10) rounds = 30;
    if (bits>=14) rounds = 24;
    if (bits>=20) rounds = 18;
    if (bits>=32) rounds = 12;

    for (int i=rounds-1; i>=0; i--)
    {
      buf[0] = (byte)bits;
      buf[1] = (byte)i;
      for (int j=2; j<16; j++) buf[j] = 0;

      Bits.copy(buf, 16, right, 0, half);

      aes.encrypt(buf, 0, 16, buf, 0);
      for (int j=0; j<8; j++) left[j] ^= buf[j];
      byte[] p = left;
      left = right;
      right = p;
    }

    Bits.copy(plain, 0, right, 0, half);
    Bits.copy(plain, half, left, 0, half);
  }

  private static char[] digitsOnly(char[] in, boolean isFormat)
  {
    int len = in.length;
    char[] out = new char[len];
    int outLen = 0;

    for (int i=0; i='0' && c<='9') out[outLen++] = c;
      else
      {
        if (isFormat && c=='#' || c=='?') out[outLen++] = c;
      }
    }
    return Arrays.copyOfRange(out, 0, outLen);
  }

  private static char[] applyFormat(char[] in, char[] format)
  {
    int inIndex = 0;
    int inLen = in.length;
    int formatLen = format.length;
    char[] out = new char[formatLen];
    for (int i=0; i='0' && c<='9')
        {
          int d = c-'0';
          n_digits++;
          if (n_digits<=3) aaa = aaa*10 + d;
          else if (n_digits==4) b=d;
          else if (n_digits<=6) cc = cc*10 + d;
          else if (n_digits<=10) dddd = dddd*10 + d;
          else return -1;
        }
        else
        {
          if (c!=' ' && c!='-' && c!='.' && c!=':' && c!='/' && c!='(' && c!=')' && c!='+') return -1;
        }
      }

      if (n_digits!=10) return -1;

      if (aaa<200) return -1;
      aaa-=200;

      if (b<2) return -1;
      b-=2;

      if (cc==11) return -1;
      if (cc>11) cc--;

      return ((long)aaa)*8*99*10000 + ((long)b)*99*10000 + ((long)cc)*10000 + dddd;
    }

    private static char[] unrank(long in, char[] format)
    {
      int aaa = (int)(in / (8*99*10000));
      int b = (int)((in % (8*99*10000)) / (99*10000));
      int cc = (int)((in % (99*10000)) / 10000);
      int dddd = (int)(in % 10000);
      if (cc>=11) cc++;
      b+=2;
      aaa+=200;

      char[] f = format;
      if (f==null || f.length==0) f = defaultFormat;

      char[] out = new char[f.length];
      int outIndex = 0;

      int n_digit = 0;
      for (int i=0; i=900) return null;
      if (aaa>666) aaa--;
      aaa--;
      bb--;

      aaa *= 99;
      if (use_cccc)
      {
        cccc--;
        if (cccc==0) return null;
        aaa *= 9999;
        bb *= 9999;
      }

      r.value = aaa + bb + cccc;
      return r;
    }

    static char[] unrank(int in, char[] format)
    {
      if (9!=format.length) return null;

      boolean use_cccc = format[8]=='#';

      int aaa = use_cccc ? in / (99 * 9999) : in / 99;
      int bb = use_cccc ? (in % (99*9999)) / 9999 : in % 99;
      int cccc = in % 9999;
      bb++;
      cccc++;
      aaa++;
      if (aaa>=666) aaa++;

      char[] out = new char[9];

      int n_digit = 0;
      for (int i=0; i<9; i++)
      {
        char c = format[i];
        if (c=='#')
        {
          n_digit++;

          int d=0;
          switch (n_digit)
          {
            case 1: d = aaa / 100; break;
            case 2: d = (aaa % 100) / 10; break;
            case 3: d = aaa % 10; break;
            case 4: d = bb / 10; break;
            case 5: d = bb % 10; break;
            case 6: d = cccc / 1000; break;
            case 7: d = (cccc % 1000) / 100; break;
            case 8: d = (cccc % 100) / 10; break;
            case 9: d = cccc % 10; break;
            default: return null;
          }

          c = (char)('0'+d);
        }
        out[i] = c;
      }

      return out;
    }

    public static String encrypt(byte[] key, String plain, String format)
    {
      char[] in = plain.toCharArray();
      char[] input = digitsOnly(in, false);
      char[] f = format==null ? null : digitsOnly(format.toCharArray(), true);

      if (f==null || f.length==0)
      {
        f = new char[input.length];
        for (int i=0; i0) out = applyFormat(out, format.toCharArray());
      if (out==null) return null;
      return new String(out);
    }

    public static String decrypt(byte[] key, String enc, String format)
    {
      char[] in = enc.toCharArray();
      char[] input = digitsOnly(in, false);
      char[] f = format==null ? null : digitsOnly(format.toCharArray(), true);

      if (f==null || f.length==0)
      {
        f = new char[input.length];
        for (int i=0; i0) out = applyFormat(out, format.toCharArray());
      if (out==null) return null;
      return new String(out);
    }

  }

  public static final class CreditCard
  {
    static int lunhCheckSum(char[] s, int offset, int len)
    {
      if (len<0) len = s.length;
      boolean odd = (len & 1)!=0;
      int sum = 0;

      for (int i=0; i= 10) digit = (digit % 10) + 1;
        }
        sum += digit;
        odd = !odd;
      }

      sum %= 10;
      return sum==0 ? 0 : 10-sum;
    }

    private static final class Rank
    {
      long value;
      long mask;
      long max;
      int bits;
      char[] format;
    }

    private static Rank rank(char[] in, char[] format)
    {
      Rank r = new Rank();
      r.format = format.clone();
      int len =in.length;
      if (len<12 || len>19) return null;
      if (format.length!=len) return null;

      if (lunhCheckSum(in,0, len-1) != in[len-1]-'0') return null;
      int count = len-1;
      r.value = 0;
      count = 0;
      for (int i=0; i=12 && len<=19);
      if (len!=format.length) return null;

      char[] out = new char[len];
      for (int i=len-2; i>=0; i--)
      {
        if (format[i]=='#') { out[i] = (char)(in % 10 + '0'); in /= 10; }
        else out[i] = format[i];
      }

      out[len-1] = (char)(lunhCheckSum(out, 0, len-1) + '0');
      if (format[len-1]!='#' && format[len-1]!=out[len-1]) return null;
      return out;
    }

    public static String encrypt(byte[] key, String plain, String format)
    {
      char[] in = plain.toCharArray();
      char[] input = digitsOnly(in, false);
      char[] f = format==null ? null : digitsOnly(format.toCharArray(), true);

      if (f==null || f.length==0)
      {
        f = new char[input.length];
        for (int i=0; i0 && format.charAt(format.length()-1)!='#';

      long v = rank.value;
      byte[] v_buf = new byte[8];
      byte[] enc = new byte[8];
      AES aes = new AES(key);

      for (;;)
      {
        Converter.setLE8(v_buf, 0, v);
        aesFeistelEncrypt(aes, v_buf, enc, rank.bits);
        v = Converter.getLE8(enc, 0) & rank.mask;
        if (v >= rank.max) continue;

        if (checkLunh)
        {
          char[] temp = unrank(v, f, in.length);
          if (temp==null) continue;
        }

        break;
      }

      char[] out = unrank(v, f, in.length);
      if (out==null) return null;
      if (format!=null && format.length()>0) out = applyFormat(out, format.toCharArray());
      if (out==null) return null;
      return new String(out);
    }

    public static String decrypt(byte[] key, String enc, String format)
    {
      char[] in = enc.toCharArray();
      char[] input = digitsOnly(in, false);
      char[] f = format==null ? null : digitsOnly(format.toCharArray(), true);

      if (f==null || f.length==0)
      {
        f = new char[input.length];
        for (int i=0; i0 && format.charAt(format.length()-1)!='#';

      long v = rank.value;
      byte[] v_buf = new byte[8];
      byte[] dec = new byte[8];
      AES aes = new AES(key);
      for (;;)
      {
        Converter.setLE8(v_buf, 0, v);
        aesFeistelDecrypt(aes, v_buf, dec, rank.bits);
        v = Converter.getLE8(dec, 0) & rank.mask;
        if (v >= rank.max) continue;

        if (checkLunh)
        {
          char[] temp = unrank(v, f, in.length);
          if (temp==null) continue;
        }

        break;
      }

      char[] out = unrank(v, f, in.length);
      if (out==null) return null;
      if (format!=null && format.length()>0) out = applyFormat(out, format.toCharArray());
      if (out==null) return null;
      return new String(out);
    }
  }

  public static final class EMail
  {
    private static final BigInteger bn62 = new BigInteger("62");

    private static final String[] topDomains = {
      "ac", "ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "ax", "az", "ba", "bb",
      "bd", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cc",
      "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cw", "cx", "cy", "cz", "de", "dj", "dk",
      "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gb", "gd",
      "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
      "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
      "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
      "md", "me", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz",
      "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl",
      "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh",
      "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "su", "sv", "sx", "sy", "sz", "tc", "td", "tf", "tg", "th",
      "tj", "tk", "tl", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "us", "uy", "uz", "va", "vc",
      "ve", "vg", "vi", "vn", "vu", "wf", "ws", "ye", "yt", "za", "zm", "zw"};

    private static int encodeEmailPlainChar(char src)
    {
      if (src>='a' && src<='z') return src-'a';
      if (src>='A' && src<='Z') return src-'A';
      if (src>='0' && src<='9') return 26 + (src-'0');
      switch (src)
      {
        case '.': return 36;
        case '!': return 37;
        case '#': return 38;
        case '$': return 39;
        case '%': return 40;
        case '&': return 41;
        //case '\'': break;
        case '*': return 42;
        case '+': return 43;
        case '-': return 44;
        case '/': return 45;
        case '=': return 46;
        //case '?': break;
        //case '^': break;
        case '_': return 47;
        //case '`': break;
        case '{': return 48;
        case '|': return 49;
        case '}': return 50;
        case '~': return 51;
        //case ' ': break;
        //case '"': break;
        case '(': return 52;
        case ')': return 53;
        case ',': return 54;
        case ':': return 55;
        case ';': return 56;
        case '<': return 57;
        case '>': return 58;
        case '[': return 59;
        //case '\\': break;
        case ']': return 60;
        case '@': return 61;
      }
      return -1;
    }

    private static char decodeEmailPlainChar(int src)
    {
      if (src<26) return (char)('a'+src);
      if (src<36) return (char)('0'+src-26);
      switch (src)
      {
        case 36: return '.';
        case 37: return '!';
        case 38: return '#';
        case 39: return '$';
        case 40: return '%';
        case 41: return '&';
        case 42: return '*';
        case 43: return '+';
        case 44: return '-';
        case 45: return '/';
        case 46: return '=';
        case 47: return '_';
        case 48: return '{';
        case 49: return '|';
        case 50: return '}';
        case 51: return '~';
        case 52: return '(';
        case 53: return ')';
        case 54: return ',';
        case 55: return ':';
        case 56: return ';';
        case 57: return '<';
        case 58: return '>';
        case 59: return '[';
        case 60: return ']';
        case 61: return '@';
      }
      return (char)-1;
    }

    private static int encodeEmailEncryptedChar(char src)
    {
      if (src>='a' && src<='z') return src-'a';
      if (src>='A' && src<='Z') return src-'A'+26;
      if (src>='0' && src<='9') return 52 + (src-'0');
      return -1;
    }

    private static char decodeEmailEncryptedChar(byte src)
    {
      if (src<0) return (char)-1;
      if (src<26) return (char)('a'+src);
      if (src<52) return (char)('A'+src-26);
      if (src<62) return (char)('0'+src-52);
      return (char)-1;
    }

    private static byte[] sha256(byte[] in1, byte in2)
    {
      MessageDigest md = null;

      try { md = MessageDigest.getInstance("SHA-256"); }
      catch (NoSuchAlgorithmException e) { throw new RuntimeException("SHA-256 is not available"); }

      md.update(in1);
      md.update(in2);
      return md.digest();
    }

    public static String encrypt(byte[] key, String plain, int maxSize)
    {
      if (maxSize<=0) maxSize = 254;
      if (plain.length()+6>maxSize) return null;

      int nameSize = (int)plain.indexOf('@');
      if (nameSize<=0 || nameSize>=63) return null;
      int domainSize = (int)plain.length() - nameSize-1;
      if (domainSize<3) return null;

      int padLen = maxSize-6 - plain.length();

      byte[] plainAscii = plain.getBytes(StandardCharsets.US_ASCII);
      byte[] padding = new byte[padLen];
      for (int i=0; i32) n = 32;
        for (int j=0; j0) padding[padLen-1] = 61; // delimiter

      BigInteger bn = BigInteger.ZERO;

      for (int i=0; i64) padNameSize = 64;
      if (padNameSize>n-1) padNameSize = n-1;

      int offset = 0;
      if (temp[0]=='a')
      {
        char c = temp[1];
        if ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')) offset=1;
      }

      StringBuilder sb = new StringBuilder();
      sb.append(temp, offset, padNameSize-offset);
      sb.append('@');
      sb.append(temp, padNameSize, n-padNameSize);
      sb.append('.');

      sb.append(topDomains[e_sum % topDomains.length]);
      return sb.toString();
    }

    public static String decrypt(byte[] key, String enc)
    {
      int nameSize = enc.indexOf('@');
      if (nameSize<0 || nameSize>64) return null;

      int dotSize = enc.lastIndexOf('.');
      if (dotSize!=enc.length()-3) return null;

      BigInteger bn = BigInteger.ZERO;

      for (int i=0; i=maxSymbol) return -1;

        int code = symbol;
        for (int i=0; i=maxSymbol) return -1;
        return symbol;
      }

      private static void setBE2(byte[] pointer, int offset, short value) {
          pointer[offset + 0] = (byte) (value >> 8);
          pointer[offset + 1] = (byte) (value);
      }

      private static short byteToUShort(byte b) {
          return (short) (((short) b) & 0xff);
      }

      private static short getBE2(byte[] pointer, int offset) {
          return (short) ((byteToUShort(pointer[offset + 0]) << 8) | byteToUShort(pointer[offset + 1]));
      }

      int getSymbolLen(byte[] src, int index, int size)
      {
        if (size<2) return 0;
        int w = getBE2(src, index+0) & 0xffff;
        if (w<0xd800 || w>0xdbff) return 2;
        if (rangesCount==3) return 0;
        if (size<4) return 0;
        int w2 = getBE2(src, index+2);
        if (w2<0xdc00 || w2>0xdfff) return 0;
        return 4;
      }

      int getSymbol(byte[] src, int index, int size)
      {
        int w = getBE2(src, index+0) & 0xffff;
        if (w<0xd800 || w>0xdbff) return w;
        int w2 = getBE2(src, index+2) & 0xffff;
        return 0x10000 + ((w - 0xd800) << 10) + (w2 - 0xdc00);
      }

      int putSymbol(byte[] dst, int src)
      {
        if (rangesCount==3 && src>=0x10000) return 0;
        if (src < 0x10000)
        {
          setBE2(dst, 0, (short)src);
          return 2;
        }

        src-=0x10000;
        short w = (short)(0xd800 + (src >> 10));
        setBE2(dst, 0, w);
        w = (short)(0xdc00 + (src & 0x3ff));
        setBE2(dst, 2, w);
        return 4;
      }

      BigInteger stringToBn(String string)
      {
        byte[] src;
        try { src = string.getBytes("UTF-16BE"); }
        catch (Exception e) { return null; }
        BigInteger out = BigInteger.ZERO;
        int codesCount = maxCode - 1; // exclude zero
        int size = src.length;
        int index = 0;
        while (size>0)
        {
          int n = getSymbolLen(src, index, size);
          if (n<=0) return null;
          int symbol = getSymbol(src, index, size);
          int code = symbolToCode(symbol);
          if (code<=0) return null;

          code--; // exclude zero
          out = out.multiply(BigInteger.valueOf(codesCount));
          out = out.add(BigInteger.valueOf(code));
          index += n;
          size -= n;
        }
        return out;
      }

      String bnToString(BigInteger bn)
      {
        byte[] temp = new byte[16];
        byte[] bytes = new byte[0];
        int codesCount = maxCode - 1; // exclude zero

        while (!bn.equals(BigInteger.ZERO))
        {
          BigInteger[] d = bn.divideAndRemainder(BigInteger.valueOf(codesCount));
          int code = (int)(d[1].longValue()) + 1; // exclude zero
          bn = d[0];
          int symbol = codeToSymbol(code);
          if (symbol<0) return null;

          int n = putSymbol(temp, symbol);
          byte[] old = bytes;
          bytes = new byte[n + old.length];
          System.arraycopy(temp, 0, bytes, 0, n);
          System.arraycopy(old, 0, bytes, n, old.length);
        }
        try { return new String(bytes, "UTF-16BE"); }
        catch (Exception e) { return null; }
      }

    }

    private static final Encoding utf = new Encoding(0x110000, unicodeNonSymbols.length);
    private static final Encoding ucs = new Encoding(0x10000,  3);

    private static byte[] bnToBin(BigInteger bn)
    {
      byte[] out = bn.toByteArray();
      if (out.length>0 && out[0]==0) return Arrays.copyOfRange(out, 1, out.length);
      return out;
    }

    private static BigInteger binToBn(byte[] bytes)
    {
      if ((bytes[0] & 0x80)!=0)
      {
        byte[] temp = new byte[bytes.length+1];
        temp[0] = 0;
        System.arraycopy(bytes, 0, temp, 1, bytes.length);
        bytes = temp;
      }
      return new BigInteger(1, bytes);
    }

    public static String encrypt(byte[] key, String plain, boolean BMPOnly)
    {
      byte[] key1 = Arrays.copyOfRange(key, 0, 16);

      Encoding encoding = BMPOnly ? ucs : utf;
      BigInteger bn = encoding.stringToBn(plain);
      if (bn==null) return null;
      byte[] plainBytes = bnToBin(bn);
      int size = plainBytes.length;
      byte[] enc = null;
      AES aes = null;
      if (size<16)
      {
        enc = new byte[size];
        aes = new AES(key1);
      }

      for (;;)
      {
        if (size<16) aesFeistelEncrypt(aes, plainBytes, enc, size*8);
        else enc = AES.XTS.encrypt(key, plainBytes);
        if (enc[0]!=0) break;
        plainBytes = enc;
      }

      bn = binToBn(enc);
      return encoding.bnToString(bn);
    }

    public static String decrypt(byte[] key, String encrypted, boolean BMPOnly)
    {
      byte[] key1 = Arrays.copyOfRange(key, 0, 16);
      Encoding encoding = BMPOnly ? ucs : utf;
      BigInteger bn = encoding.stringToBn(encrypted);
      byte[] encryptedBytes = bnToBin(bn);
      int size = encryptedBytes.length;

      byte[] dec = null;
      AES aes = null;

      if (size<16)
      {
        dec = new byte[size];
        aes = new AES(key1);
      }

      for (;;)
      {
        if (size<16) aesFeistelDecrypt(aes, encryptedBytes, dec,size*8);
        else dec = AES.XTS.decrypt(key, encryptedBytes);
        if (dec[0]!=0) break;
        encryptedBytes = dec;
      }

      bn = binToBn(dec);
      return encoding.bnToString(bn);
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy