Crittografia basata su password AES a 256 bit Java


390

Devo implementare la crittografia AES a 256 bit, ma tutti gli esempi che ho trovato online usano un "KeyGenerator" per generare una chiave a 256 bit, ma vorrei usare la mia passkey. Come posso creare la mia chiave? Ho provato a riempirlo a 256 bit, ma poi ricevo un errore che dice che la chiave è troppo lunga. Ho installato la patch di giurisdizione illimitata, quindi non è questo il problema :)

Vale a dire. KeyGenerator si presenta così ...

// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available

// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

Codice preso da qui

MODIFICARE

In realtà stavo riempiendo la password di 256 byte, non bit, che è troppo lungo. Quello che segue è un po 'di codice che sto usando ora che ho più esperienza con questo.

byte[] key = null; // TODO
byte[] input = null; // TODO
byte[] output = null;
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(input)

I bit "TODO" che devi fare tu stesso :-)


Potresti chiarire: chiamare kgen.init (256) funziona?
Mitch Wheat,

2
Sì, ma questo genera automaticamente una chiave ... ma poiché voglio crittografare i dati tra due posizioni, devo conoscere la chiave in anticipo, quindi devo specificarne una invece di "generare" una. Posso specificare uno a 16 bit che funziona per la crittografia a 128 bit che funziona. Ho provato uno a 32 bit per la crittografia a 256 bit, ma non ha funzionato come previsto.
Nippysaurus,

4
Se ho capito bene, stai cercando di utilizzare una chiave pre-organizzata a 256 bit, specificata, ad esempio, come matrice di byte. In tal caso, l'approccio di DarkSquid con SecretKeySpec dovrebbe funzionare. È anche possibile derivare una chiave AES da una password; se è quello che stai cercando, fammi sapere, e ti mostrerò il modo corretto di farlo; semplicemente l'hashing di una password non è la migliore pratica.
Erickson,

Fai attenzione a inserire un numero, potresti rendere l'AES meno sicuro.
Giosuè,

1
@erickson: è esattamente quello che devo fare (derivare una chiave AES da una password).
Nippysaurus,

Risposte:


476

Condividi i caratteri password(a char[]) e salt(a byte[]—8 byte selezionati da a SecureRandomrende un buon sale — che non ha bisogno di essere tenuto segreto) con il destinatario fuori banda. Quindi per ottenere una buona chiave da queste informazioni:

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

I numeri magici (che potrebbero essere definiti come costanti da qualche parte) 65536 e 256 sono rispettivamente il conteggio dell'iterazione di derivazione della chiave e la dimensione della chiave.

La funzione di derivazione chiave viene ripetuta per richiedere un notevole sforzo computazionale e ciò impedisce agli aggressori di provare rapidamente molte password diverse. Il conteggio delle iterazioni può essere modificato in base alle risorse di elaborazione disponibili.

La dimensione della chiave può essere ridotta a 128 bit, che è ancora considerata una crittografia "forte", ma non dà molto margine di sicurezza se vengono scoperti attacchi che indeboliscono AES.

Utilizzata con una modalità di concatenamento di blocchi appropriata, la stessa chiave derivata può essere utilizzata per crittografare molti messaggi. In Cipher Block Chaining (CBC) , viene generato un vettore di inizializzazione casuale (IV) per ciascun messaggio, producendo un testo di cifratura diverso anche se il testo normale è identico. CBC potrebbe non essere la modalità più sicura disponibile (vedere AEAD di seguito); ci sono molte altre modalità con proprietà di sicurezza diverse, ma tutte usano un input casuale simile. In ogni caso, gli output di ciascuna operazione di crittografia sono il testo della cifra e il vettore di inizializzazione:

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));

Conservare il ciphertexte il iv. Al momento della decodifica, SecretKeyviene rigenerato esattamente allo stesso modo, usando la password con gli stessi parametri salt e iterazione. Inizializza la cifra con questa chiave e il vettore di inizializzazione memorizzato con il messaggio:

/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);

Java 7 includeva il supporto API per le modalità di cifratura AEAD e il provider "SunJCE" incluso nelle distribuzioni OpenJDK e Oracle implementa queste a partire da Java 8. Una di queste modalità è fortemente consigliata al posto di CBC; proteggerà l'integrità dei dati e la loro privacy.


A java.security.InvalidKeyExceptioncon il messaggio "Dimensione chiave illegale o parametri predefiniti" significa che la forza della crittografia è limitata; i file delle politiche sulla giurisdizione illimitata non si trovano nella posizione corretta. In un JDK, dovrebbero essere posizionati sotto${jdk}/jre/lib/security

In base alla descrizione del problema, sembra che i file delle politiche non siano installati correttamente. I sistemi possono facilmente avere più runtime Java; ricontrollare per assicurarsi che venga utilizzata la posizione corretta.


29
@Nick: leggi PKCS # 5. I sali sono necessari per PBKDF2, motivo per cui l'API per la crittografia basata su password li richiede come input per la derivazione della chiave. Senza sali, è possibile utilizzare un attacco del dizionario, consentendo un elenco precalcolato delle chiavi di crittografia simmetriche più probabili. I cifrari IV e i sali di derivazione chiave hanno scopi diversi. I IV consentono di riutilizzare la stessa chiave per più messaggi. I sali impediscono gli attacchi del dizionario sulla chiave.
Erickson,

2
Innanzitutto, sarebbe la crittografia DES, non AES. La maggior parte dei provider non ha un buon supporto per gli PBEwith<prf>and<encryption>algoritmi; ad esempio, SunJCE non fornisce e PBE per AES. In secondo luogo, abilitare jasypt non è un obiettivo. Un pacchetto che pretende di offrire sicurezza senza richiedere una comprensione dei principi sottostanti sembra pericoloso prima facie.
Erickson,

6
Ho implementato la risposta di @erickson come classe: github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE fa il lavoro, PBEStorage è un oggetto di valore per la memorizzazione del IV / testo cifrato insieme).
Steve Clay,

3
@AndyNuss Questo esempio è per la crittografia reversibile, che generalmente non dovrebbe essere usata per le password. È possibile utilizzare la derivazione della chiave PBKDF2 per "hash" password in modo sicuro. Ciò significa che nell'esempio sopra, memorizzeresti il ​​risultato tmp.getEncoded()come hash. È inoltre necessario memorizzare le saltiterazioni e (65536 in questo esempio) in modo da poter ricalcolare l'hash quando qualcuno tenta di autenticarsi. In questo caso, genera il sale con un generatore di numeri casuali crittografico ogni volta che la password viene modificata.
Erickson,

6
Per eseguire questo codice, assicurati di avere i file della politica di giurisdizione di forza illimitata giusti nel tuo JRE come indicato in ngs.ac.uk/tools/jcepolicyfiles
Amir Moghimi

75

Prendi in considerazione l'utilizzo del modulo di crittografia Spring Security

Il modulo Spring Security Crypto fornisce supporto per la crittografia simmetrica, la generazione di chiavi e la codifica password. Il codice è distribuito come parte del modulo principale ma non ha dipendenze da nessun altro codice Spring Security (o Spring).

Fornisce una semplice astrazione per la crittografia e sembra corrispondere a ciò che è richiesto qui,

Il metodo di crittografia "standard" è AES a 256 bit utilizzando PBKDF2 di PKCS # 5 (funzione di derivazione della chiave basata su password n. 2). Questo metodo richiede Java 6. La password utilizzata per generare SecretKey deve essere conservata in un luogo sicuro e non condivisa. Il salt viene usato per prevenire attacchi da dizionario contro la chiave nel caso in cui i tuoi dati crittografati vengano compromessi. Viene inoltre applicato un vettore di inizializzazione casuale a 16 byte, quindi ogni messaggio crittografato è univoco.

Uno sguardo agli interni rivela una struttura simile alla risposta di Erickson .

Come notato nella domanda, ciò richiede anche la politica di giurisdizione illimitata di JCR (Java Cryptography Extension) (altrimenti incontrerai InvalidKeyException: Illegal Key Size). È scaricabile per Java 6 , Java 7 e Java 8 .

Esempio di utilizzo

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class CryptoExample {
    public static void main(String[] args) {
        final String password = "I AM SHERLOCKED";  
        final String salt = KeyGenerators.string().generateKey();

        TextEncryptor encryptor = Encryptors.text(password, salt);      
        System.out.println("Salt: \"" + salt + "\"");

        String textToEncrypt = "*royal secrets*";
        System.out.println("Original text: \"" + textToEncrypt + "\"");

        String encryptedText = encryptor.encrypt(textToEncrypt);
        System.out.println("Encrypted text: \"" + encryptedText + "\"");

        // Could reuse encryptor but wanted to show reconstructing TextEncryptor
        TextEncryptor decryptor = Encryptors.text(password, salt);
        String decryptedText = decryptor.decrypt(encryptedText);
        System.out.println("Decrypted text: \"" + decryptedText + "\"");

        if(textToEncrypt.equals(decryptedText)) {
            System.out.println("Success: decrypted text matches");
        } else {
            System.out.println("Failed: decrypted text does not match");
        }       
    }
}

E output di esempio,

Sale: "feacbc02a3a697b0"
Testo originale: "* segreti reali *"
Testo crittografato: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Testo decifrato: "* segreti reali *"
Operazione riuscita: corrispondenze di testo decrittografate

Puoi usare quel modulo senza caricare tutta la primavera? Sembra che non abbiano reso disponibili per il download i file jar.
theglauber,

5
@theglauber Sì, è possibile utilizzare il modulo senza Spring Security o il framework Spring. Dal punto di vista del pom , l'unica dipendenza di runtime è la registrazione comune di Apache 1.1.1 . Puoi tirare il vaso con Maven o scaricarlo direttamente dal repository binario ufficiale (vedi Download dei binari di Spring 4 per maggiori informazioni sui binari di Spring).
John McCarthy,

1
È possibile impostare la lunghezza della chiave su 128 bit? La modifica della cartella di sicurezza in ogni PC non è un'opzione per me.
IvanRF,

1
@IvanRF scusa, non sembra. 256 è hard coded nella fonte
John McCarthy,

2
L' NULL_IV_GENERATORutilità utilizzata dall'utilità Spring non è sicura. Se l'applicazione non fornisce un IV, lasciare che il provider lo scelga e interrogarlo dopo l'inizializzazione.
Erickson,

32

Dopo aver letto i suggerimenti di Erickson e aver raccolto ciò che potevo da un paio di altri post e questo esempio qui , ho tentato di aggiornare il codice di Doug con le modifiche consigliate. Sentiti libero di modificare per renderlo migliore.

  • Il vettore di inizializzazione non è più corretto
  • la chiave di crittografia viene derivata utilizzando il codice di erickson
  • Il salt 8 byte viene generato in setupEncrypt () usando SecureRandom ()
  • la chiave di decrittazione viene generata dal sale di crittografia e dalla password
  • la cifra di decrittazione viene generata dalla chiave di decrittazione e dal vettore di inizializzazione
  • rimosso hex twiddling al posto del codec org.apache.commons routine esadecimali

Alcune note: utilizza una chiave di crittografia a 128 bit: java apparentemente non eseguirà la crittografia a 256 bit immediatamente. L'implementazione di 256 richiede l'installazione di alcuni file extra nella directory di installazione di Java.

Inoltre, non sono una persona crittografica. Badate.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Crypto
{
    String mPassword = null;
    public final static int SALT_LEN = 8;
    byte [] mInitVec = null;
    byte [] mSalt = null;
    Cipher mEcipher = null;
    Cipher mDecipher = null;
    private final int KEYLEN_BITS = 128; // see notes below where this is used.
    private final int ITERATIONS = 65536;
    private final int MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet 
     * @param password
     */
    public Crypto (String password)
    {
        mPassword = password;
    }

    /**
     * return the generated salt for this object
     * @return
     */
    public byte [] getSalt ()
    {
        return (mSalt);
    }

    /**
     * return the initialization vector created from setupEncryption
     * @return
     */
    public byte [] getInitVec ()
    {
        return (mInitVec);
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db (String msg)
    {
        System.out.println ("** Crypt ** " + msg);
    }

    /**
     * this must be called after creating the initial Crypto object. It creates a salt of SALT_LEN bytes
     * and generates the salt bytes using secureRandom().  The encryption secret key is created 
     * along with the initialization vectory. The member variable mEcipher is created to be used
     * by the class later on when either creating a CipherOutputStream, or encrypting a buffer
     * to be written to disk.
     *  
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidParameterSpecException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     */
    public void setupEncrypt () throws NoSuchAlgorithmException, 
                                                           InvalidKeySpecException, 
                                                           NoSuchPaddingException, 
                                                           InvalidParameterSpecException, 
                                                           IllegalBlockSizeException, 
                                                           BadPaddingException, 
                                                           UnsupportedEncodingException, 
                                                           InvalidKeyException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;

        // crate secureRandom salt and store  as member var for later use
         mSalt = new byte [SALT_LEN];
        SecureRandom rnd = new SecureRandom ();
        rnd.nextBytes (mSalt);
        Db ("generated salt :" + Hex.encodeHexString (mSalt));

        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        /* Derive the key, given password and salt. 
         * 
         * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
         * The end user must also install them (not compiled in) so beware. 
         * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
         */
        KeySpec spec = new PBEKeySpec (mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);
        tmp = factory.generateSecret (spec);
        SecretKey secret = new SecretKeySpec (tmp.getEncoded(), "AES");

        /* Create the Encryption cipher object and store as a member variable
         */
        mEcipher = Cipher.getInstance ("AES/CBC/PKCS5Padding");
        mEcipher.init (Cipher.ENCRYPT_MODE, secret);
        AlgorithmParameters params = mEcipher.getParameters ();

        // get the initialization vectory and store as member var 
        mInitVec = params.getParameterSpec (IvParameterSpec.class).getIV();

        Db ("mInitVec is :" + Hex.encodeHexString (mInitVec));
    }



    /**
     * If a file is being decrypted, we need to know the pasword, the salt and the initialization vector (iv). 
     * We have the password from initializing the class. pass the iv and salt here which is
     * obtained when encrypting the file initially.
     *   
     * @param initvec
     * @param salt
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws DecoderException
     */
    public void setupDecrypt (String initvec, String salt) throws NoSuchAlgorithmException, 
                                                                                       InvalidKeySpecException, 
                                                                                       NoSuchPaddingException, 
                                                                                       InvalidKeyException, 
                                                                                       InvalidAlgorithmParameterException, 
                                                                                       DecoderException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;
        SecretKey secret = null;

        // since we pass it as a string of input, convert to a actual byte buffer here
        mSalt = Hex.decodeHex (salt.toCharArray ());
       Db ("got salt " + Hex.encodeHexString (mSalt));

        // get initialization vector from passed string
        mInitVec = Hex.decodeHex (initvec.toCharArray ());
        Db ("got initvector :" + Hex.encodeHexString (mInitVec));


        /* Derive the key, given password and salt. */
        // in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
        // The end user must also install them (not compiled in) so beware. 
        // see here: 
      // http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);

        tmp = factory.generateSecret(spec);
        secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        /* Decrypt the message, given derived key and initialization vector. */
        mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));
    }


    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     * 
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that. 
     *  
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile (File input, File output) throws 
                                                                                          IOException, 
                                                                                          IllegalBlockSizeException, 
                                                                                          BadPaddingException
    {
        FileInputStream fin;
        FileOutputStream fout;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        while ((nread = fin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            // and results in full blocks of MAX_FILE_BUF being written. 
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // encrypt the buffer using the cipher obtained previosly
            byte [] tmp = mEcipher.update (trimbuf);

            // I don't think this should happen, but just in case..
            if (tmp != null)
                fout.write (tmp);
        }

        // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
        byte [] finalbuf = mEcipher.doFinal ();
        if (finalbuf != null)
            fout.write (finalbuf);

        fout.flush();
        fin.close();
        fout.close();

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     * 
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *  
     * @param input - File object representing encrypted data on disk 
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile (File input, File output) throws 
                                                                                                                                            IllegalBlockSizeException, 
                                                                                                                                            BadPaddingException, 
                                                                                                                                            IOException
    {
        FileInputStream fin; 
        FileOutputStream fout;
        CipherInputStream cin;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
        cin = new CipherInputStream (fin, mDecipher);

        while ((nread = cin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // write out the size-adjusted buffer
            fout.write (trimbuf);
        }

        fout.flush();
        cin.close();
        fin.close ();       
        fout.close();   

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String [] args)
    {

        // create the input.txt file in the current directory before continuing
        File input = new File ("input.txt");
        File eoutput = new File ("encrypted.aes");
        File doutput = new File ("decrypted.txt");
        String iv = null;
        String salt = null;
        Crypto en = new Crypto ("mypassword");

        /*
         * setup encryption cipher using password. print out iv and salt
         */
        try
      {
          en.setupEncrypt ();
          iv = Hex.encodeHexString (en.getInitVec ()).toUpperCase ();
          salt = Hex.encodeHexString (en.getSalt ()).toUpperCase ();
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidParameterSpecException e)
      {
          e.printStackTrace();
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (UnsupportedEncodingException e)
      {
          e.printStackTrace();
      }

        /*
         * write out encrypted file
         */
        try
      {
          en.WriteEncryptedFile (input, eoutput);
          System.out.printf ("File encrypted to " + eoutput.getName () + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }


        /*
         * decrypt file
         */
        Crypto dc = new Crypto ("mypassword");
        try
      {
          dc.setupDecrypt (iv, salt);
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidAlgorithmParameterException e)
      {
          e.printStackTrace();
      }
      catch (DecoderException e)
      {
          e.printStackTrace();
      }

        /*
         * write out decrypted file
         */
        try
      {
          dc.ReadEncryptedFile (eoutput, doutput);
          System.out.println ("decryption finished to " + doutput.getName ());
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }
   }


}

13
Questa è sostanzialmente la stessa risposta di quella di Erickson, circondata da un involucro - non ben programmato secondo me -. printStackTrace()
Maarten Bodewes,

2
@owlstead - Questa è un'ottima risposta. Mostra come crittografare un flusso crittografando il buffer di byte, invece di avere tutto in memoria. La risposta di Erickson non funzionerà per file di grandi dimensioni, che non rientrano nella memoria. Quindi +1 a wufoo. :)
dynamokaj,

2
@dynamokaj L'uso di CipherInputStreame CipherOutputStreamnon è un grosso problema. Mischiare tutte le eccezioni sotto la tabella è un problema. Il fatto che il sale improvvisamente sia diventato un campo e che sia richiesto il IV è un problema. Il fatto che non segua le convenzioni di codifica Java è un problema. E il fatto che questo funzioni solo su file mentre non è stato richiesto è un problema. E che il resto del codice sia sostanzialmente una copia non aiuta neanche. Ma forse lo modificherò per renderlo migliore, come suggerito ...
Maarten Bodewes,

@owlstead Sono d'accordo sul fatto che la codifica avrebbe potuto avere un aspetto migliore, l'ho ridotta a 1/4 o qualcosa del genere, ma mi piace che mi abbia fatto conoscere CipherInputStream e CipherOutputStream, dato che era proprio quello di cui avevo bisogno ieri! ;)
dynamokaj,

perché due volte? fout.close (); fout.close ();
Marian Paździoch,

7

Generare la tua chiave da un array di byte è semplice:

byte[] raw = ...; // 32 bytes in size for a 256 bit key
Key skey = new javax.crypto.spec.SecretKeySpec(raw, "AES");

Ma la creazione di una chiave a 256 bit non è sufficiente. Se il generatore di chiavi non è in grado di generare chiavi a 256 bit, la Cipherclasse probabilmente non supporta neanche AES a 256 bit. Dici di aver installato la patch di giurisdizione illimitata, quindi il codice AES-256 dovrebbe essere supportato (ma anche le chiavi a 256 bit dovrebbero essere, quindi questo potrebbe essere un problema di configurazione).

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted = cipher.doFinal(plainText.getBytes());

Una soluzione alternativa alla mancanza del supporto AES-256 consiste nel prendere alcune implementazioni liberamente disponibili di AES-256 e utilizzarlo come provider personalizzato. Ciò comporta la creazione della propria Providersottoclasse e l'utilizzo con Cipher.getInstance(String, Provider). Ma questo può essere un processo coinvolto.


5
Dovresti sempre indicare la modalità e l'algoritmo di riempimento. Java utilizza la modalità ECB non sicura per impostazione predefinita.
Maarten Bodewes,

Non puoi creare il tuo provider, i provider devono essere firmati (non riesco a credere che abbia letto inizialmente questo errore). Anche se tu potessi, la limitazione della dimensione della chiave sta nell'implementazione Cipher, non nel provider stesso. È possibile utilizzare AES-256 in Java 8 e versioni precedenti, ma è necessario utilizzare un'API proprietaria. O un runtime che non pone restrizioni sulla dimensione della chiave ovviamente.
Maarten Bodewes,

Le versioni recenti di OpenJDK (e Android) non hanno restrizioni sull'aggiunta del proprio provider di sicurezza / crittografia. Ma lo fai a tuo rischio e pericolo, ovviamente. Se dimentichi di mantenere aggiornate le tue librerie potresti esporsi a rischi per la sicurezza.
Maarten Bodewes,

1
@ MaartenBodewes + OpenJDK non ha mai avuto il problema della "politica crittografica limitata" in primo luogo, e Oracle JDK lo ha rimosso più di un anno fa per 8u161 e 9 (e forse alcune versioni più basse ora solo pagate ma non le ho controllate)
dave_thompson_085,

6

Quello che ho fatto in passato è l'hash della chiave tramite qualcosa come SHA256, quindi estrarre i byte dall'hash nel byte chiave [].

Dopo aver ottenuto il tuo byte [] puoi semplicemente fare:

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());

12
Per altri: questo non è un metodo molto sicuro. È necessario utilizzare PBKDF 2 specificato in PKCS # 5. erickson ha detto come fare sopra. Il metodo di DarkSquid è vulnerabile agli attacchi con password e inoltre non funziona a meno che la dimensione del tuo testo in chiaro sia un multiplo della dimensione del blocco di AES (128 bit) perché ha tralasciato il riempimento. Inoltre non specifica la modalità; leggi le modalità operative di Block Cipher di Wikipedia per dubbi.
Capanna8

1
@DarkSquid Cipher aes256 = Cipher.getInstance("AES/OFB/NoPadding"); MessageDigest keyDigest = MessageDigest.getInstance("SHA-256"); byte[] keyHash = keyDigest.digest(secret.getBytes("UTF-8")); SecretKeySpec key = new SecretKeySpec(keyHash, "AES"); aes256.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initializationVector)); Sto anche facendo lo stesso come suggerito nella tua risposta, ma finisco ancora con questo java.security.InvalidKeyException: dimensione della chiave illegale È obbligatorio il download del file di criteri JCE?
Niranjan Subramanian

2
NON USARE questo metodo in nessun tipo di ambiente di produzione. Quando si inizia con la crittografia basata su password, molti utenti vengono sopraffatti da muri di codice e non capiscono come funzionano gli attacchi del dizionario e altri semplici hack. Mentre può essere frustrante da imparare, è un investimento utile per ricercare questo. Ecco un buon articolo per principianti: adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante

1

Aggiungendo alle modifiche di @ Wufoo, la seguente versione utilizza InputStreams anziché i file per semplificare il lavoro con una varietà di file. Memorizza anche IV e Salt all'inizio del file, rendendolo quindi solo la password deve essere tracciata. Poiché IV e Salt non hanno bisogno di essere segreti, questo rende la vita un po 'più semplice.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    public final static int SALT_LEN     = 8;
    static final String     HEXES        = "0123456789ABCDEF";
    String                  mPassword    = null;
    byte[]                  mInitVec     = null;
    byte[]                  mSalt        = new byte[SALT_LEN];
    Cipher                  mEcipher     = null;
    Cipher                  mDecipher    = null;
    private final int       KEYLEN_BITS  = 128;    // see notes below where this is used.
    private final int       ITERATIONS   = 65536;
    private final int       MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet
     * @param password
     */
    public AES(String password) {
        mPassword = password;
    }

    public static String byteToHex(byte[] raw) {
        if (raw == null) {
            return null;
        }

        final StringBuilder hex = new StringBuilder(2 * raw.length);

        for (final byte b : raw) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    public static byte[] hexToByte(String hexString) {
        int    len = hexString.length();
        byte[] ba  = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            ba[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                                + Character.digit(hexString.charAt(i + 1), 16));
        }

        return ba;
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db(String msg) {
        System.out.println("** Crypt ** " + msg);
    }

    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     *
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that.
     *
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IOException, IllegalBlockSizeException, BadPaddingException {
        try {
            long             totalread = 0;
            int              nread     = 0;
            byte[]           inbuf     = new byte[MAX_FILE_BUF];
            SecretKeyFactory factory   = null;
            SecretKey        tmp       = null;

            // crate secureRandom salt and store  as member var for later use
            mSalt = new byte[SALT_LEN];

            SecureRandom rnd = new SecureRandom();

            rnd.nextBytes(mSalt);
            Db("generated salt :" + byteToHex(mSalt));
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            /*
             *  Derive the key, given password and salt.
             *
             * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
             * The end user must also install them (not compiled in) so beware.
             * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
             */
            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp = factory.generateSecret(spec);

            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /*
             *  Create the Encryption cipher object and store as a member variable
             */
            mEcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            mEcipher.init(Cipher.ENCRYPT_MODE, secret);

            AlgorithmParameters params = mEcipher.getParameters();

            // get the initialization vectory and store as member var
            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();
            Db("mInitVec is :" + byteToHex(mInitVec));
            outputStream.write(mSalt);
            outputStream.write(mInitVec);

            while ((nread = inputStream.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                // and results in full blocks of MAX_FILE_BUF being written.
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // encrypt the buffer using the cipher obtained previosly
                byte[] tmpBuf = mEcipher.update(trimbuf);

                // I don't think this should happen, but just in case..
                if (tmpBuf != null) {
                    outputStream.write(tmpBuf);
                }
            }

            // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
            byte[] finalbuf = mEcipher.doFinal();

            if (finalbuf != null) {
                outputStream.write(finalbuf);
            }

            outputStream.flush();
            inputStream.close();
            outputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (InvalidKeyException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidParameterSpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     *
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *
     * @param input - File object representing encrypted data on disk
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        try {
            CipherInputStream cin;
            long              totalread = 0;
            int               nread     = 0;
            byte[]            inbuf     = new byte[MAX_FILE_BUF];

            // Read the Salt
            inputStream.read(this.mSalt);
            Db("generated salt :" + byteToHex(mSalt));

            SecretKeyFactory factory = null;
            SecretKey        tmp     = null;
            SecretKey        secret  = null;

            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp    = factory.generateSecret(spec);
            secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /* Decrypt the message, given derived key and initialization vector. */
            mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            // Set the appropriate size for mInitVec by Generating a New One
            AlgorithmParameters params = mDecipher.getParameters();

            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();

            // Read the old IV from the file to mInitVec now that size is set.
            inputStream.read(this.mInitVec);
            Db("mInitVec is :" + byteToHex(mInitVec));
            mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));

            // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
            cin = new CipherInputStream(inputStream, mDecipher);

            while ((nread = cin.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // write out the size-adjusted buffer
                outputStream.write(trimbuf);
            }

            outputStream.flush();
            cin.close();
            inputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (Exception ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String[] args) {

        // create the input.txt file in the current directory before continuing
        File   input   = new File("input.txt");
        File   eoutput = new File("encrypted.aes");
        File   doutput = new File("decrypted.txt");
        String iv      = null;
        String salt    = null;
        AES    en      = new AES("mypassword");

        /*
         * write out encrypted file
         */
        try {
            en.WriteEncryptedFile(new FileInputStream(input), new FileOutputStream(eoutput));
            System.out.printf("File encrypted to " + eoutput.getName() + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }

        /*
         * decrypt file
         */
        AES dc = new AES("mypassword");

        /*
         * write out decrypted file
         */
        try {
            dc.ReadEncryptedFile(new FileInputStream(eoutput), new FileOutputStream(doutput));
            System.out.println("decryption finished to " + doutput.getName());
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }
    }
}

1
Questa soluzione sembra usare una gestione del buffer scomoda e una gestione delle eccezioni assolutamente al di sotto della media, fondamentalmente registrandole e poi dimenticandole. Si noti che l'utilizzo di CBC è corretto per i file ma non per la sicurezza del trasporto. L'uso di PBKDF2 e AES può ovviamente essere difeso, in tal senso può essere una buona base per una soluzione.
Maarten Bodewes,

1

(Forse utile per gli altri con un requisito simile)

Avevo un requisito simile per usare AES-256-CBCencrypt e decrypt in Java.

Per ottenere (o specificare) la crittografia / decrittografia a 256 byte, il Java Cryptography Extension (JCE)criterio deve essere impostato su"Unlimited"

Può essere impostato nel java.securityfile in $JAVA_HOME/jre/lib/security(per JDK) o $JAVA_HOME/lib/security(per JRE)

crypto.policy=unlimited

O nel codice come

Security.setProperty("crypto.policy", "unlimited");

Java 9 e versioni successive lo hanno abilitato di default.


0

Prendi in considerazione l'utilizzo di Encryptor4j di cui sono l'autore.

Prima di tutto assicurati di aver installato i file dei criteri di giurisdizione di forza illimitata in modo da poter utilizzare le chiavi AES a 256 bit.

Quindi procedi come segue:

String password = "mysupersecretpassword"; 
Key key = KeyFactory.AES.keyFromPassword(password.toCharArray());
Encryptor encryptor = new Encryptor(key, "AES/CBC/PKCS7Padding", 16);

Ora puoi usare il criptatore per crittografare il tuo messaggio. Puoi anche eseguire la crittografia di streaming, se lo desideri. Genera e antepone automaticamente un IV sicuro per la tua comodità.

Se è un file che desideri comprimere dai un'occhiata a questa risposta Crittografia di un file di grandi dimensioni con AES utilizzando JAVA per un approccio ancora più semplice.


2
Ciao Martin, dovresti sempre indicare che sei lo scrittore della biblioteca se vuoi segnalarlo. Ci sono un sacco di involucri crittografici che cercano di rendere le cose facili. Questo ha un documento di sicurezza o ha ricevuto recensioni per renderlo utile?
Maarten Bodewes,

-1

Utilizzare questa classe per la crittografia. Funziona.

public class ObjectCrypter {


    public static byte[] encrypt(byte[] ivBytes, byte[] keyBytes, byte[] mes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = null;
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(mes);

    }

    public static byte[] decrypt(byte[] ivBytes, byte[] keyBytes, byte[] bytes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException, ClassNotFoundException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(bytes);

    }
}

E questi sono ivBytes e una chiave casuale;

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";

byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
keyBytes = key.getBytes("UTF-8");

10
"funziona" .... sì, ma non soddisfa i requisiti per la creazione di una soluzione crittograficamente sicura (né soddisfa gli standard di codifica Java per quanto riguarda la gestione delle eccezioni, a mio avviso).
Maarten Bodewes,

2
IV è inizializzato a zero. Cerca attacchi BEAST e ACPA.
Michele Giuseppe Fadda,

Eccezioni al wazoo, al metodo di generazione della chiave "random" e a zero IV è un problema con questa implementazione, ma questi problemi sono banali da risolvere. +1.
Phil
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.