Mantenimento di dati sensibili crittografati in memoria

1

Sto lavorando su un sistema di trasferimento elettronico dei fondi (EFT) e devo stare molto attento con i dati sensibili come i numeri delle carte di credito.

Abbiamo bisogno di queste informazioni montare i dati ISO 8583 prima di inviarli ai gateway EFT.

Utilizziamo char [] e byte [] per mantenere i dati sensibili e utilizzare Array.fill quando non abbiamo più bisogno di queste informazioni.

Pensando a migliorare la sicurezza, pensa di creare una classe per includere queste informazioni sensibili e magari mantenere i dati crittografati fino al momento necessario.

Ecco il codice che intendo utilizzare con una chiave generata a caso.

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);
    }       

}

È una buona idea o devo usare un altro approccio?

Modifica 1: La nostra applicazione deve essere conforme al link

    
posta Rodrigo Menezes 27.02.2018 - 17:29
fonte

2 risposte

8

Quando parli di sicurezza, è importante definire chiaramente ciò di cui sei preoccupato. Questo è in genere chiamato un "modello di minaccia". Immagino che tu ti interessi con qualcuno che cattura la memoria della tua applicazione e recupera i numeri della carta da essa.

Se ciò è corretto, vedo un problema fondamentale nel fatto che la tua chiave viene anche memorizzata nello stesso spazio di memoria e nel codice che la decrittografa. Stai rendendo solo leggermente più difficile ottenere i dati. Giudicherei questo uguale a una tecnica di offuscamento di base.

Da una nota a margine, DES è un codice obsoleto. Suggerirei che se sei interessato a questo argomento, potresti voler fare qualche altra ricerca. È anche importante tenersi aggiornati su tali sviluppi in questo spazio. I vecchi articoli possono suggerire l'utilizzo di approcci che non sono considerati sicuri anche quando erano autorevoli al momento.

    
risposta data 27.02.2018 - 17:52
fonte
4

OK, qual è il tuo modello di minaccia?

  • I dati arrivano al file di paging quando la memoria è sovraccaricata e alcuni altri processi fanno capolino nel file di paging. La tua soluzione migliora le tue probabilità di non ottenere una pagina decodificata nel file di paging, ma non elimina tale possibilità, dal momento che devi ancora decifrare i dati da qualche parte nella RAM. La soluzione giusta non sarebbe affatto una paginazione su disco o una partizione di file di scambio / swap crittografata.

  • Un processo di rouge cattura il controllo del sistema abbastanza da osservare direttamente la RAM di altri processi, ad es. ottenendo privilegi di amministratore. Questo è un gioco per ogni processo in questa scatola; il processo rouge ora può rilevare dove si memorizza la chiave di decodifica e decrittografare le informazioni protette, nella RAM o nel DB. La soluzione diventa irrilevante.

  • Esegui codice non completamente attendibile all'interno del tuo server, ad es. una sorta di plug-in di terze parti, e vuoi impedirgli di dare una sbirciatina alle informazioni a cui non dovrebbe avere accesso. Vedi sopra.

La soluzione corretta sarebbe basata su una chiara separazione dei privilegi, dando al codice il minimo privilegio di cui ha bisogno e mantenendo la quantità di codice attendibile il più piccola possibile.

Ad esempio, manterrei i dati sensibili nell'applicazione principale crittografati in ogni momento, probabilmente nemmeno recuperati nella RAM. Non ha niente a che fare con questo, tranne forse controllarne la presenza. Questo processo non dovrebbe avere l'accesso alla chiave di decrittografia affatto. Le macchine che eseguono il database non dovrebbero avere accesso alla chiave di decodifica.

Avrei eseguito il processo di esportazione come processo separato, o forse una VM separata, o anche una casella fisica separata. Tale processo sarebbe una porzione di codice molto piccola che prende gli ID dei record da esportare, li preleva dal DB (dove vengono memorizzati crittografati), li decripta e li invia al destinatario, presumibilmente usando un canale crittografato in modo diverso (ad es. tramite una connessione TLS o scrivendo un file crittografato).

L'esportatore dovrebbe essere in grado di accedere alla chiave di decodifica, presumibilmente memorizzato altrove, e avere privilegi a malapena sufficienti per leggere (non scrivere) la parte rilevante del database e per esportare nel canale di esportazione. Probabilmente hai già alcune informazioni per l'archiviazione, l'accesso e la rotazione delle chiavi segrete. L'esportatore dovrebbe correre per il minor tempo possibile e essere spento quando non viene utilizzato. Certamente non dovrebbe essere accessibile da Internet e accessibile in minima parte dalla rete interna, inclusi i computer che eseguono le altre parti dell'applicazione.

Ciò ridurrebbe la superficie di attacco a distanza e aggiungerà un livello o due affinché un attaccante possa perforare se per es. il tuo principale app server, o server di database, è compromesso.

    
risposta data 27.02.2018 - 18:28
fonte

Leggi altre domande sui tag