common.crypto.FPE 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.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);
}
}
}