I'm working in a Electronic Funds Transfer (EFT) system and need be very careful with sensitive data like credit card numbers.
We need this information do mount the ISO 8583 data before sending to EFT Gateways.
We use char[] and byte[] to keep sensitive data and use Array.fill when We don't need this information anymore.
Thinking in improve security a thought in create a class to wrap this sensitive information and maybe keep data encrypted until needed.
Here's the code I plan to use with a random generated key.
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.security.auth.Destroyable;
public final class SensitiveChars implements CharSequence, Destroyable {
private static final int KEYSIZE = 56;
private static final String DES = "DES";
private static final String DES_ECB_PKCS5_PADDING = DES + "/ECB/PKCS5Padding";
private byte[] data;
private Charset charset;
private int lenght;
private SecretKey secretKey;
public SensitiveChars(char[] data) {
super();
this.charset = Charset.defaultCharset();
ByteBuffer byteBuffer = charset.encode(CharBuffer.wrap(data));
lenght = data.length;
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(DES);
SecureRandom secureRandom = new SecureRandom();
int keyBitSize = KEYSIZE;
keyGenerator.init(keyBitSize, secureRandom);
secretKey = keyGenerator.generateKey();
setRawData(byteBuffer.array());
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
public static SensitiveChars of(byte[] bytes) {
return of(bytes, Charset.defaultCharset());
}
public static SensitiveChars of(byte[] bytes, Charset charset) {
return of(bytes, charset, false);
}
public static SensitiveChars of(byte[] bytes, Charset charset, boolean cleanBytes) {
char[] chars = toChar(bytes, charset, cleanBytes);
return new SensitiveChars(chars);
}
@Override
public int length() {
return lenght;
}
@Override
public char charAt(int index) {
char[] tempArray = getChars();
char c = tempArray[index];
clearData(tempArray);
return c;
}
@Override
public CharSequence subSequence(int start, int end) {
char[] dataTmp = getChars();
char[] range = Arrays.copyOfRange(dataTmp, start, end);
clearData(dataTmp);
return new SensitiveChars(range);
}
public char[] getChars() {
if (lenght == 0) {
return new char[0];
} else {
byte[] rawData = getRawData();
ByteBuffer byteBuffer = ByteBuffer.wrap(rawData);
CharBuffer charBuffer = charset.decode(byteBuffer);
char[] result = Arrays.copyOf(charBuffer.array(), lenght);
clearData(charBuffer.array());
clearData(rawData);
return result;
}
}
public void append(char[] chars) {
char[] dataTmp = getChars();
int lengthData = dataTmp.length;
char[] newData = Arrays.copyOf(dataTmp, lengthData + chars.length);
clearData(data);
int posIni = lengthData;
for (int i = 0; i < chars.length; i++) {
newData[i + posIni] = chars[i];
}
CharBuffer charBuffer = CharBuffer.wrap(newData);
ByteBuffer byteBuffer = charset.encode(charBuffer);
byteBuffer.compact();
setRawData(byteBuffer.array());
lenght = newData.length;
clearData(dataTmp);
clearData(newData);
}
private void setRawData(byte[] data) {
this.data = encrypt(data);
}
private byte[] getRawData() {
return decrypt(data);
}
@Override
public void destroy() {
clearData(data);
data = new byte[0];
lenght = 0;
}
private byte[] encrypt(byte[] bytes) {
try {
Cipher cipher = Cipher.getInstance(DES_ECB_PKCS5_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(bytes);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
| BadPaddingException e) {
throw new IllegalStateException(e);
}
}
private byte[] decrypt(byte[] bytes) {
try {
Cipher cipher = Cipher.getInstance(DES_ECB_PKCS5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(bytes);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
| BadPaddingException e) {
throw new IllegalStateException(e);
}
}
private static void clearData(byte[] cs) {
Arrays.fill(cs, (byte) 0);
}
private static void clearData(char[] cs) {
CharsUtils.clearData(cs);
}
}
It's a good idea or I must use another approach?
Edit 1: Our application need be comply with https://www.pcisecuritystandards.org/