Criptografia baseada em senha AES Java de 256 bits

390

Preciso implementar a criptografia AES de 256 bits, mas todos os exemplos que encontrei on-line usam um "KeyGenerator" para gerar uma chave de 256 bits, mas gostaria de usar minha própria senha. Como posso criar minha própria chave? Eu tentei preenchê-lo para 256 bits, mas, em seguida, recebo um erro dizendo que a chave é muito longa. Eu tenho o patch de jurisdição ilimitado instalado, então esse não é o problema :)

Ou seja. O KeyGenerator se parece com isso ...

// 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();

Código retirado daqui

EDITAR

Na verdade, eu estava preenchendo a senha com 256 bytes, não com bits, o que é muito longo. A seguir, alguns códigos que estou usando agora que tenho mais experiência com isso.

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)

Os bits "TODO" que você precisa fazer :-)

Nippysaurus
fonte
Você poderia esclarecer: chamar kgen.init (256) funciona?
Mitch Wheat
2
Sim, mas isso gera automaticamente uma chave ... mas, como quero criptografar dados entre dois lugares, preciso conhecê-la com antecedência, portanto, preciso especificar uma em vez de "gerar" uma. Eu posso especificar um de 16 bits que funciona para criptografia de 128 bits que funciona. Eu tentei um de 32 bits para criptografia de 256 bits, mas não funcionou como esperado.
Nippysaurus
4
Se bem entendi, você está tentando usar uma chave de 256 bits pré-organizada, especificada, por exemplo, como uma matriz de bytes. Nesse caso, a abordagem do DarkSquid usando o SecretKeySpec deve funcionar. Também é possível derivar uma chave AES de uma senha; se é isso que você procura, informe-me e mostrarei a maneira correta de fazer isso; simplesmente fazer o hash de uma senha não é a melhor prática.
22610
Cuidado ao preencher um número, pois você pode tornar seu AES menos seguro.
Joshua
11
@ erickson: é exatamente o que eu preciso fazer (derivar uma chave AES de uma senha).
Nippysaurus

Respostas:

476

Compartilhe os password(a char[]) e salt(a - byte[]8 bytes selecionados por um SecureRandomfaz um bom sal - que não precisa ser mantido em segredo) com o destinatário fora da banda. Em seguida, para obter uma boa chave dessas informações:

/* 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");

Os números mágicos (que podem ser definidos como constantes em algum lugar) 65536 e 256 são a contagem de iterações de derivação de chave e o tamanho da chave, respectivamente.

A função de derivação de chave é iterada para exigir um esforço computacional significativo e impede que os invasores tentem rapidamente muitas senhas diferentes. A contagem de iterações pode ser alterada dependendo dos recursos de computação disponíveis.

O tamanho da chave pode ser reduzido para 128 bits, o que ainda é considerado criptografia "forte", mas não oferece grande margem de segurança se forem descobertos ataques que enfraquecem o AES.

Usado com um modo de encadeamento de blocos adequado, a mesma chave derivada pode ser usada para criptografar muitas mensagens. No Cipher Block Chaining (CBC) , um vetor de inicialização aleatória (IV) é gerado para cada mensagem, produzindo texto cifrado diferente, mesmo que o texto sem formatação seja idêntico. O CBC pode não ser o modo mais seguro disponível para você (consulte AEAD abaixo); existem muitos outros modos com diferentes propriedades de segurança, mas todos eles usam uma entrada aleatória semelhante. De qualquer forma, as saídas de cada operação de criptografia são o texto cifrado e o vetor de inicialização:

/* 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"));

Armazene o ciphertexte o iv. Na descriptografia, o SecretKeyé regenerado exatamente da mesma maneira, usando a senha com os mesmos parâmetros de sal e iteração. Inicialize a cifra com essa chave e o vetor de inicialização armazenado com a mensagem:

/* 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);

O Java 7 incluiu suporte à API para modos de cifra AEAD , e o provedor "SunJCE" incluído nas distribuições OpenJDK e Oracle implementa estes a partir do Java 8. Um desses modos é altamente recomendado no lugar do CBC; protegerá a integridade dos dados, bem como a sua privacidade.


A java.security.InvalidKeyExceptioncom a mensagem "Tamanho da chave ilegal ou parâmetros padrão" significa que a força da criptografia é limitada; os arquivos de política de jurisdição de força ilimitada não estão no local correto. Em um JDK, eles devem ser colocados em${jdk}/jre/lib/security

Com base na descrição do problema, parece que os arquivos de políticas não foram instalados corretamente. Os sistemas podem facilmente ter vários tempos de execução Java; verifique duas vezes para garantir que o local correto esteja sendo usado.

erickson
fonte
29
@ Nick: Leia PKCS # 5. Os sais são necessários para PBKDF2, e é por isso que a API para criptografia baseada em senha os exige como entrada para derivação de chave. Sem sais, um ataque de dicionário poderia ser usado, permitindo uma lista pré-calculada das chaves de criptografia simétrica mais prováveis. IVs cifrados e sais de derivação de chave servem a propósitos diferentes. IVs permitem reutilizar a mesma chave para várias mensagens. Os sais impedem ataques de dicionário na tecla.
22610
2
Primeiro, isso seria criptografia DES, não AES. A maioria dos provedores não tem um bom suporte para os PBEwith<prf>and<encryption>algoritmos; por exemplo, o SunJCE não fornece e PBE para AES. Segundo, ativar o jasypt não é objetivo. Um pacote que pretende oferecer segurança sem exigir a compreensão dos princípios subjacentes parece perigoso à primeira vista.
21410
6
Implementei a resposta de @ erickson como uma classe: github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (o PBE faz o trabalho, o PBEStorage é um objeto de valor para armazenar o texto cifrado / IV.)
Steve argila
3
@AndyNuss Este exemplo é para criptografia reversível, que geralmente não deve ser usada para senhas. Você pode usar a derivação de chave PBKDF2 para "hash" senhas com segurança. Isso significa que, no exemplo acima, você armazenaria o resultado tmp.getEncoded()como hash. Você também deve armazenar as saltiterações (65536 neste exemplo) para poder recalcular o hash quando alguém tentar se autenticar. Nesse caso, gere o sal com um gerador de números aleatórios criptográficos sempre que a senha for alterada.
Erickson
6
Para executar esse código, certifique-se de ter o ilimitado Jurisdição Força direito arquivos de diretiva em sua JRE como indicado na ngs.ac.uk/tools/jcepolicyfiles
Amir Moghimi
75

Considere o uso do Spring Security Crypto Module

O módulo Spring Security Crypto fornece suporte para criptografia simétrica, geração de chave e codificação de senha. O código é distribuído como parte do módulo principal, mas não depende de nenhum outro código Spring Security (ou Spring).

Ele fornece uma abstração simples para criptografia e parece corresponder ao necessário aqui,

O método de criptografia "padrão" é o AES de 256 bits usando o PBKDF2 do PKCS # 5 (função de derivação de chave com base em senha nº 2). Este método requer Java 6. A senha usada para gerar a SecretKey deve ser mantida em um local seguro e não ser compartilhada. O salt é usado para impedir ataques de dicionário contra a chave, caso seus dados criptografados sejam comprometidos. Um vetor de inicialização aleatória de 16 bytes também é aplicado para que cada mensagem criptografada seja exclusiva.

Um exame das partes internas revela uma estrutura semelhante à resposta de erickson .

Conforme observado na pergunta, isso também requer a Política de Jurisdição de Força Ilimitada da Java Cryptography Extension (JCE) (caso contrário, você encontrará InvalidKeyException: Illegal Key Size). É possível fazer o download para Java 6 , Java 7 e Java 8 .

Exemplo de uso

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 amostra de saída,

Sal: "feacbc02a3a697b0"
Texto original: "* segredos reais *"
Texto criptografado: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Texto decodificado: "* segredos reais *"
Sucesso: correspondências de texto descriptografadas
John McCarthy
fonte
Você pode usar esse módulo sem carregar todo o Spring? Eles não parecem ter disponibilizado arquivos jar para download.
theglauber
5
@ theglauber Sim, você pode usar o módulo sem o Spring Security ou o framework Spring. Observando o pom , a única dependência de tempo de execução é o apache commons-logging 1.1.1 . Você pode puxar o pote com o maven ou fazer o download diretamente do repositório binário oficial (consulte Download de binários do Spring 4 para obter mais informações sobre os binários do Spring).
John McCarthy
11
É possível definir o comprimento da chave para 128 bits? Modificar a pasta de segurança em todos os PCs não é uma opção para mim.
21915 IvanRF
11
@IvanRF desculpe, não parece. 256 é codificado na fonte
John McCarthy
2
O NULL_IV_GENERATORusado pelo utilitário Spring não é seguro. Se o aplicativo não fornecer um IV, deixe que o provedor o escolha e consulte-o após a inicialização.
Erickson
32

Depois de ler as sugestões de erickson e recolher o que pude de algumas outras postagens e deste exemplo aqui , tentei atualizar o código de Doug com as alterações recomendadas. Sinta-se livre para editar para torná-lo melhor.

  • O vetor de inicialização não é mais corrigido
  • chave de criptografia é derivada usando o código de erickson
  • Sal de 8 bytes é gerado em setupEncrypt () usando SecureRandom ()
  • chave de descriptografia é gerada a partir do sal de criptografia e da senha
  • cifra de descriptografia é gerada a partir da chave de descriptografia e do vetor de inicialização
  • removida a rotação hexadecimal em vez do org.apache.commons codec Rotinas hexadecimais

Algumas anotações: isso usa uma chave de criptografia de 128 bits - o java aparentemente não executa criptografia de 256 bits imediatamente. A implementação do 256 requer a instalação de alguns arquivos extras no diretório de instalação do java.

Além disso, eu não sou uma pessoa de criptografia. Fique atento.

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


}
wufoo
fonte
13
Essa é basicamente a mesma resposta que a de Erickson, cercada por um invólucro - que não é tão bem programado na minha opinião. printStackTrace()
Maarten Bodewes
2
@owlstead - Esta é uma ótima resposta. Ele mostra como criptografar um fluxo criptografando o buffer de bytes, em vez de ter tudo na memória. A resposta de Erickson não funciona para arquivos grandes, que não cabem na memória. Então, +1 ao wufoo. :)
dynamokaj
2
@dynamokaj O uso CipherInputStreame CipherOutputStreamnão é um grande problema. Baralhar todas as exceções sob a tabela é um problema. O fato de que o sal se tornou repentinamente um campo e que o IV é necessário é um problema. O fato de não seguir as convenções de codificação Java é um problema. E o fato de que isso só funciona em arquivos enquanto não foi solicitado é um problema. E que o resto do código é basicamente uma cópia também não ajuda. Mas talvez eu o ajuste para torná-lo melhor, como sugerido ...
Maarten Bodewes
@owlstead Concordo que a codificação poderia ter parecido melhor. Reduzi para 1/4 ou algo assim, mas eu gosto que ele me apresentou aos CipherInputStream e CipherOutputStream, já que era exatamente o que eu precisava ontem! ;)
dynamokaj 17/11
por que duas vezes? fout.close (); fout.close ();
Marian Paździoch
7

É fácil gerar sua própria chave a partir de uma matriz de bytes:

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

Mas criar uma chave de 256 bits não é suficiente. Se o gerador de chaves não puder gerar chaves de 256 bits para você, Cipherprovavelmente a classe também não suporta o AES de 256 bits. Você diz que possui o patch de jurisdição ilimitado instalado, portanto a cifra AES-256 deve ser suportada (mas as chaves de 256 bits também devem ser, portanto, isso pode ser um problema de configuração).

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

Uma solução alternativa para a falta de suporte ao AES-256 é usar algumas implementações disponíveis gratuitamente do AES-256 e usá-lo como um provedor personalizado. Isso envolve criar sua própria Providersubclasse e usá-la com Cipher.getInstance(String, Provider). Mas isso pode ser um processo envolvido.

waqas
fonte
5
Você sempre deve indicar o modo e o algoritmo de preenchimento. Java usa o modo ECB inseguro por padrão.
Maarten Bodewes
Você não pode criar seu próprio provedor, os provedores precisam ser assinados (não acredito que eu li esse erro inicialmente). Mesmo que você pudesse, a restrição do tamanho da chave está na implementação de Cipher, não no próprio provedor. Você pode usar o AES-256 no Java 8 e inferior, mas precisa usar uma API proprietária. Ou um tempo de execução que não impõe restrições ao tamanho da chave, é claro.
Maarten Bodewes
Versões recentes do OpenJDK (e Android) não têm restrições para adicionar seu próprio provedor de segurança / criptografia. Mas você faz isso por sua conta e risco, é claro. Se você esquecer de manter suas bibliotecas atualizadas, poderá se expor a riscos de segurança.
Maarten Bodewes
11
@ MaartenBodewes + OpenJDK nunca teve o problema de 'política de criptografia limitada' em primeiro lugar, e o Oracle JDK o removeu há um ano atrás para 8u161 e 9 (e talvez algumas versões mais baixas agora só pagam, mas eu não as verifiquei)
Dave_thompson_085 19/09/19
6

O que eu fiz no passado foi fazer a hash da chave por meio de algo como SHA256 e extrair os bytes do hash para o byte da chave [].

Depois de ter seu byte [], você pode simplesmente:

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());
DarkSquid
fonte
12
Para outros: este não é um método muito seguro. Você deve usar o PBKDF 2 especificado no PKCS # 5. Erickson disse como fazer isso acima. O método do DarkSquid é vulnerável a ataques de senha e também não funciona, a menos que o tamanho do seu texto sem formatação seja um múltiplo do tamanho do bloco da AES (128 bits) porque ele deixou de fora o preenchimento. Também não especifica o modo; leia os Modos de operação de cifra em bloco da Wikipedia para obter informações.
Hut8
11
@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)); Também estou fazendo o mesmo como sugerido na sua resposta, mas ainda assim acabo com este java.security.InvalidKeyException: tamanho de chave ilegal O download do arquivo de políticas do JCE é obrigatório?
Niranjan Subramanian
2
NÃO USE este método em nenhum tipo de ambiente de produção. Ao iniciar a criptografia baseada em senha, muitos usuários ficam sobrecarregados com paredes de código e não entendem como os ataques de dicionário e outros hacks simples funcionam. Embora possa ser frustrante aprender, é um investimento válido para pesquisar isso. Aqui está um bom artigo iniciantes: adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante
1

Adicionando às edições do @ Wufoo, a versão a seguir usa InputStreams em vez de arquivos para facilitar o trabalho com uma variedade de arquivos. Ele também armazena o IV e o Salt no início do arquivo, fazendo com que apenas a senha precise ser rastreada. Como o IV e o Sal não precisam ser secretos, isso facilita a vida.

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();
        }
    }
}
Doug
fonte
11
Essa solução parece usar algum tratamento estranho de buffer e tratamento de exceção absolutamente insignificante, basicamente registrando-os e esquecendo-os. Esteja avisado de que o uso do CBC é bom para arquivos, mas não para segurança de transporte. O uso de PBKDF2 e AES pode, é claro, ser defendido, nesse sentido, pode ser uma boa base para uma solução.
Maarten Bodewes
1

(Talvez útil para outras pessoas com um requisito semelhante)

Eu tinha um requisito semelhante para usar AES-256-CBCcriptografar e descriptografar em Java.

Para alcançar (ou especificar) a criptografia / descriptografia de 256 bytes, a Java Cryptography Extension (JCE)política deve ser definida como"Unlimited"

Pode ser definido no java.securityarquivo em $JAVA_HOME/jre/lib/security(para JDK) ou $JAVA_HOME/lib/security(para JRE)

crypto.policy=unlimited

Ou no código como

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

O Java 9 e versões posteriores têm isso ativado por padrão.

Praveen
fonte
0

Considere usar o Encryptor4j, do qual sou o autor.

Primeiro, verifique se os arquivos da Política de jurisdição de força ilimitada estão instalados antes de prosseguir para poder usar as chaves AES de 256 bits.

Em seguida, faça o seguinte:

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

Agora você pode usar o criptografador para criptografar sua mensagem. Você também pode executar a criptografia de streaming, se desejar. Ele gera e anexa automaticamente um IV seguro para sua conveniência.

Se for um arquivo que você deseja compactar, dê uma olhada nesta resposta Criptografando um arquivo grande com o AES usando JAVA para uma abordagem ainda mais simples.

sobrancelha branca
fonte
2
Olá Martin, você deve sempre indicar que você é o escritor da biblioteca, se quiser apontar. Há um monte de invólucros de criptografia tentando facilitar as coisas. Este documento tem um documento de segurança ou recebeu críticas para que valha a pena?
Maarten Bodewes
-1

Use esta classe para criptografia. Funciona.

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 estes são ivBytes e uma chave aleatória;

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");
Engenheiro
fonte
10
"funciona" .... sim, mas não atende aos requisitos para a criação de uma solução criptograficamente segura (nem atende aos padrões de codificação Java com relação ao tratamento de exceções, na minha opinião).
Maarten Bodewes
2
IV é inicializado em zero. Procure por ataques BEAST e ACPA.
Michele Giuseppe Fadda
Exceções ao wazoo, o método de geração da chave "aleatória" e um IV zero são um problema com esta implementação, mas esses problemas são triviais para corrigir. +1.
Phil