Come crittografare String in Java


150

Ciò di cui ho bisogno è di crittografare la stringa che verrà visualizzata in un codice a barre 2D (PDF-417), quindi quando qualcuno ha un'idea per la scansione non otterrà nulla di leggibile.

Altri requisiti:

  • non dovrebbe essere complicato
  • non dovrebbe consistere in RSA, infrastruttura PKI, coppie di chiavi, ecc.

Deve essere abbastanza semplice da sbarazzarsi delle persone che curiosano in giro e facile da decifrare per altre aziende interessate a ottenere quei dati. Ci chiamano, diciamo loro lo standard o diamo loro una chiave semplice che può quindi essere utilizzata per la decrittazione.

Probabilmente quelle aziende potrebbero usare tecnologie diverse, quindi sarebbe bene attenersi a qualche standard che non è legato a qualche piattaforma o tecnologia speciale.

Cosa suggerisci? Esiste una classe Java che sta facendo encrypt()e decrypt()senza troppe complicazioni nel raggiungere elevati standard di sicurezza?



Attenzione . Molte delle risposte seguenti mostrano un metodo o l'altro per eseguire qualsiasi tipo di crittografia su Java. Le risposte potrebbero non riflettere le buone pratiche crittografiche e potrebbero non essere riviste bene; non esiste una sicurezza copia / incolla . Le risposte dovrebbero almeno tenere conto della conversione delle stringhe. La vera domanda con il codice a barre 2D incluso è troppo ampia e dovrebbe richiedere una soluzione specifica per il cliente.
Maarten Bodewes,

Risposte:


156

Questa è la prima pagina che viene visualizzata tramite Google e le vulnerabilità della sicurezza in tutte le implementazioni mi fanno rabbrividire, quindi sto postando questo per aggiungere informazioni sulla crittografia per gli altri come sono passati 7 anni dal post originale. Ho conseguito un Master in Ingegneria Informatica e ho trascorso molto tempo a studiare e imparare la crittografia, quindi sto buttando i miei due centesimi per rendere Internet un posto più sicuro.

Inoltre, tieni presente che molta implementazione potrebbe essere sicura per una determinata situazione, ma perché usarli e potenzialmente fare un errore? Usa gli strumenti più potenti che hai a disposizione a meno che tu non abbia un motivo specifico per non farlo. Nel complesso consiglio vivamente di usare una biblioteca e di stare lontano dai dettagli grintosi se puoi.

AGGIORNAMENTO DEL 4/5/18: Ho riscritto alcune parti per renderle più semplici da comprendere e ho cambiato la libreria consigliata da Jasypt alla nuova libreria di Google Tink , consiglierei di rimuovere completamente Jasypt da un'installazione esistente.

Prefazione

Descriverò le basi della crittografia simmetrica sicura di seguito e sottolineerò gli errori comuni che vedo online quando le persone implementano la crittografia da sole con la libreria Java standard. Se vuoi semplicemente saltare tutti i dettagli presenti nella nuova libreria di Google, prova a importarli nel tuo progetto e usa la modalità AES-GCM per tutte le tue crittografie e sarai sicuro.

Ora se vuoi imparare i dettagli grintosi su come crittografare in Java continua a leggere :)

Block Ciphers

Per prima cosa devi scegliere una chiave simmetrica Block Cipher. Un Block Cipher è una funzione / programma del computer utilizzato per creare pseudo-casualità. La pseudo-casualità è una casualità falsa che nessun computer diverso da un computer quantistico sarebbe in grado di dire la differenza tra essa e la casualità reale. Il Block Cipher è come il building block della crittografia e, se utilizzato con modalità o schemi diversi, è possibile creare crittografie.

Ora per quanto riguarda gli algoritmi Block Cipher disponibili oggi, assicurati di MAI , ripeto MAI usare DES , direi MAI MAI 3DES . L'unico Block Cipher che persino la versione NSA di Snowden è stata in grado di verificare di essere il più vicino possibile allo pseudo-casuale è AES 256 . Esiste anche AES 128; la differenza è che AES 256 funziona in blocchi a 256 bit, mentre AES 128 funziona in 128 blocchi. Tutto sommato, AES 128 è considerato sicuro sebbene siano stati scoperti alcuni punti deboli, ma 256 è il più solido possibile.

Fatto curioso DES è stato rotto dalla NSA quando è stato inizialmente fondato e in realtà ha mantenuto un segreto per alcuni anni. Sebbene alcune persone sostengano che 3DES sia sicuro, ci sono alcuni articoli di ricerca che hanno scoperto e analizzato i punti deboli di 3DES .

Modalità di crittografia

La crittografia viene creata quando si prende un codice a blocchi e si utilizza uno schema specifico in modo che la casualità sia combinata con una chiave per creare qualcosa che è reversibile fintanto che si conosce la chiave. Questa viene definita Modalità crittografia.

Ecco un esempio di una modalità di crittografia e la modalità più semplice nota come ECB solo per capire visivamente cosa sta succedendo:

Modalità BCE

Le modalità di crittografia che vedrai più comunemente online sono le seguenti:

BCE CTR, CBC, GCM

Esistono altre modalità al di fuori di quelle elencate e i ricercatori sono sempre alla ricerca di nuove modalità per migliorare i problemi esistenti.

Passiamo ora alle implementazioni e a ciò che è sicuro. Non usare MAI la BCE, non serve a nascondere i dati ripetuti, come mostrato dal famoso pinguino di Linux .Esempio di pinguino di Linux

Durante l'implementazione in Java, tenere presente che se si utilizza il codice seguente, la modalità ECB è impostata per impostazione predefinita:

Cipher cipher = Cipher.getInstance("AES");

... PERICOLO QUESTA È UNA VULNERABILITÀ! e sfortunatamente, questo è visto su StackOverflow e online in tutorial ed esempi.

Nonces e IVs

In risposta al problema riscontrato con la modalità BCE sono stati creati anche i nomi noti come IV. L'idea è che generiamo una nuova variabile casuale e la alleghiamo a ogni crittografia in modo che quando si crittografano due messaggi uguali risultino diversi. Il bello dietro questo è che un IV o un nonce è di dominio pubblico. Ciò significa che un utente malintenzionato può avere accesso a questo, ma fintanto che non hanno la tua chiave, non possono fare nulla con quella conoscenza.

I problemi comuni che vedrò è che le persone imposteranno IV come valore statico come nello stesso valore fisso nel loro codice. ed ecco la trappola per IVs nel momento in cui ne ripeti una, in realtà comprometti l'intera sicurezza della tua crittografia.

Generazione di un IV casuale

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Nota: SHA1 è rotto ma non sono riuscito a trovare come implementare correttamente SHA256 in questo caso d'uso, quindi se qualcuno vuole fare un crack e aggiornare questo sarebbe fantastico! Anche gli attacchi SHA1 sono ancora non convenzionali in quanto possono essere necessari alcuni anni su un enorme cluster per rompersi. Scopri i dettagli qui.

Implementazione CTR

Non è richiesta alcuna imbottitura per la modalità CTR.

 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

Implementazione CBC

Se scegli di implementare la modalità CBC, fallo con PKCS7Padding come segue:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

Vulnerabilità CBC e CTR e perché dovresti usare GCM

Sebbene alcune altre modalità come CBC e CTR siano sicure, si imbattono nel problema in cui un utente malintenzionato può capovolgere i dati crittografati, modificandone il valore quando viene decrittografato. Quindi diciamo che crittografare un messaggio bancario immaginario "Vendi 100", il tuo messaggio crittografato assomiglia a questo "eu23ng" l'attaccante cambia un po 'in "eu53ng" e all'improvviso quando decodifica il tuo messaggio, si legge come "Vendi 900".

Per evitare ciò, la maggior parte di Internet utilizza GCM e ogni volta che vedi HTTPS probabilmente usano GCM. GCM firma il messaggio crittografato con un hash e verifica che il messaggio non sia stato modificato utilizzando questa firma.

Eviterei di implementare GCM a causa della sua complessità. Stai meglio usando la nuova libreria di Google Google Trucco perché di nuovo qui se ripeti accidentalmente un IV stai compromettendo la chiave nel caso con GCM, che è l'ultimo difetto di sicurezza. Nuovi ricercatori stanno lavorando per le modalità di crittografia resistente alla ripetizione IV dove anche se si ripete la IV la chiave non è in pericolo, ma questo deve ancora diventare mainstream.

Ora, se vuoi implementare GCM, ecco un link a una bella implementazione GCM . Tuttavia, non posso garantire la sicurezza o se è correttamente implementato ma riduce le basi. Nota anche con GCM non c'è imbottitura.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Chiavi vs password

Un'altra nota molto importante è che quando si tratta di crittografia una chiave e una password non sono le stesse cose. Una chiave nella crittografia deve avere una certa quantità di entropia e casualità per essere considerata sicura. Questo è il motivo per cui è necessario assicurarsi di utilizzare le librerie crittografiche appropriate per generare la chiave per te.

Quindi hai davvero due implementazioni che puoi fare qui, la prima è usare il codice trovato su questo thread StackOverflow per la generazione di chiavi casuali . Questa soluzione utilizza un generatore di numeri casuali sicuro per creare una chiave da zero che è possibile utilizzare.

L'altra opzione meno sicura è usare l'input dell'utente come una password. Il problema di cui abbiamo discusso è che la password non ha abbastanza entropia, quindi dovremmo usare PBKDF2 , un algoritmo che prende la password e la rafforza. Ecco un'implementazione StackOverflow che mi è piaciuta . Tuttavia, la libreria Google Tink ha tutto questo integrato e dovresti approfittarne.

Sviluppatori Android

Un punto importante da sottolineare qui è sapere che il tuo codice Android è retroingegnerizzabile e la maggior parte dei casi lo è anche il codice java. Ciò significa che se memorizzi la password in testo semplice nel tuo codice. Un hacker può recuperarlo facilmente. Di solito, per questo tipo di crittografia, si desidera utilizzare la crittografia asimmetrica e così via. Questo non rientra nell'ambito di questo post, quindi eviterò di immergermi.

Una lettura interessante del 2013 : sottolinea che l'88% delle implementazioni di Crypto su Android sono state eseguite in modo errato.

Pensieri finali

Ancora una volta suggerirei di evitare di implementare direttamente la libreria java per crypto e di utilizzare Google Tink , ti farà risparmiare il mal di testa poiché hanno davvero fatto un buon lavoro nell'implementare correttamente tutti gli algoritmi. E anche allora assicurati di controllare i problemi sollevati sul github di Tink, il popup delle vulnerabilità qua e là.

Se hai domande o feedback, non esitare a commentare! La sicurezza è in continua evoluzione e devi fare del tuo meglio per tenerti aggiornato :)


15
Questa è la cosa più pulita che abbia mai visto.
Seraf,

1
@SabirKhan Potrebbe essere motivo di preoccupazione, ma gli algoritmi core non sono ancora stati interrotti, quindi non sarei troppo preoccupato per questo. Nel caso in cui non ti fidi, controlla anche github.com/google/keyczar , è stato sviluppato dal team di sicurezza di Google.
Konstantino Sparakis,

1
@KonstantinoSparakis: se non ho frainteso la documentazione per BasicTextEncryptor e StrongTextEncryptor di Jasypt, quelle classi usano DES e 3DES per la crittografia, che è esattamente ciò che dici ai lettori di non usare. IMO, è necessario sostituire gli esempi di codice forniti con uno che utilizza StandardPBEStringEncryptor di Jasypt e definisce manualmente un algoritmo AES da utilizzare.
xpages-noob

1
@ xpages-noob Ho aggiornato il post. Ho effettivamente trovato Google Tink, che è la più recente libreria supportata per crypto, quindi dovresti dare un'occhiata!
Konstantino Sparakis,

2
La dimensione del blocco AES è di 128 bit. In AES 256, la dimensione della chiave è di 256 bit. Allo stesso modo, AES 192 e AES 128. Inoltre, dal momento che Java 8, il getInstanceStrong()metodo di Cipherè preferibile a SHA1PRNG
Saptarshi Basu,

110

Consiglierei di usare alcuni cifrari simmetrici standard ampiamente disponibili come DES , 3DES o AES . Sebbene questo non sia l'algoritmo più sicuro, ci sono molte implementazioni e dovresti solo fornire la chiave a chiunque dovrebbe decifrare le informazioni nel codice a barre. javax.crypto.Cipher è ciò con cui vuoi lavorare qui.

Supponiamo che siano presenti i byte da crittografare

byte[] input;

Successivamente, avrai bisogno della chiave e dei byte vettoriali di inizializzazione

byte[] keyBytes;
byte[] ivBytes;

Ora puoi inizializzare il Cipher per l'algoritmo selezionato:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

La crittografia andrebbe così:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

E la decrittazione in questo modo:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

9
Posso suggerire di aggiornare questo esempio per fare riferimento DESedeall'algoritmo? Dal momento che questa è una domanda (e una risposta) popolare, sarebbe un peccato incoraggiare le persone a usare DES, poiché la cifra è così debole per gli standard odierni.
Duncan Jones,

qualcosa di sbagliato in javax.crypto.BadPaddingException: dato blocco finale non correttamente imbottito durante il decript
curiosità

2
@Duncan Infatti DES è debole ma suppongo che AES sarebbe preferibile rispetto a DESede (aka TipleDES): http://security.stackexchange.com/a/26181/69785
Piovezan,

2
Questo dovrebbe essere aggiornato per avere AES / GCM / NoPadding, DES è vulnerabile agli attacchi di forza bruta, anche TripleDes non è raccomandato
Konstantino Sparakis

1
La risposta di Konstantino Sparakis di seguito è molto meglio di questa.
Steve,

22

avvertimento

Non utilizzarlo come una sorta di misura di sicurezza.

Il meccanismo di crittografia in questo post è un pad One-time, il che significa che la chiave segreta può essere facilmente recuperata da un utente malintenzionato utilizzando 2 messaggi crittografati. Messaggi crittografati XOR 2 e ottieni la chiave. Così semplice!

Segnalato da Moussa


Sto usando il Base64Encoder / Decoder di Sun che si trova nel JRE di Sun, per evitare l'ennesimo JAR in lib. È pericoloso dal punto di usare OpenJDK o qualche altro JRE. Oltre a ciò, c'è un altro motivo per cui dovrei prendere in considerazione l'uso della lib di commons Apache con Encoder / Decoder?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class

1
Ho anche usato questa proposta di soluzione tramite sun.misc.BASE64Encoder ma quando si utilizzano stringhe piuttosto grandi per codificare, l'encoder ha restituito stringhe a blocchi (76 caratteri ciascuna). Sono quindi passato ad Apache Commons Codec Base64 che offre metodi di codifica senza blocchi!
basZero

78
Il meccanismo di crittografia che hai descritto è MOLTO PERICOLOSO se usato più di una volta. questo è il motivo per cui si chiama One-time pad. La chiave segreta può essere facilmente recuperata da un utente malintenzionato utilizzando 2 messaggi crittografati. xo 2 messaggi crittografati e ottieni la chiave. Così semplice!
xtrem

3
La sua idea non è quella di essere pesante, ma solo di rimbalzare le persone dal tentativo di leggere ciò che è scritto nei codici a barre 2D PDF-417. E comunque, ci sono solo alcuni indici non cruciali per nessuno ...
ante.sabo

2
OK. Preoccupato solo che qualcuno lo usi come meccanismo di crittografia.
xtrem,

Per la crittografia, è possibile evitare l'encoder (ad es. BASE64Encoder) per avere attacchi di forza bruta.
Jagrut Dalwadi,

13

grazie ho creato questa classe usando il tuo codice forse qualcuno lo trova utile

crypter di oggetti

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}

ha pubblicato una domanda correlata qui !
user2023507

Non dovrebbe essere il caso che passare diversi tasti durante la crittografia e la decrittografia non debba restituire il testo? Non sembra succedere qui. PS: sto usando diversi oggetti di questa classe per eseguire questo test.
instanceOfObject del

6

Aggiornamento il 12-dic-2019

A differenza di altre modalità come la CBC, la modalità GCM non richiede che l'IV sia imprevedibile. L'unico requisito è che il IV deve essere unico per ogni invocazione con una determinata chiave. Se si ripete una volta per una determinata chiave, la sicurezza può essere compromessa. Un modo semplice per raggiungere questo obiettivo è utilizzare un IV casuale da un forte generatore di numeri pseudo casuali, come mostrato di seguito.

È anche possibile usare una sequenza o un timestamp come IV, ma potrebbe non essere così banale come potrebbe sembrare. Ad esempio, se il sistema non tiene correttamente traccia delle sequenze già utilizzate come IV in un archivio persistente, una chiamata può ripetere un IV dopo il riavvio del sistema. Allo stesso modo, non esiste un orologio perfetto. L'orologio del computer si riadatta ecc.

Inoltre, la chiave deve essere ruotata dopo ogni 2 ^ 32 invocazioni. Per ulteriori dettagli sul requisito IV, fare riferimento a questa risposta e alle raccomandazioni del NIST .


Questo è il codice di crittografia e decrittografia che ho appena scritto in Java 8 considerando i seguenti punti. Spero che qualcuno lo trovi utile:

  1. Algoritmo di crittografia : la crittografia a blocchi AES con chiave a 256 bit è considerata abbastanza sicura. Per crittografare un messaggio completo, è necessario selezionare una modalità. Si consiglia la crittografia autenticata (che fornisce sia riservatezza che integrità). GCM, CCM ed EAX sono le modalità di crittografia autenticate più comunemente utilizzate. GCM è generalmente preferito e funziona bene nelle architetture Intel che forniscono istruzioni dedicate per GCM. Tutte e tre le modalità sono basate sul CTR (basate sul contatore) e quindi non hanno bisogno di riempimento. Di conseguenza non sono vulnerabili agli attacchi relativi all'imbottitura

  2. Per GCM è necessario un vettore di inizializzazione (IV). Il IV non è un segreto. L'unico requisito deve essere casuale o imprevedibile. In Java, la SecuredRandomclasse intende produrre numeri pseudo casuali crittograficamente forti. L'algoritmo di generazione di numeri pseudo-casuali può essere specificato nel getInstance()metodo. Tuttavia, a partire da Java 8, il modo consigliato è utilizzare il getInstanceStrong()metodo che utilizzerà l'algoritmo più potente configurato e fornito daProvider

  3. NIST consiglia 96 bit IV per GCM per promuovere l'interoperabilità, l'efficienza e la semplicità di progettazione

  4. Per garantire ulteriore sicurezza, nella seguente implementazione SecureRandomviene nuovamente eseguito il seeding dopo aver prodotto ogni 2 ^ 16 byte di generazione di pseudo casuali byte

  5. Il destinatario deve conoscere il IV per poter decrittografare il testo cifrato. Pertanto, il IV deve essere trasferito insieme al testo cifrato. Alcune implementazioni inviano IV come AD (dati associati), il che significa che il tag di autenticazione verrà calcolato sia sul testo cifrato che sul IV. Tuttavia, ciò non è richiesto. L'IV può essere semplicemente pre-sospeso con il testo cifrato perché se l'IV viene modificato durante la trasmissione a causa di un attacco intenzionale o di un errore di rete / file system, la convalida del tag di autenticazione fallirà comunque

  6. Le stringhe non devono essere utilizzate per contenere il messaggio di testo chiaro o la chiave poiché le stringhe sono immutabili e quindi non possiamo cancellarle dopo l'uso. Queste stringhe non chiarite rimangono quindi nella memoria e possono apparire in una discarica. Per lo stesso motivo, il client che chiama questi metodi di crittografia o decrittografia deve cancellare tutte le variabili o gli array che contengono il messaggio o la chiave dopo che non sono più necessari.

  7. Nessun provider è codificato nel codice seguendo le raccomandazioni generali

  8. Infine per la trasmissione su rete o archiviazione, la chiave o il testo cifrato devono essere codificati utilizzando la codifica Base64. I dettagli di Base64 sono disponibili qui . È necessario seguire l'approccio Java 8

Le matrici di byte possono essere cancellate usando:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

Tuttavia, a partire da Java 8, non esiste un modo semplice per cancellare SecretKeyspece SecretKeypoiché le implementazioni di queste due interfacce non sembrano aver implementato il metodo destroy()dell'interfaccia Destroyable. Nel codice seguente, viene scritto un metodo separato per cancellare SecretKeySpece SecretKeyusando reflection.

La chiave deve essere generata utilizzando uno dei due approcci indicati di seguito.

Si noti che le chiavi sono segreti come le password, ma a differenza delle password che sono destinate all'uso umano, le chiavi sono pensate per essere utilizzate da algoritmi crittografici e quindi dovrebbero essere generate usando solo il modo sopra.

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

La chiave di crittografia può essere generata principalmente in due modi:

  • Senza alcuna password

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • Con password

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

Aggiornamento basato sui commenti

Come sottolineato da @MaartenBodewes, la mia risposta non ha gestito nessuno Stringcome richiesto dalla domanda. Pertanto, proverò a colmare questa lacuna nel caso in cui qualcuno si imbattesse in questa risposta e lasci dubbi sulla gestione String.

Come indicato in precedenza nella risposta, la gestione delle informazioni sensibili in a non Stringè, in generale, una buona idea perché Stringè immutabile e quindi non possiamo cancellarla dopo l'uso. E come sappiamo, anche quando a Stringnon ha un forte riferimento, il garbage collector non si precipita immediatamente per rimuoverlo dal mucchio. Pertanto, Stringcontinua a rimanere nella memoria per una finestra temporale sconosciuta anche se non è accessibile al programma. Il problema è che una discarica di heap in quel lasso di tempo rivelerebbe le informazioni sensibili. Pertanto, è sempre meglio gestire tutte le informazioni sensibili in un array di byte o in un array di caratteri e quindi riempire l'array con 0 una volta che viene raggiunto il loro scopo.

Tuttavia, con tutta questa conoscenza, se finiamo ancora in una situazione in cui le informazioni sensibili da crittografare si trovano in un String, dobbiamo prima convertirle in un array di byte e invocare le funzioni encrypte decryptintrodotte sopra. (L'altra chiave di input può essere generata utilizzando lo snippet di codice fornito sopra).

A Stringpuò essere convertito in byte nel modo seguente:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

A partire da Java 8, Stringè archiviato internamente in heap con UTF-16codifica. Tuttavia, abbiamo usato UTF-8qui perché di solito occupa meno spazio di UTF-16, specialmente per i caratteri ASCII.

Allo stesso modo, l'array di byte crittografato può anche essere convertito in una stringa come di seguito:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

1
Per quanto voglia votare questa risposta in quanto sembra aderire alle pratiche di crittografia correnti, non vedo alcuna gestione delle stringhe, rendendola più simile a una descrizione su come utilizzare la modalità GCM. Come tale non riesce a rispondere alla domanda .
Maarten Bodewes,

1
@MaartenBodewes Grazie mille per aver dedicato del tempo a rivedere e condividere feedback. Ho scritto questo con la consapevolezza che la crittografia di un Stringutilizzo delle funzioni create sopra sarebbe banale. Tuttavia, a una seconda occhiata dopo aver letto il tuo commento, capisco che potrebbe non essere ovvio. Sicuramente modificherò per aggiungere quei dettagli.
Saptarshi Basu il

5

Cosa ne pensi di questo:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Funziona bene per me ed è piuttosto compatto.


cosa succederà se il parametro di immissione secret == null o input == null? lavorare con byte piuttosto che con stringhe è ok, ma nel mio caso era irrilevante ... l'unica cosa che conta è che questo deve essere leggibile e decodificabile con qualsiasi dispositivo, con qualsiasi codifica dei caratteri possibile ...
ante.sabo

@ ante.sabo apparentemente, lancerà un NPE. Questa è l'unica cosa da fare con i NULL.
Miha_x64

Finché input.length <= secret.lengthvale e non secretviene mai riutilizzato, questo è sicuro e chiamato a one-time-pad. In questi casi input.length > secret.lengthè una variante del codice Vigenère e considerata molto debole.
trichner,

5

Puoi usare Jasypt

Con Jasypt, crittografare e controllare una password può essere semplice come ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

crittografia:

String myEncryptedText = textEncryptor.encrypt(myText);

decrittazione:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

Caratteristiche:

Jasypt offre semplici tecniche di crittografia unidirezionale (digest) e bidirezionale.

API aperta per l'utilizzo con qualsiasi provider JCE e non solo con quello predefinito di Java VM. Jasypt può essere facilmente utilizzato con provider noti come Bouncy Castle. Per saperne di più.

Maggiore sicurezza per le password degli utenti. Per saperne di più.

Supporto per la crittografia binaria. Jasypt consente il digest e la crittografia dei file binari (array di byte). Crittografa i tuoi oggetti o file quando necessario (per essere inviato in rete, ad esempio).

Supporto per la crittografia dei numeri. Oltre ai testi e ai binari, consente il digest e la crittografia dei valori numerici (BigInteger e BigDecimal, altri tipi numerici sono supportati durante la crittografia per la persistenza dell'ibernazione). Per saperne di più.

Completamente sicuro per i thread.

Supporto per il pooling di codificatori / digestori, al fine di ottenere prestazioni elevate nei sistemi multi-processore / multi-core.

Include una versione leggera ("lite") della libreria per una migliore gestibilità in ambienti con dimensioni ridotte come le piattaforme mobili.

Fornisce strumenti di crittografia semplici e senza configurazione per gli utenti non esperti di crittografia, nonché strumenti di crittografia standard altamente configurabili per utenti esperti.

Sospendi l'integrazione facoltativa 3 e 4 per i campi persistenti delle entità mappate in modo crittografato. La crittografia dei campi è definita nei file di mapping di Hibernate e rimane trasparente per il resto dell'applicazione (utile per dati personali sensibili, database con molti utenti abilitati alla lettura ...). Crittografa testi, file binari, numeri, valori booleani, date ... Ulteriori informazioni.

Perfettamente integrabile in un'applicazione Spring, con funzionalità di integrazione specifiche per Spring 2, Spring 3.0 e Spring 3.1. Tutti i digestori e gli encryptor in jasypt sono progettati per essere facilmente utilizzati (istanziati, iniettati in dipendenza ...) dalla primavera. E, poiché sono thread-safe, possono essere utilizzati senza problemi di sincronizzazione in un ambiente orientato al singleton come Spring. Ulteriori informazioni: Primavera 2, Primavera 3.0, Primavera 3.1.

Spring Security (precedentemente Acegi Security) integrazione opzionale per eseguire la crittografia delle password e le attività di corrispondenza per il framework di sicurezza, migliorando la sicurezza delle password degli utenti utilizzando meccanismi di crittografia delle password più sicuri e fornendo un livello più elevato di configurazione e controllo. Per saperne di più.

Fornisce funzionalità avanzate per la crittografia totale o parziale dei file di configurazione di un'applicazione, incluse informazioni riservate come le password del database. Integra perfettamente la configurazione crittografata in applicazioni semplici, basate su Spring e / o abilitate per Hibernate. Per saperne di più.

Fornisce strumenti CLI (Command Line Interface) facili da usare per consentire agli sviluppatori di inizializzare i loro dati crittografati e includere operazioni di crittografia / decrittografia / digest in attività di manutenzione o script. Per saperne di più.

Si integra in Apache Wicket, per una crittografia più solida degli URL nelle applicazioni sicure.

Guide complete e documentazione javadoc, per consentire agli sviluppatori di comprendere meglio cosa stanno realmente facendo ai propri dati.

Supporto per set di caratteri robusto, progettato per crittografare e digerire adeguatamente i testi qualunque sia il set di caratteri originale. Supporto completo per lingue come giapponese, coreano, arabo ... senza problemi di codifica o piattaforma.

Funzionalità di configurazione di livello molto elevato: lo sviluppatore può implementare trucchi come istruire un "crittografo" per richiedere ad un server HTTPS remoto la password da utilizzare per la crittografia. Ti consente di soddisfare le tue esigenze di sicurezza.


1
Ma quale sicurezza Jasyptoffre? Non riesco a capirlo dal loro sito web. È indistinguibile dagli attacchi in chiaro o scelti? Integrità? Riservatezza?
trichner,

4

Ecco la mia implementazione da meta64.com come Spring Singleton. Se si desidera creare un'istanza di ciper per ogni chiamata che funzioni anche, e quindi è possibile rimuovere le chiamate "sincronizzate", ma attenzione "cifratura" non è thread-safe.

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}

3
Questo crittograferà con la modalità BCE che è orribile. Dovresti impostare almeno la modalità CBC o GCM
Konstantino Sparakis,

Grazie per il suggerimento Konstantinto, l'ho cercato su Google e ho trovato del codice che utilizza "AES / CBC / PKCS5Padding" come stringa Init per Cipher, anziché solo "AES", ma ne esaminerò di più. Oppure, se lo desideri, puoi fornire la soluzione effettiva, in modo che gli altri possano vedere il modo migliore. Tuttavia, a parte i dettagli della CBC, credo che la mia soluzione sia la più semplice e sicura e meriti di essere votata al di sopra di tutto il resto.

Sì, non preoccuparti, Crypto è un argomento complicato. Purtroppo ogni implementazione in questa pagina è rotta e purtroppo è la prima pagina che viene visualizzata quando si utilizza google per cercare "come eseguire la crittografia Java". Quando avrò la possibilità, proverò a risolverli tutti.
Konstantino Sparakis,

Il mio esempio è lo stesso di questo: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/… Tranne che avevo bisogno di Cipher.getInstance ("AES / ECB / PKCS5Padding"); Il mio codice presuppone che ci sia un file delle proprietà con una chiave di crittografia perfettamente lunga 16 byte, ma per crittografare una stringa da una password "fornita dall'utente" la pagina Oracle (collegata sopra) mostra anche il modo di farlo.

1
Quindi il problema con la BCE è che è estremamente vulnerabile all'analisi delle frequenze. C'è il famoso esempio del pinguino di Linux, blog.filippo.io/the-ecb-penguin per vedere come, sebbene l'immagine sia crittografata, puoi ancora dire che è un pinguino. Sono andato avanti e ho scritto i miei pensieri in giù soggetto sotto :) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis

4

Qui una soluzione semplice con solo java.*e javax.crypto.*dipendenze per la crittografia dei byte che forniscono riservatezza e integrità . Deve essere indistinguibile da un attacco di testo in chiaro scelto per brevi messaggi nell'ordine dei kilobyte.

Utilizza AESin GCMmodalità senza riempimento, una chiave a 128 bit è derivata da PBKDF2molte iterazioni e un salt statico dalla password fornita. Questo assicura che la forzatura bruta delle password sia dura e distribuisce l'entropia sull'intera chiave.

Viene generato un vettore di inizializzazione casuale (IV) che verrà anteposto al testo cifrato. Inoltre, il byte statico 0x01viene anteposto come primo byte come "versione".

L'intero messaggio entra nel codice di autenticazione del messaggio (MAC) generato da AES/GCM.

Ecco qua, nessuna classe di crittografia con dipendenze esterne che fornisce riservatezza e integrità :

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Ecco l'intero progetto con una bella CLI: https://github.com/trichner/tcrypt

Modifica: ora con appropriato encryptStringedecryptString


Questo è incredibile. Grazie! Ho imparato molto dal tuo codice e dopo aver creato la classe Exception di BadVersionException, il tuo codice ha funzionato perfettamente la prima volta. Eccellente!!
Morkus,

Mi piace questo tentativo. Detto questo ... Il sale dovrebbe essere casuale, non statico. Probabilmente le iterazioni non dovrebbero essere statiche. GCM include già il IV nel calcolo del tag. Tuttavia non contiene il numero di versione. Non è necessario specificare il provider per la portabilità, quello "SunJCE" sarà quello predefinito sulle piattaforme che lo supportano. Questo codice non contiene alcuna gestione delle stringhe di messaggi, necessaria per questa particolare domanda .
Maarten Bodewes,

Bene, l'ho pulito un po 'di più e ho aggiunto il richiesto encryptStringe decryptString:)
trichner

Questo ha funzionato molto bene; ty per il codice. Va notato che questo codice richiede API 19 (Kit Kat) o superiore per funzionare correttamente.
PGMacDesign

3

Vorrei prendere in considerazione l'utilizzo di qualcosa come https://www.bouncycastle.org/ È una libreria precompilata che ti consente di crittografare qualsiasi cosa ti piaccia con un numero di diversi Cipher Capisco che vuoi solo proteggere dallo snooping, ma se davvero vuoi proteggere le informazioni, l'utilizzo di Base64 non ti proteggerà effettivamente.


1
Il solo fatto di raccomandare una libreria di crittografia casuale con cifre non è una risposta alla domanda. Oltre a ciò, perché non usare le cifre integrate?
Maarten Bodewes,

2

Ecco alcuni link che puoi leggere su ciò che Java supporta

Crittografia / decrittografia di un flusso di dati.

Questo esempio dimostra come crittografare (utilizzando un algoritmo di crittografia simmetrica come AES, Blowfish, RC2, 3DES, ecc.) Una grande quantità di dati. I dati vengono passati in blocchi a uno dei metodi di crittografia: EncryptBytes, EncryptString, EncryptBytesENC o EncryptStringENC. (Il nome del metodo indica il tipo di input (stringa o array di byte) e il tipo restituito (stringa codificata o array di byte). Le proprietà FirstChunk e LastChunk vengono utilizzate per indicare se un blocco è il primo, il mezzo o l'ultimo in uno stream per essere crittografato. Per impostazione predefinita, sia FirstChunk che LastChunk sono uguali a true, il che significa che i dati trasmessi rappresentano l'intero importo.

JCERefGuide

Esempi di crittografia Java


Sì, esiste la crittografia supportata da Java. La crittografia di uno stream non è nemmeno ciò che è stato richiesto.
Maarten Bodewes,

2

Come molti ragazzi hanno già detto, dovresti usare un cifrario standard che è troppo usato come DES o AES.

Un semplice esempio di come è possibile crittografare e decrittografare una stringa in Java usando AES .

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}

CBC non è più una modalità sicura. Il riempimento è vulnerabile al riempimento degli attacchi Oracle. Inoltre, la gestione della chiave e dei messaggi in String non è sicura. Rimarranno nel pool di String e appariranno in una discarica di heap
Saptarshi Basu,

2
Apprezzo il commento. Questo era un semplice esempio dei metodi di crittografia e decrittografia di Java come richiesto dall'utente. La domanda è stata posta circa 9 anni fa e ha avuto risposta in base a ciò. Grazie.
viveknaskar,

2
Sì, questo sembra un modo semplice per introdurre encrypt / decrypt. Ha funzionato come un incanto per me .... Grazie.
Codewrapper il

0

Ecco una soluzione copia / incolla. Consiglio anche di leggere e votare la risposta di @ Konstantino anche se non fornisce alcun codice. Il vettore di inizializzazione (IV) è come un sale - non deve essere tenuto segreto. Sono nuovo di GCM e apparentemente AAD è facoltativo e utilizzato solo in determinate circostanze. Imposta la chiave nella variabile d'ambiente SECRET_KEY_BASE. Usa qualcosa come KeePass per generare una password di 32 caratteri. Questa soluzione è modellata sulla mia soluzione Ruby.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Ecco un esempio:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.

-4

Potresti voler prendere in considerazione alcuni strumenti automatici per eseguire la generazione del codice di crittografia / decrittazione, ad es. https://www.stringencrypt.com/java-encryption/

Può generare diversi codici di crittografia e decrittografia ogni volta per la crittografia di stringa o file.

È abbastanza utile quando si tratta di crittografia rapida delle stringhe senza utilizzare RSA, AES ecc.

Risultati del campione:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

Lo usiamo sempre nella nostra azienda.


Questa è sicurezza attraverso l'oscurità e non è davvero sicura.
Chloe

Questa domanda richiede l'attuale crittografia moderna della forza crittografica come AES, non solo l'offuscamento per rendere le stringhe più difficili da estrarre staticamente. Questo non sembra nemmeno mantenere uno stato tra i caratteri, quindi è suscettibile all'analisi delle frequenze. ( Cifra di sostituzione con alfabeto singolo , tranne che per i codici codificati UTF-16 invece dell'alfabeto latino. Ma se lo si utilizza sul testo ASCII in inglese, si ottengono solo alcuni valori di caratteri univoci a 16 bit, a meno che non lo stia leggendo male)
Peter Cordes,

-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }

Formalmente crittografa i dati in un formato illeggibile. Per decrittografare usa lo stesso codice. E cambia s [i] * f in s [I] / f.
Arshad shaik,

Questa è sicurezza attraverso l'oscurità e non è davvero sicura.
Chloe

-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }

1
JavaEncryprtionUtil fa parte dell'API JDK? in caso contrario dovresti precisare il nome della libreria.
Little Student di Fermat,

4
Non riesco a trovare quella classe. Sembra che la risposta sia inventata.
james.garriss,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.