Java – Keeping sensitive data encrypted in memory

dataencryptionjavaSecurity

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/

Best Answer

When you are talking about security, it's important to clearly define what you are worried about. This is typically called a 'threat model'. I gather that you are concerned with someone capturing the memory of your application and retrieving the card numbers from it.

If that is correct, I see a fundamental issue in that your key is also being stored in that same memory space as well as the code that decrypts it. You are only making it slightly harder to get the data. I would judge this equal to a basic obfuscation technique.

On a side note, DES is an obsolete cipher. I would suggest that if you are interested in this topic, you may want to do some more research. It's also important to keep up-to-date on such developments in this space. Old articles may suggest using approaches that are not considered secure even when they were authoritative at the time.