Byte iniziali errati dopo la decrittografia Java AES / CBC


116

Cosa c'è di sbagliato nel seguente esempio?

Il problema è che la prima parte della stringa decrittografata non ha senso. Tuttavia, il resto va bene, ottengo ...

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}

48
NON USARE ALCUNA RISPOSTA A QUESTA DOMANDA IN UN PROGETTO SERIO! Tutti gli esempi forniti in questa domanda sono vulnerabili al padding oracle e sono nel complesso un pessimo utilizzo della crittografia. Introducerai una grave vulnerabilità di crittografia nel tuo progetto utilizzando uno qualsiasi degli snippet di seguito.
HoLyVieR

16
@HoLyVieR, Riguardo alle seguenti citazioni: "Non dovresti sviluppare la tua libreria di crittografia" e "utilizzare un'API di alto livello fornita dal tuo framework". Nessuno qui sta sviluppando la propria libreria di crittografia. Stiamo semplicemente utilizzando l'API di alto livello già esistente fornita dal framework java. Lei, signore, è estremamente impreciso.
k170

10
@MaartenBodewes, solo perché siete entrambi d'accordo non significa che siate entrambi nel giusto. I bravi sviluppatori conoscono la differenza tra il wrapping di un'API di alto livello e la riscrittura di un'API di basso livello. I buoni lettori noteranno che l'OP ha chiesto un "semplice esempio di crittografia / decrittografia AES java" ed è esattamente quello che ha ottenuto . Inoltre non sono d'accordo con le altre risposte, motivo per cui ho pubblicato una mia risposta. Forse dovreste provare lo stesso e illuminarci tutti con la vostra esperienza.
k170

6
@HoLyVieR Questa è davvero la cosa più assurda che abbia mai letto su SO! Chi sei tu per dire alle persone cosa possono e non possono sviluppare?
TedTrippin

14
Non vedo ancora esempi @HoLyVieR. Vediamo alcuni, o suggerimenti alle librerie? Per niente costruttivo.
danieljimenez

Risposte:


245

Molte persone, me compreso, affrontano molti problemi nel fare questo lavoro a causa della mancanza di alcune informazioni come, dimenticando di convertire in Base64, vettori di inizializzazione, set di caratteri, ecc. Quindi ho pensato di creare un codice completamente funzionante.

Spero che questo sia utile a tutti voi: per compilare è necessario un file jar aggiuntivo di Apache Commons Codec, disponibile qui: http://commons.apache.org/proper/commons-codec/download_codec.cgi

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

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

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.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 string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.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[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}

47
Se non vuoi dipendere dalla libreria di codec Apache Commons di terze parti, puoi utilizzare javax.xml.bind.DatatypeConverter di JDK per eseguire la codifica / decodifica Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
Stai usando una flebo costante ?!
vianna77

36
Java 8 ha già strumenti Base64: java.util.Base64.getDecoder () e java.util.Base64.getEncoder ()
Hristo Stoyanov

11
Il IV non deve essere segreto, ma deve essere imprevedibile per la modalità CBC (e unico per CTR). Può essere inviato insieme al testo cifrato. Un modo comune per farlo è anteporre l'IV al testo cifrato e tagliarlo prima della decrittografia. Dovrebbe essere generato tramiteSecureRandom
Artjom B.

6
Una password non è una chiave. Una flebo dovrebbe essere casuale.
Maarten Bodewes

40

Ecco una soluzione senza Apache Commons Codec's Base64:

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

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Esempio di utilizzo:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

stampe:

Hello world!
դ;��LA+�ߙb*
Hello world!

5
Questo è un esempio perfettamente funzionale, proprio come quello di @ chandpriyankara. Ma perché definire una firma di encrypt(String)e non encrypt(byte[] )? La crittografia (anche la decrittografia) è un processo basato su byte (AES lo è comunque). La crittografia prende i byte come input e restituisce i byte, così come la decrittografia (caso in questione: l' Cipheroggetto lo fa). Ora, un caso d'uso particolare potrebbe essere quello di avere byte crittografati provenienti da una stringa o essere inviati come una stringa (allegato MIME base64 per una posta ...), ma questo è un problema di codifica byte, per il quale esistono centinaia di soluzioni, totalmente estranee a AES / crittografia.
GPI

3
@GPI: Sì, ma lo trovo più utile Stringspoiché è fondamentalmente quello con cui lavoro il 95% delle volte e finisci comunque per convertire.
BullyWiiPlaza

9
No, questo non è equivalente al codice di chandpriyankara! Il tuo codice utilizza ECB che è generalmente insicuro e non desiderato. Dovrebbe specificare esplicitamente CBC. Quando viene specificato CBC, il codice verrà interrotto.
Dan

Perfettamente funzionante, completamente insicuro e utilizza pratiche di programmazione pessime. la classe è chiamata male. La dimensione della chiave non viene controllata in anticipo. Ma soprattutto, il codice utilizza la modalità ECB insicura, nascondendo il problema nella domanda originale . Infine, non specifica una codifica dei caratteri, il che significa che la decodifica in testo potrebbe non riuscire su altre piattaforme.
Maarten Bodewes

24

Mi sembra che tu non abbia a che fare correttamente con il tuo vettore di inizializzazione (IV). È passato molto tempo dall'ultima volta che ho letto di AES, IV e block chaining, ma la tua linea

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

non sembra essere OK. Nel caso di AES, puoi pensare al vettore di inizializzazione come allo "stato iniziale" di un'istanza di cifratura, e questo stato è un po 'di informazione che non puoi ottenere dalla tua chiave ma dal calcolo effettivo del cifrario di cifratura. (Si potrebbe sostenere che se l'IV potesse essere estratto dalla chiave, allora non sarebbe di alcuna utilità, poiché la chiave è già stata data all'istanza di cifratura durante la sua fase di inizializzazione).

Pertanto, dovresti ottenere l'IV come byte [] dall'istanza di cifratura alla fine della tua crittografia

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

e dovresti inizializzare il tuo Cipherin DECRYPT_MODEcon questo byte []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Quindi, la tua decrittazione dovrebbe essere OK. Spero che questo ti aiuti.


Grazie per aver aiutato un principiante. Ho copiato questo esempio da altri post. Suppongo che tu non sappia come evitare la necessità di una flebo? Ho visto, ma non provato, altri esempi AES che non lo usano.
TedTrippin

Ignoralo, ho trovato la risposta! Devo usare AES / ECB / PKCS5Padding.
TedTrippin

20
La maggior parte delle volte non si desidera utilizzare ECB. Basta google perché.
João Fernandes

2
@ Mushy: ha convenuto che la scelta e l'impostazione esplicita di un IV, da una fonte casuale attendibile, è meglio che lasciare che l'istanza Cihper ne prenda uno. D'altra parte, questa risposta affronta il problema originale di confondere il vettore di inizializzazione per la chiave. Questo è il motivo per cui all'inizio è stato votato. Ora, questo post è diventato più un punto di riferimento del codice di esempio, e le persone qui ne hanno fatto un ottimo esempio, proprio accanto a ciò di cui parlava la domanda originale.
GPI

3
@GPI Upvoted. Gli altri "ottimi esempi" non sono così grandi e in realtà non affrontano affatto la questione. Invece questo sembra essere stato il posto giusto per i neofiti per copiare ciecamente campioni crittografici senza capire che potrebbero esserci possibili problemi di sicurezza e, come sempre, ci sono.
Maarten Bodewes

17

L'IV che stai utilizzando per la decrittazione non è corretto. Sostituisci questo codice

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Con questo codice

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

E questo dovrebbe risolvere il tuo problema.


Di seguito è incluso un esempio di una semplice classe AES in Java. Non consiglio di utilizzare questa classe in ambienti di produzione, poiché potrebbe non tenere conto di tutte le esigenze specifiche dell'applicazione.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

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

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Nota che AES non ha nulla a che fare con la codifica, motivo per cui ho scelto di gestirlo separatamente e senza bisogno di librerie di terze parti.


Prima di tutto, non hai risposto alla domanda originale. Secondo, perché stai rispondendo a una domanda già risposta e ben accettata? Pensavo che la protezione avrebbe dovuto fermare questo spam.
TedTrippin

14
Come la risposta accettata, ho scelto di rispondere alla tua domanda tramite l'esempio. Ho fornito una parte di codice completamente funzionale, che mostra come gestire correttamente il vettore di inizializzazione, tra le altre cose. Per quanto riguarda la tua seconda domanda, ho ritenuto che fosse necessaria una risposta aggiornata poiché il codec Apache non è più necessario. Quindi no, questo non è spam. Smettila di inciampare.
k170

7
Un IV ha uno scopo specifico che è quello di randomizzare il testo cifrato e fornire sicurezza semantica. Se utilizzi la stessa coppia chiave + IV, gli aggressori possono determinare se hai inviato un messaggio con lo stesso prefisso di prima. La flebo non deve essere segreta, ma deve essere imprevedibile. Un modo comune è semplicemente anteporre l'IV al testo cifrato e tagliarlo prima della decrittazione.
Artjom B.

4
downvote: hardcoded IV, vedere il commento di Artjom B. sopra perché è cattivo
Murmel

1
La modalità CTR deve essere abbinata a NoPadding. La modalità CTR non è certamente richiesta al posto di CBC (a meno che non si applichino gli oracoli di riempimento), ma se viene utilizzato il CTR , utilizzare "/NoPadding". CTR è una modalità che trasforma AES in un cifrario a flusso e un cifrario a flusso opera sui byte anziché sui blocchi.
Maarten Bodewes

16

In questa risposta scelgo di avvicinarmi al tema principale "Semplice esempio di crittografia / decrittografia AES Java" e non alla specifica domanda di debug perché penso che questo trarrà vantaggio dalla maggior parte dei lettori.

Questo è un semplice riassunto del mio post sul blog sulla crittografia AES in Java, quindi consiglio di leggerlo prima di implementare qualsiasi cosa. Tuttavia fornirò ancora un semplice esempio da utilizzare e fornirò alcuni suggerimenti a cosa fare attenzione.

In questo esempio, sceglierò di utilizzare la crittografia autenticata con la modalità Galois / Counter o GCM . Il motivo è che nella maggior parte dei casi desideri integrità e autenticità in combinazione con riservatezza (leggi di più nel blog ).

Tutorial sulla crittografia / decrittografia AES-GCM

Di seguito sono riportati i passaggi necessari per crittografare / decrittografare con AES-GCM con Java Cryptography Architecture (JCA) . Non mischiare con altri esempi , poiché sottili differenze potrebbero rendere il tuo codice completamente insicuro.

1. Crea chiave

Poiché dipende dal tuo caso d'uso, assumerò il caso più semplice: una chiave segreta casuale.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Importante:

2. Creare il vettore di inizializzazione

Viene utilizzato un vettore di inizializzazione (IV) in modo che la stessa chiave segreta crei diversi testi cifrati .

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Importante:

3. Crittografa con IV e chiave

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

Importante:

  • utilizzare tag di autenticazione a 16 byte / 128 bit (utilizzato per verificare integrità / autenticità)
  • il tag di autenticazione verrà automaticamente aggiunto al testo cifrato (nell'implementazione JCA)
  • poiché GCM si comporta come un cifrario a flusso, non è richiesto alcun riempimento
  • utilizzare CipherInputStreamquando si crittografano grandi blocchi di dati
  • vuoi controllare dati aggiuntivi (non segreti) se sono stati modificati? Potresti voler utilizzare i dati associati con cipher.updateAAD(associatedData); Altro qui.

3. Serializza in un singolo messaggio

Basta aggiungere IV e testo cifrato. Come affermato sopra, la flebo non deve essere segreta.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

Facoltativamente codifica con Base64 se hai bisogno di una rappresentazione di stringa. Utilizza l' implementazione integrata di Android o Java 8 (non utilizzare Apache Commons Codec: è un'implementazione orribile). La codifica viene utilizzata per "convertire" array di byte in rappresentazioni di stringa per renderlo ASCII sicuro, ad esempio:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. Preparare la decrittografia: deserializza

Se hai codificato il messaggio, prima decodificalo in un array di byte:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Importante:

5. Decrittografa

Inizializza la crittografia e imposta gli stessi parametri della crittografia:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

Importante:

  • non dimenticare di aggiungere i dati associati con cipher.updateAAD(associatedData);se li hai aggiunti durante la crittografia.

Uno snippet di codice funzionante può essere trovato in questo gist.


Tieni presente che le implementazioni Android (SDK 21+) e Java (7+) più recenti dovrebbero avere AES-GCM. Le versioni precedenti potrebbero non esserlo. Scelgo ancora questa modalità, poiché è più facile da implementare oltre ad essere più efficiente rispetto alla modalità simile di Encrypt-then-Mac (con ad esempio AES-CBC + HMAC ). Consulta questo articolo su come implementare AES-CBC con HMAC .


Il problema è che chiedere esempi è esplicitamente fuori tema su SO. E il problema più grande è che si tratta di parti di codice non revisionate, difficili da convalidare. Apprezzo lo sforzo, ma non credo che SO dovrebbe essere il posto giusto per questo.
Maarten Bodewes

1
Ammiro lo sforzo, però, quindi mi limiterò a segnalare un singolo errore: "il iv deve essere imprevedibile in combinazione con l'essere unico (cioè usa iv casuale)" - questo è vero per la modalità CBC ma non per GCM.
Maarten Bodewes

this is true for CBC mode but not for GCMintendi l'intera parte, o solo che non ha bisogno di essere imprevedibile?
Patrick Favre

1
"Se non capisci l'argomento, probabilmente non dovresti usare primitive di basso livello in primo luogo" certo, DOVREBBE essere così, molti sviluppatori lo fanno ancora. Non sono sicuro di astenermi dal pubblicare contenuti di alta qualità riguardanti la sicurezza / crittografia in luoghi in cui spesso non c'è molto che sia la soluzione giusta per questo. - grazie per aver indicato il mio errore btw
Patrick Favre

1
OK, solo perché mi piace la risposta rispetto al contenuto (piuttosto che allo scopo): la gestione degli IV può essere semplificata soprattutto durante la decrittazione: Java rende facile creare un IV direttamente da un array di byte esistente dopotutto. Lo stesso vale per la decrittazione, che non deve iniziare dall'offset 0. Tutta questa copia semplicemente non è necessaria. Inoltre, se devi inviare una lunghezza per l'IV (vero?), Allora perché non utilizzare un singolo byte (senza segno) - non supererai i 255 byte per l'IV, giusto?
Maarten Bodewes

2

Versione eseguibile dell'editor online: -

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.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[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}

Fantastico, felice che abbia aiutato!
Bhupesh Pant

Una password non è una chiave, un IV non dovrebbe essere statico. Codice ancora digitato in modo stringato, che rende impossibile distruggere la chiave. Nessuna indicazione su cosa fare con la flebo, né alcuna idea che dovrebbe essere imprevedibile.
Maarten Bodewes

1

Spesso è una buona idea fare affidamento sulla soluzione fornita dalla libreria standard:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

Questo stampa "Testo da codificare".

La soluzione si basa sulla guida di riferimento dell'architettura di crittografia Java e sulla risposta https://stackoverflow.com/a/20591539/146745 .


5
Non utilizzare mai la modalità ECB. Periodo.
Konstantino Sparakis

1
ECB non dovrebbe essere utilizzato se si crittografa più di un blocco di dati con la stessa chiave, quindi per il "Testo da codificare" è abbastanza buono. stackoverflow.com/a/1220869/146745
Andrej

La chiave @AndroidDev viene generata nella sezione di preparazione della chiave: aesKey = keygen.generateKey ()
andrej

1

Questo è un miglioramento rispetto alla risposta accettata.

I cambiamenti:

(1) Usando IV casuale e anteponendolo al testo crittografato

(2) Utilizzo di SHA-256 per generare una chiave da una passphrase

(3) Nessuna dipendenza da Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}

Un hash non è ancora una funzione di generazione di chiavi basata su password / PBKDF. O si utilizza una chiave casuale o si utilizza un PBKDF come PBKDF2 / Crittografia basata su password.
Maarten Bodewes

@MaartenBodewes Puoi suggerire un miglioramento?
wvdz

PBKDF2 è presente in Java, quindi penso di averne appena suggerito uno. OK, non ne ho codificato uno, ma a mio parere è un po 'troppo. Ci sono molti esempi di crittografia basata su password.
Maarten Bodewes

@MaartenBodewes Ho pensato che potesse essere una soluzione semplice. Per curiosità, quali sarebbero le vulnerabilità specifiche quando si utilizza questo codice così com'è?
wvdz

0

Un'altra soluzione che utilizza java.util.Base64 con Spring Boot

Classe Encryptor

package com.jmendoza.springboot.crypto.cipher;

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

Classe EncryptorController

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

Esempio

http: // localhost: 8082 / cifratura / cifrare / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cifratura / decifrare / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza


-1

Versione ottimizzata della risposta accettata.

  • nessuna libreria di terze parti

  • include IV nel messaggio crittografato (può essere pubblico)

  • la password può essere di qualsiasi lunghezza

Codice:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

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

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Uso:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Output di esempio:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

La tua funzione di derivazione della password non è sicura. Non mi aspetto e.printStackTrace()nel cosiddetto codice ottimizzato.
Maarten Bodewes
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.