Quais são as práticas recomendadas para usar a criptografia AES no Android?

89

Por que faço esta pergunta:

Eu sei que tem havido muitas perguntas sobre a criptografia AES, mesmo para Android. E há muitos trechos de código se você pesquisar na web. Mas em cada página, em cada pergunta do Stack Overflow, encontro outra implementação com grandes diferenças.

Portanto, criei esta questão para encontrar uma "prática recomendada". Espero que possamos coletar uma lista dos requisitos mais importantes e configurar uma implementação que seja realmente segura!

Eu li sobre vetores de inicialização e sais. Nem todas as implementações que encontrei tinham esses recursos. Então você precisa disso? Aumenta muito a segurança? Como você o implementa? O algoritmo deve gerar exceções se os dados criptografados não puderem ser descriptografados? Ou isso é inseguro e deve apenas retornar uma string ilegível? O algoritmo pode usar Bcrypt em vez de SHA?

E quanto a essas duas implementações que encontrei? Eles estão bem? Perfeito ou faltando algumas coisas importantes? Qual destes é seguro?

O algoritmo deve pegar uma string e uma "senha" para criptografar e, em seguida, criptografar a string com essa senha. A saída deve ser uma string (hex ou base64?) Novamente. A descriptografia também deve ser possível, é claro.

Qual é a implementação AES perfeita para Android?

Implementação # 1:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
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 AdvancedCrypto implements ICrypto {

        public static final String PROVIDER = "BC";
        public static final int SALT_LENGTH = 20;
        public static final int IV_LENGTH = 16;
        public static final int PBE_ITERATION_COUNT = 100;

        private static final String RANDOM_ALGORITHM = "SHA1PRNG";
        private static final String HASH_ALGORITHM = "SHA-512";
        private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
        private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
        private static final String SECRET_KEY_ALGORITHM = "AES";

        public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
                try {

                        byte[] iv = generateIv();
                        String ivHex = HexEncoder.toHex(iv);
                        IvParameterSpec ivspec = new IvParameterSpec(iv);

                        Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
                        byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
                        String encryptedHex = HexEncoder.toHex(encryptedText);

                        return ivHex + encryptedHex;

                } catch (Exception e) {
                        throw new CryptoException("Unable to encrypt", e);
                }
        }

        public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
                try {
                        Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        String ivHex = encrypted.substring(0, IV_LENGTH * 2);
                        String encryptedHex = encrypted.substring(IV_LENGTH * 2);
                        IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
                        decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
                        byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
                        String decrypted = new String(decryptedText, "UTF-8");
                        return decrypted;
                } catch (Exception e) {
                        throw new CryptoException("Unable to decrypt", e);
                }
        }

        public SecretKey getSecretKey(String password, String salt) throws CryptoException {
                try {
                        PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
                        SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
                        SecretKey tmp = factory.generateSecret(pbeKeySpec);
                        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
                        return secret;
                } catch (Exception e) {
                        throw new CryptoException("Unable to get secret key", e);
                }
        }

        public String getHash(String password, String salt) throws CryptoException {
                try {
                        String input = password + salt;
                        MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
                        byte[] out = md.digest(input.getBytes("UTF-8"));
                        return HexEncoder.toHex(out);
                } catch (Exception e) {
                        throw new CryptoException("Unable to get hash", e);
                }
        }

        public String generateSalt() throws CryptoException {
                try {
                        SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                        byte[] salt = new byte[SALT_LENGTH];
                        random.nextBytes(salt);
                        String saltHex = HexEncoder.toHex(salt);
                        return saltHex;
                } catch (Exception e) {
                        throw new CryptoException("Unable to generate salt", e);
                }
        }

        private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
                SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                byte[] iv = new byte[IV_LENGTH];
                random.nextBytes(iv);
                return iv;
        }

}

Fonte: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

Implementação # 2:

import java.security.SecureRandom;

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

/**
 * Usage:
 * <pre>
 * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
 * ...
 * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
 * </pre>
 * @author ferenc.hechler
 */
public class SimpleCrypto {

    public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String seed, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }


    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }
    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length()/2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2*buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }
    private final static String HEX = "0123456789ABCDEF";
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }

}

Fonte: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

crocitar
fonte
Estou tentando implementar a solução 1, mas precisava de algumas aulas. você tem o código-fonte completo?
albanx
1
Não, não tenho, desculpe. Mas eu fiz isso funcionar simplesmente excluindo implements ICryptoe alterando throws CryptoExceptionpara throws Exceptione assim por diante. Então você não precisará mais dessas aulas.
caw
Mas também a classe HexEncoder está faltando? Onde posso encontrar?
albanx
HexEncoder faz parte da biblioteca BouncyCastle, eu acho. Você pode apenas fazer o download. Ou você pode pesquisar no google por "byte [] to hex" e vice-versa em Java.
caw
Obrigado Marco. Mas noto que há 3 métodos getSecretKey, getHash, generateSaltna primeira implementação que não são utilizados. Talvez eu esteja errado, mas como essa classe poderia ser usada para criptografar uma string na prática?
albanx de

Respostas:

37

Nenhuma das implementações fornecidas em sua pergunta está totalmente correta e nenhuma das implementações fornecidas deve ser usada como está. A seguir, discutirei aspectos da criptografia baseada em senha no Android.

Chaves e Hashes

Vou começar a discutir o sistema baseado em senha com sais. O sal é um número gerado aleatoriamente. Não é "deduzido". A implementação 1 inclui um generateSalt()método que gera um número aleatório criptograficamente forte. Como o sal é importante para a segurança, ele deve ser mantido em segredo assim que for gerado, embora precise ser gerado apenas uma vez. Se este for um site da Web, é relativamente fácil manter o segredo do sal, mas para aplicativos instalados (para desktops e dispositivos móveis), isso será muito mais difícil.

O método getHash()retorna um hash da senha e do salt fornecidos, concatenado em uma única string. O algoritmo usado é SHA-512, que retorna um hash de 512 bits. Este método retorna um hash que é útil para verificar a integridade de uma string, então também pode ser usado chamandogetHash() apenas com uma senha ou apenas um salt, já que simplesmente concatena ambos os parâmetros. Como esse método não será usado no sistema de criptografia baseado em senha, não o discutirei mais.

O método getSecretKey()deriva uma chave de uma charmatriz da senha e um sal codificado em hexadecimal, conforme retornado de generateSalt(). O algoritmo usado é PBKDF1 (eu acho) de PKCS5 com SHA-256 como a função hash e retorna uma chave de 256 bits. getSecretKey()gera uma chave gerando repetidamente hashes da senha, salt e um contador (até a contagem de iteração fornecida PBE_ITERATION_COUNT, aqui 100) para aumentar o tempo necessário para montar um ataque de força bruta. O comprimento do salt deve ser pelo menos tão longo quanto a chave que está sendo gerada, neste caso, pelo menos 256 bits. A contagem de iterações deve ser definida o maior tempo possível, sem causar atrasos excessivos. Para obter mais informações sobre sais e contagens de iteração na derivação principal, consulte a seção 4 em RFC2898 .

A implementação no PBE do Java, entretanto, é falha se a senha contiver caracteres Unicode, ou seja, aqueles que requerem mais de 8 bits para serem representados. Conforme declarado em PBEKeySpec, "o mecanismo PBE definido em PKCS # 5 olha apenas para os 8 bits de ordem inferior de cada caractere". Para contornar esse problema, você pode tentar gerar uma seqüência hexadecimal (que conterá apenas caracteres de 8 bits) de todos os caracteres de 16 bits da senha antes de passá-la para PBEKeySpec. Por exemplo, "ABC" torna-se "004100420043". Observe também que PBEKeySpec "solicita a senha como uma matriz de caracteres, para que possa ser substituída [com clearPassword()] quando terminar". (Com relação à "proteção de strings na memória", consulte esta pergunta .) Não vejo nenhum problema, no entanto,

Encriptação

Depois que uma chave é gerada, podemos usá-la para criptografar e descriptografar o texto.

Na implementação 1, o algoritmo de cifra utilizado é AES/CBC/PKCS5Padding, ou seja, AES no modo de cifra Cipher Block Chaining (CBC), com preenchimento definido em PKCS # 5. (Outros modos de cifra AES incluem modo contador (CTR), modo livro de código eletrônico (ECB) e modo contador Galois (GCM). Outra pergunta no Stack Overflow contém respostas que discutem em detalhes os vários modos de cifra AES e os recomendados para uso. Esteja ciente também de que existem vários ataques à criptografia do modo CBC, alguns dos quais são mencionados no RFC 7457.)

Observe que você deve usar um modo de criptografia que também verifique a integridade dos dados criptografados (por exemplo, criptografia autenticada com dados associados , AEAD, descrita em RFC 5116). No entanto, AES/CBC/PKCS5Paddingnão fornece verificação de integridade, portanto, sozinho não é recomendado . Para fins de AEAD, o uso de um segredo com pelo menos o dobro do comprimento de uma chave de criptografia normal é recomendado, para evitar ataques de chave relacionados: a primeira metade serve como chave de criptografia e a segunda metade serve como chave para a verificação de integridade. (Ou seja, neste caso, gere um único segredo a partir de uma senha e sal e divida esse segredo em dois.)

Implementação Java

As várias funções na implementação 1 usam um provedor específico, a saber "BC", para seus algoritmos. Em geral, porém, não é recomendado solicitar provedores específicos, uma vez que nem todos os provedores estão disponíveis em todas as implementações Java, seja por falta de suporte, para evitar duplicação de código, ou por outros motivos. Esse conselho se tornou especialmente importante desde o lançamento da visualização do Android P no início de 2018, porque algumas funcionalidades do provedor "BC" foram descontinuadas lá - consulte o artigo "Mudanças na criptografia no Android P" no Android Developers Blog. Consulte também a introdução aos provedores Oracle .

Portanto, PROVIDERnão deve existir e a string -BCdeve ser removida PBE_ALGORITHM. A implementação 2 está correta a este respeito.

É inadequado para um método capturar todas as exceções, mas sim tratar apenas as exceções que pode. As implementações fornecidas em sua pergunta podem lançar uma variedade de exceções verificadas. Um método pode escolher envolver apenas essas exceções verificadas com CryptoException ou especificar essas exceções verificadas na throwscláusula. Por conveniência, agrupar a exceção original com CryptoException pode ser apropriado aqui, uma vez que existem potencialmente muitas exceções verificadas que as classes podem lançar.

SecureRandom no Android

Conforme detalhado no artigo "Some SecureRandom Thoughts", no Android Developers Blog, a implementação do java.security.SecureRandomAndroid nas versões anteriores a 2013 tem uma falha que reduz a força dos números aleatórios que fornece. Essa falha pode ser mitigada conforme descrito nesse artigo.

Peter O.
fonte
Essa geração de segredo duplo é um desperdício na minha opinião, você poderia facilmente dividir o segredo gerado em dois, ou - se não houver bits suficientes disponíveis - adicionar um contador (1 para a primeira chave, 2 para a segunda chave) para o segredo e execute um único hash. Não há necessidade de realizar todas as iterações duas vezes.
Maarten Bodewes
Obrigado pelas informações sobre o HMAC e o sal. Não vou usar o HMAC desta vez, mas depois pode ser muito útil. E, em geral, isso é uma coisa boa, sem dúvida.
caw
Muito obrigado por todas as edições e esta (agora) introdução maravilhosa à criptografia AES em Java!
caw
1
Deveria. getInstancetem uma sobrecarga que leva apenas o nome do algoritmo. Exemplo: Cipher.getInstance () Vários provedores, incluindo Bouncy Castle, podem ser registrados na implementação Java e esse tipo de sobrecarga busca na lista de provedores um deles que implementa o algoritmo fornecido. Você deveria experimentar e ver.
Peter O.
1
Sim, ele pesquisará os provedores na ordem fornecida por Security.getProviders () - embora agora também verifique se a chave é aceita por aquele provedor durante a chamada init () permitindo a criptografia assistida por hardware. Mais detalhes aqui: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Maarten Bodewes
18

# 2 nunca deve ser usado, pois usa apenas "AES" (que significa criptografia de modo ECB em texto, um grande não-não) para a cifra. Vou apenas falar sobre o # 1.

A primeira implementação parece aderir às melhores práticas de criptografia. As constantes são geralmente OK, embora o tamanho do sal e o número de iterações para realizar PBE estejam no lado curto. Além disso, parece ser para AES-256, já que a geração da chave PBE usa 256 como um valor codificado (uma pena depois de todas essas constantes). Ele usa CBC e PKCS5Padding, que é pelo menos o que você esperaria.

Falta completamente qualquer proteção de autenticação / integridade, então um invasor pode alterar o texto cifrado. Isso significa que ataques oracle de preenchimento são possíveis em um modelo cliente / servidor. Isso também significa que um invasor pode tentar alterar os dados criptografados. Isso provavelmente resultará em algum erro em algum lugar porque o preenchimento ou conteúdo não é aceito pelo aplicativo, mas essa não é uma situação na qual você deseja estar.

O tratamento de exceções e a validação de entrada podem ser aprimorados, detectar a exceção está sempre errado em meu livro. Além disso, a classe implementa ICrypt, que eu não sei. Eu sei que ter apenas métodos sem efeitos colaterais em uma aula é um pouco estranho. Normalmente, você faria isso estático. Não há armazenamento em buffer de instâncias de Cipher, etc., portanto, todos os objetos necessários são criados ad-nauseum. No entanto, você pode remover com segurança ICrypto da definição ao que parece; nesse caso, você também pode refatorar o código para métodos estáticos (ou reescrevê-lo para ser mais orientado a objetos, sua escolha).

O problema é que qualquer wrapper sempre faz suposições sobre o caso de uso. Dizer que um invólucro está certo ou errado é, portanto, besteira. É por isso que sempre tento evitar a geração de classes de wrapper. Mas pelo menos não parece explicitamente errado.

Maarten Bodewes
fonte
Muito obrigado por esta resposta detalhada! Sei que é uma pena, mas ainda não conhecia a seção de revisão de código: D Obrigado por essa dica, vou dar uma olhada. Mas essa questão também se encaixa aqui na minha opinião, pois não quero apenas uma revisão desses trechos de código. Em vez disso, gostaria de perguntar a todos quais aspectos são importantes ao implementar a criptografia AES no Android. E você está certo novamente, este trecho de código é para AES-256. Então você diria que esta é uma implementação geral segura do AES-256? O caso de uso é que eu só quero armazenar com segurança informações de texto em um banco de dados.
caw
1
Parece bom, mas a ideia de não ter verificações de integridade e autenticação me incomodaria. Se você tiver espaço suficiente, consideraria seriamente adicionar um HMAC sobre o texto cifrado. Dito isso, como você provavelmente está tentando simplesmente adicionar confidencialidade, considero isso uma grande vantagem, mas não diretamente um requisito.
Maarten Bodewes
Mas se a intenção é apenas que outras pessoas não tenham acesso às informações criptografadas, não preciso de um HMAC, certo? Se eles alterarem o texto cifrado e forçarem um resultado "errado" de descriptografia, não haverá problema real, certo?
caw
Se isso não estiver em seu cenário de risco, tudo bem. Se eles puderem de alguma forma acionar uma descriptografia repetida pelo sistema depois de alterar o texto cifrado (um ataque de oráculo de preenchimento), eles poderão descriptografar os dados sem nunca saber a chave. Eles não podem fazer isso se simplesmente obterem os dados em um sistema que não possui a chave. Mas é por isso que é sempre uma prática recomendada adicionar um HMAC. Pessoalmente, eu consideraria um sistema com AES-128 e HMAC mais seguro do que AES-256 sem - mas, como disse, provavelmente não é necessário.
Maarten Bodewes
1
Por que não usar AES no modo Galois / Counter (AES-GCM) se você deseja integridade?
Kimvais
1

Você fez uma pergunta muito interessante. Como acontece com todos os algoritmos, a chave de cifra é o "molho secreto", pois uma vez que é conhecida do público, todo o resto também é. Então, você procura maneiras de este documento do Google

segurança

Além do Google In-App Billing, também oferece ideias sobre segurança, o que também é perspicaz

billing_best_practices

the100rabh
fonte
Obrigado por esses links! O que exatamente você quer dizer com "quando a chave de criptografia está fora, todo o resto também sai"?
caw
O que quero dizer é que a chave de criptografia precisa ser segura; se alguém puder obtê-la, seus dados criptografados serão tão bons quanto texto simples. Por favor, vote positivamente se você achou minha resposta útil até certo ponto :-)
the100rabh
0

Use a API BouncyCastle Lightweight. Ele fornece 256 AES com PBE e sal.
Aqui está um exemplo de código, que pode criptografar / descriptografar arquivos.

public void encrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(true, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(true, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public void decrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(false, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(false, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                // int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Kelheor
fonte
Obrigado! Esta é provavelmente uma solução boa e segura, mas não quero usar software de terceiros. Tenho certeza de que deve ser possível implementar AES de forma segura por conta própria.
caw
2
Depende se você deseja incluir proteção contra ataques de canal lateral. Geralmente, você deve presumir que não é seguro implementar algoritmos criptográficos por conta própria. Como o AES CBC está disponível nas bibliotecas de tempo de execução Java do Oracle, provavelmente é melhor usá-las e usar as bibliotecas do Bouncy Castle se um algoritmo não estiver disponível.
Maarten Bodewes
Está faltando a definição de buf(eu realmente espero que não seja um staticcampo). Também se parece com ambos encrypt()e decrypt()não conseguirá processar o bloco final corretamente se a entrada for um múltiplo de 1024 bytes.
tc.