Bytes iniciais incorretos após a descriptografia Java AES / CBC

116

O que há de errado com o exemplo a seguir?

O problema é que a primeira parte da string descriptografada não faz sentido. Porém, o resto está bem, eu entendo ...

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

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

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

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

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

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

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

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}
TedTrippin
fonte
48
NÃO USE QUALQUER RESPOSTA A ESTA QUESTÃO EM PROJETO SÉRIO! Todos os exemplos fornecidos nesta questão são vulneráveis ​​ao oráculo de preenchimento e, em geral, são muito mal utilizados na criptografia. Você irá introduzir uma vulnerabilidade de criptografia séria em seu projeto usando qualquer um dos trechos abaixo.
HoLyVieR
16
@HoLyVieR, Sobre as seguintes citações: "Você não deve desenvolver sua própria biblioteca de criptografia" e "usar uma API de alto nível que sua estrutura fornece." Ninguém aqui está desenvolvendo sua própria biblioteca de criptografia. Estamos simplesmente usando a API de alto nível já existente que a estrutura java fornece. O senhor é totalmente impreciso.
k170
10
@MaartenBodewes, Só porque vocês dois concordam, não significa que ambos estejam corretos. Bons desenvolvedores sabem a diferença entre envolver uma API de alto nível e reescrever uma API de baixo nível. Bons leitores notarão que o OP pediu um "exemplo simples de criptografar / descriptografar java AES" e foi exatamente isso que ele obteve . Também não concordo com as outras respostas, por isso postei uma resposta minha. Talvez vocês devessem tentar o mesmo e esclarecer a todos nós com sua experiência.
k170
6
@HoLyVieR Essa é realmente a coisa mais absurda que já li no SO! Quem é você para dizer às pessoas o que elas podem ou não desenvolver?
TedTrippin de
14
Ainda não vejo exemplos @HoLyVieR. Vamos ver alguns, ou dicas para bibliotecas? Nada construtivo.
danieljimenez,

Respostas:

245

Muitas pessoas, inclusive eu, enfrentam muitos problemas para fazer este trabalho devido à falta de algumas informações, como esquecer de converter para Base64, vetores de inicialização, conjunto de caracteres, etc. Então pensei em fazer um código totalmente funcional.

Espero que seja útil para todos vocês: Para compilar, você precisa de um jar adicional do Apache Commons Codec, que está disponível aqui: http://commons.apache.org/proper/commons-codec/download_codec.cgi

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

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

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

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

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

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

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

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

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

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

        return null;
    }

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

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}
Chand Priyankara
fonte
47
Se você não quiser depender da biblioteca Apache Commons Codec de terceiros, você pode usar o javax.xml.bind.DatatypeConverter do JDK para realizar a codificação / decodificação Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0
8
Você está usando um IV constante ?!
vianna77
36
Java 8 já tem ferramentas Base64: java.util.Base64.getDecoder () e java.util.Base64.getEncoder ()
Hristo Stoyanov
11
O IV não precisa ser secreto, mas deve ser imprevisível para o modo CBC (e exclusivo para CTR). Ele pode ser enviado junto com o texto cifrado. Uma maneira comum de fazer isso é prefixar o IV ao texto cifrado e cortá-lo antes da descriptografia. Deve ser gerado através deSecureRandom
Artjom B.
6
Uma senha não é uma chave. Um IV deve ser aleatório.
Maarten Bodewes
40

Aqui está uma solução sem Apache Commons Codec's Base64:

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

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

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

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

        return cipher.doFinal(plainText);
    }

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

        return cipher.doFinal(cipherText);
    }
}

Exemplo de uso:

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

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

Impressões:

Hello world!
դ;��LA+�ߙb*
Hello world!
BullyWiiPlaza
fonte
5
Este é um exemplo perfeitamente funcional, assim como o de @chandpriyankara. Mas por que definir uma assinatura de encrypt(String)e não encrypt(byte[] )? A criptografia (descriptografia também) é um processo baseado em bytes (AES é assim mesmo). A criptografia recebe bytes como entrada e emite bytes, assim como a descriptografia (caso em questão: o Cipherobjeto sim). Agora, um caso de uso particular pode ser ter bytes criptografados vindos de uma String, ou ser enviado como uma String (anexo MIME base64 para um Mail ...), mas isso é um problema de bytes de codificação, para os quais existem centenas de soluções totalmente alheias a AES / criptografia.
GPI
3
@GPI: Sim, mas acho mais útil Stringsporque é basicamente com isso que trabalho 95% do tempo e você acaba convertendo de qualquer maneira.
BullyWiiPlaza
9
Não, isso não é equivalente ao código de chandpriyankara! Seu código usa ECB, que geralmente é inseguro e indesejado. Deve especificar explicitamente CBC. Quando CBC for especificado, seu código será interrompido.
Dan,
Perfeitamente funcional, totalmente inseguro e usando práticas de programação muito ruins. a classe está nomeada mal. O tamanho da chave não é verificado com antecedência. Mas o mais importante, o código usa o modo inseguro de ECB, escondendo o problema na pergunta original . Por fim, ele não especifica uma codificação de caracteres, o que significa que a decodificação para texto pode falhar em outras plataformas.
Maarten Bodewes
24

Parece-me que você não está lidando corretamente com o seu vetor de inicialização (IV). Já se passou muito tempo desde a última vez que li sobre AES, IVs e encadeamento de blocos, mas sua linha

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

não parece estar bem. No caso do AES, você pode pensar no vetor de inicialização como o "estado inicial" de uma instância de cifra, e esse estado é um bit de informação que você não pode obter de sua chave, mas do cálculo real da cifra de criptografia. (Pode-se argumentar que se o IV pudesse ser extraído da chave, não teria utilidade, pois a chave já foi fornecida à instância da cifra durante sua fase de inicialização).

Portanto, você deve obter o IV como um byte [] da instância de cifra no final de sua criptografia

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

e você deve inicializar seu Cipherin DECRYPT_MODEcom este byte []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Então, sua descriptografia deve estar OK. Espero que isto ajude.

GPI
fonte
Obrigado por ajudar um novato. Eu remendei este exemplo de outros posts. Suponho que não saiba como evitar a necessidade de uma intravenosa. Eu vi, mas não tentei, outros exemplos de AES que não o usam.
TedTrippin de
Ignore isso, eu encontrei a resposta! Preciso usar AES / ECB / PKCS5Padding.
TedTrippin
20
Na maioria das vezes você não quer usar o ECB. Apenas google por quê.
João Fernandes
2
@Mushy: concordou que escolher e definir explicitamente um IV, de uma fonte aleatória confiável, é melhor do que apenas permitir que a instância Cihper escolha um. Por outro lado, essa resposta aborda a questão original de confundir o vetor de inicialização para a chave. É por isso que foi votado a favor no início. Agora, este post se tornou mais um exemplo de código a ser apontado, e as pessoas aqui deram um ótimo exemplo - logo ao lado do assunto da pergunta original.
GPI
3
@GPI votou positivamente. Os outros "grandes exemplos" não são tão bons e não abordam de forma alguma a questão. Em vez disso, parece ter sido o lugar para iniciantes copiarem cegamente amostras criptográficas sem entender que pode haver possíveis problemas de segurança - e, como sempre, existem.
Maarten Bodewes
17

O IV que você está usando para descriptografar está incorreto. Substitua este código

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

Com este código

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

E isso deve resolver seu problema.


Abaixo inclui um exemplo de uma classe AES simples em Java. Não recomendo o uso dessa classe em ambientes de produção, pois ela pode não atender a todas as necessidades específicas de seu aplicativo.

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

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

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

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

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

            cipher.init(mode, keySpec, ivSpec);

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

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

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

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

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

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

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

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

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

Observe que AES não tem nada a ver com codificação, por isso optei por tratá-lo separadamente e sem a necessidade de nenhuma biblioteca de terceiros.

k170
fonte
Em primeiro lugar, você não respondeu à pergunta original. Em segundo lugar, por que você está respondendo a uma pergunta já respondida e bem aceita? Achei que a proteção deveria parar este spam.
TedTrippin de
14
Assim como a resposta aceita, optei por responder sua pergunta via exemplo. Eu forneci um pedaço de código totalmente funcional, que mostra como lidar corretamente com o vetor de inicialização, entre outras coisas. Quanto à sua segunda pergunta, achei que uma resposta atualizada era necessária, pois o codec Apache não é mais necessário. Então não, isso não é spam. Pare de tropeçar.
k170,
7
Um IV tem um propósito específico que é randomizar o texto cifrado e fornecer segurança semântica. Se você usar o mesmo par chave + IV, os invasores poderão determinar se você enviou uma mensagem com o mesmo prefixo de antes. O IV não precisa ser secreto, mas deve ser imprevisível. Uma maneira comum é simplesmente prefixar o IV ao texto cifrado e cortá-lo antes da descriptografia.
Artjom B.
4
voto negativo: IV codificado, veja o comentário de Artjom B. acima por que isso é ruim
Murmel
1
O modo CTR deve ser emparelhado com NoPadding. O modo CTR certamente não é necessário em vez do CBC (a menos que os oráculos de preenchimento se apliquem), mas se o CTR for usado, use "/NoPadding". CTR é um modo que transforma AES em uma cifra de fluxo, e uma cifra de fluxo opera em bytes em vez de blocos.
Maarten Bodewes,
16

Nesta resposta, optei por abordar o tema principal "Exemplo de criptografia / descriptografia simples de Java AES" e não a questão de depuração específica, porque acho que isso irá beneficiar a maioria dos leitores.

Este é um resumo simples da postagem do meu blog sobre criptografia AES em Java portanto, recomendo a leitura antes de implementar qualquer coisa. No entanto, ainda fornecerei um exemplo simples para usar e darei algumas dicas sobre o que observar.

Neste exemplo, vou escolher usar criptografia autenticada com modo Galois / Contador ou modo GCM . A razão é que na maioria dos casos você deseja integridade e autenticidade combinadas com confidencialidade (leia mais no blog ).

Tutorial de criptografia / descriptografia AES-GCM

Aqui estão as etapas necessárias para criptografar / descriptografar com AES-GCM com a Java Cryptography Architecture (JCA) . Não misture com outros exemplos , pois diferenças sutis podem tornar seu código totalmente inseguro.

1. Criar chave

Como depende do seu caso de uso, assumirei o caso mais simples: uma chave secreta aleatória.

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

Importante:

2. Crie o vetor de inicialização

Um vetor de inicialização (IV) é usado para que a mesma chave secreta crie diferentes textos cifrados .

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

Importante:

3. Criptografar com IV e Chave

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

Importante:

  • usar etiqueta de autenticação de 16 bytes / 128 bits (usada para verificar integridade / autenticidade)
  • a tag de autenticação será automaticamente anexada ao texto cifrado (na implementação JCA)
  • uma vez que o GCM se comporta como uma cifra de fluxo, nenhum preenchimento é necessário
  • usar CipherInputStream ao criptografar grandes blocos de dados
  • deseja que dados adicionais (não secretos) sejam verificados caso tenham sido alterados? Você pode querer usar dados associados com cipher.updateAAD(associatedData); Mais aqui.

3. Serializar para mensagem única

Basta anexar IV e o texto cifrado. Como afirmado acima, o IV não precisa ser segredo.

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

Opcionalmente, codifique com Base64 se precisar de uma representação de string. Use a implementação integrada do Android ou do Java 8 (não use o Apache Commons Codec - é uma implementação horrível). A codificação é usada para "converter" matrizes de bytes em representação de string para torná-las ASCII seguras, por exemplo:

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

4. Prepare a descriptografia: desserialize

Se você codificou a mensagem, primeiro decodifique-a para a matriz de bytes:

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

Importante:

5. Descriptografar

Inicialize a cifra e defina os mesmos parâmetros da criptografia:

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

Importante:

  • não se esqueça de adicionar dados associados com cipher.updateAAD(associatedData);se você os adicionou durante a criptografia.

Um trecho de código funcional pode ser encontrado nesta essência.


Observe que as implementações mais recentes do Android (SDK 21+) e Java (7 +) devem ter AES-GCM. Versões mais antigas podem não ter. Eu ainda escolho este modo, pois é mais fácil de implementar além de ser mais eficiente em comparação com o modo semelhante de Encrypt-then-Mac (com por exemplo AES-CBC + HMAC ). Veja este artigo sobre como implementar AES-CBC com HMAC .

Patrick Favre
fonte
O problema é que pedir exemplos está explicitamente fora do tópico do SO. E o maior problema é que esses são trechos de código não revisados, difíceis de validar. Agradeço o esforço, mas não acho que SO deva ser o lugar para isso.
Maarten Bodewes
1
Admiro o esforço, portanto, vou apontar um único erro: "o iv deve ser imprevisível em combinação com ser único (ou seja, use iv aleatório)" - isso é verdade para o modo CBC, mas não para GCM.
Maarten Bodewes
this is true for CBC mode but not for GCMvocê quer dizer a parte inteira, ou apenas não precisa ser realmente imprevisível?
Patrick Favre
1
"Se você não entender o tópico, provavelmente não deve usar primitivos de baixo nível em primeiro lugar", claro, esse DEVERIA ser o caso, muitos desenvolvedores ainda o fazem. Não tenho certeza se abster de colocar conteúdo de alta qualidade sobre segurança / criptografia em lugares onde muitas vezes não há uma solução certa para isso. - obrigado por apontar meu erro aliás
Patrick Favre
1
OK, só porque gosto da resposta wrt contents (em vez do propósito): o tratamento do IV pode ser simplificado especialmente durante a descriptografia: afinal, o Java facilita a criação de um IV diretamente de uma matriz de bytes existente. O mesmo vale para a descriptografia, que não precisa começar no deslocamento 0. Toda essa cópia simplesmente não é necessária. Além disso, se você precisa enviar um comprimento para o IV (precisa?), Por que não usar um único byte (sem sinal) - você não vai passar de 255 bytes para o IV, certo?
Maarten Bodewes
2

Versão executável do Editor Online: -

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

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

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

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

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

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

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

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

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

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

        return null;
    }

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

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}
Bhupesh Pant
fonte
Legal, feliz que ajudou!
Bhupesh Pant
Uma senha não é uma chave, um IV não deve ser estático. Código ainda restrito, o que torna impossível destruir a chave. Nenhuma indicação do que fazer com o IV, nem qualquer noção de que deveria ser imprevisível.
Maarten Bodewes
1

Muitas vezes é uma boa ideia confiar na solução fornecida pela biblioteca padrão:

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

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

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

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

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

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

Isso imprime "Texto a codificar".

A solução é baseada no Java Cryptography Architecture Reference Guide e https://stackoverflow.com/a/20591539/146745 resposta.

andrej
fonte
5
Nunca use o modo ECB. Período.
Konstantino Sparakis
1
O ECB não deve ser usado ao criptografar mais de um bloco de dados com a mesma chave, portanto, para o "Texto a ser codificado" é bom o suficiente. stackoverflow.com/a/1220869/146745
andrej
A chave @AndroidDev é gerada na seção de preparação da chave: aesKey = keygen.generateKey ()
andrej
1

Esta é uma melhoria em relação à resposta aceita.

Alterar:

(1) Usando IV aleatório e anexá-lo ao texto criptografado

(2) Usando SHA-256 para gerar uma chave a partir de uma senha

(3) Sem dependência do Apache Commons

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

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

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

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

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}
wvdz
fonte
Um hash ainda não é uma função de geração de chave / PBKDF baseada em senha. Você pode usar uma chave aleatória ou um PBKDF, como PBKDF2 / Criptografia baseada em senha.
Maarten Bodewes
@MaartenBodewes Você pode sugerir uma melhoria?
wvdz
PBKDF2 está presente em Java, então acho que apenas sugeri um. OK, eu não codifiquei um, mas isso é pedir um pouco demais na minha opinião. Existem muitos exemplos de criptografia baseada em senha.
Maarten Bodewes
@MaartenBodewes Achei que poderia ser uma solução simples. Por curiosidade, quais seriam as vulnerabilidades específicas ao usar este código como está?
wvdz
0

Outra solução usando java.util.Base64 com Spring Boot

Classe Encryptor

package com.jmendoza.springboot.crypto.cipher;

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

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

@Component
public class Encryptor {

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

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

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

Classe EncryptorController

package com.jmendoza.springboot.crypto.controller;

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

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

    @Autowired
    Encryptor encryptor;

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

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

application.properties

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

Exemplo

http: // localhost: 8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza

Jonathan Mendoza
fonte
-1

Versão otimizada da resposta aceita.

  • sem libs de terceiros

  • inclui IV na mensagem criptografada (pode ser público)

  • a senha pode ter qualquer comprimento

Código:

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

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

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

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

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

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

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

        return null;
    }

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

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

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

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

        return null;
    }

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

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

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

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

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

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

        return null;
    }
}

Uso:

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

Saída de exemplo:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World
ue7m
fonte
Sua função de derivação de senha não é segura. Eu não esperaria e.printStackTrace()no chamado código otimizado.
Maarten Bodewes