O bloco final dado não foi devidamente preenchido

115

Estou tentando implementar um algoritmo de criptografia baseado em senha, mas recebo esta exceção:

javax.crypto.BadPaddingException: bloco final fornecido não preenchido corretamente

Qual pode ser o problema?

Aqui está o meu código:

public class PasswordCrypter {

    private Key key;

    public PasswordCrypter(String password)  {
        try{
            KeyGenerator generator;
            generator = KeyGenerator.getInstance("DES");
            SecureRandom sec = new SecureRandom(password.getBytes());
            generator.init(sec);
            key = generator.generateKey();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public byte[] encrypt(byte[] array) throws CrypterException {
        try{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key);

            return cipher.doFinal(array);
        } catch (Exception e) { 
            e.printStackTrace();
        }
        return null;
    }

    public byte[] decrypt(byte[] array) throws CrypterException{
        try{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);

            return cipher.doFinal(array);
        } catch(Exception e ){
            e.printStackTrace();
        }
        return null;
    }
}

(O teste JUnit)

public class PasswordCrypterTest {

    private static final byte[] MESSAGE = "Alpacas are awesome!".getBytes();
    private PasswordCrypter[] passwordCrypters;
    private byte[][] encryptedMessages;

    @Before
    public void setUp() {
        passwordCrypters = new PasswordCrypter[] {
            new PasswordCrypter("passwd"),
            new PasswordCrypter("passwd"),
            new PasswordCrypter("otherPasswd")
        };

        encryptedMessages = new byte[passwordCrypters.length][];
        for (int i = 0; i < passwordCrypters.length; i++) {
            encryptedMessages[i] = passwordCrypters[i].encrypt(MESSAGE);
        }
    }

    @Test
    public void testEncrypt() {
        for (byte[] encryptedMessage : encryptedMessages) {
            assertFalse(Arrays.equals(MESSAGE, encryptedMessage));
        }

        assertFalse(Arrays.equals(encryptedMessages[0], encryptedMessages[2]));
        assertFalse(Arrays.equals(encryptedMessages[1], encryptedMessages[2]));
    }

    @Test
    public void testDecrypt() {
        for (int i = 0; i < passwordCrypters.length; i++) {
            assertArrayEquals(MESSAGE, passwordCrypters[i].decrypt(encryptedMessages[i]));
        }

        assertArrayEquals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[1]));
        assertArrayEquals(MESSAGE, passwordCrypters[1].decrypt(encryptedMessages[0]));

        try {
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[2])));
        } catch (CrypterException e) {
            // Anything goes as long as the above statement is not true.
        }

        try {
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[2].decrypt(encryptedMessages[1])));
        } catch (CrypterException e) {
            // Anything goes as long as the above statement is not true.
        }
    }
}
Altrim
fonte

Respostas:

197

Se você tentar descriptografar os dados preenchidos com PKCS5 com a chave errada e, em seguida, desempacotar (o que é feito pela classe Cipher automaticamente), você provavelmente obterá BadPaddingException (com provavelmente um pouco menos de 255/256, cerca de 99,61% ), porque o preenchimento tem uma estrutura especial que é validada durante o desbloqueio e muito poucas teclas produziriam um preenchimento válido.

Portanto, se você obtiver essa exceção, capture-a e trate-a como "chave errada".

Isso também pode acontecer quando você fornece uma senha errada, que então é usada para obter a chave de um armazenamento de chaves ou que é convertida em uma chave usando uma função de geração de chave.

Obviamente, o preenchimento incorreto também pode ocorrer se seus dados forem corrompidos no transporte.

Dito isso, existem algumas observações de segurança sobre seu esquema:

  • Para criptografia baseada em senha, você deve usar SecretKeyFactory e PBEKeySpec em vez de usar SecureRandom com KeyGenerator. O motivo é que SecureRandom pode ser um algoritmo diferente em cada implementação Java, fornecendo a você uma chave diferente. A SecretKeyFactory faz a derivação da chave de uma maneira definida (e de uma maneira que é considerada segura, se você selecionar o algoritmo correto).

  • Não use o modo ECB. Ele criptografa cada bloco independentemente, o que significa que blocos de texto simples idênticos também fornecem blocos de texto cifrado sempre idênticos.

    De preferência, use um modo de operação seguro , como CBC (Cipher block chaining) ou CTR (Counter). Alternativamente, use um modo que também inclua autenticação, como GCM (modo Galois-Counter) ou CCM (Counter with CBC-MAC), consulte o próximo ponto.

  • Normalmente, você não quer apenas confidencialidade, mas também autenticação, o que garante que a mensagem não seja violada. (Isso também evita ataques de texto cifrado escolhido em sua cifra, ou seja, ajuda na confidencialidade.) Portanto, adicione um MAC (código de autenticação de mensagem) à sua mensagem ou use um modo de cifra que inclua autenticação (consulte o ponto anterior).

  • DES tem um tamanho de chave efetivo de apenas 56 bits. Este espaço de chave é bastante pequeno, pode ser forçado de forma bruta em algumas horas por um atacante dedicado. Se você gerar sua chave por senha, isso ficará ainda mais rápido. Além disso, o DES tem um tamanho de bloco de apenas 64 bits, o que adiciona mais alguns pontos fracos nos modos de encadeamento. Use um algoritmo moderno como AES, que tem um tamanho de bloco de 128 bits e um tamanho de chave de 128 bits (para a variante padrão).

Paŭlo Ebermann
fonte
1
Eu só quero confirmar. Eu sou novo em criptografia e este é o meu cenário, estou usando criptografia AES. na minha função de criptografar / descriptografar, estou usando uma chave de criptografia. Usei uma chave de criptografia errada na descriptografia e entendi javax.crypto.BadPaddingException: Given final block not properly padded. Devo tratar isso como uma chave errada?
Kenicky,
Só para ficar claro, isso também pode acontecer ao fornecer a senha errada para um arquivo de armazenamento de chaves, como um arquivo .p12, que é o que aconteceu comigo.
Warren Dew
2
@WarrenDew "Senha errada para um arquivo de armazenamento de chaves" é apenas um caso especial de "chave errada".
Paŭlo Ebermann,
@kenicky desculpe, eu vi seu comentário agora há pouco ... sim, uma tecla errada quase sempre causa esse efeito. (Claro, dados corrompidos são outra possibilidade.)
Paŭlo Ebermann
@ PaŭloEbermann Concordo, mas não acho que isso seja necessariamente óbvio, já que é diferente da situação na postagem original, onde o programador tem controle sobre a chave e a descriptografia. Achei sua resposta útil o suficiente para votar positivamente, no entanto.
Warren Dew
1

dependendo do algoritmo de criptografia que você está usando, pode ser necessário adicionar alguns bytes de preenchimento no final antes de criptografar uma matriz de bytes para que o comprimento da matriz de bytes seja múltiplo do tamanho do bloco:

Especificamente no seu caso, o esquema de preenchimento que você escolheu é PKCS5, que é descrito aqui: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Presumo que você tenha o problema ao tentar criptografar)

Você pode escolher seu esquema de preenchimento ao instanciar o objeto Cipher. Os valores suportados dependem do provedor de segurança que você está usando.

A propósito, você tem certeza de que deseja usar um mecanismo de criptografia simétrica para criptografar senhas? Não seria melhor um hash unilateral? Se você realmente precisa ser capaz de descriptografar senhas, o DES é uma solução bastante fraca, você pode estar interessado em usar algo mais forte como AES se precisar ficar com um algoritmo simétrico.

fpacifici
fonte
1
então você poderia postar o código que tenta criptografar / descriptografar? (e verifique se a matriz de bytes que você tenta descriptografar não é maior que o tamanho do bloco)
fpacifici
1
Eu sou muito novo em Java e também em criptografia, então ainda não conheço melhores maneiras de fazer criptografia. Eu só quero fazer isso do que provavelmente procurar melhores maneiras de implementá-lo.
Altrim
você pode atualizar o link porque não funciona @fpacifici e atualizei meu post incluí o teste JUnit que testa a criptografia e descriptografia
Altrim 8/11/11
Corrigido (desculpe, erro ao copiar e colar). Enfim, de fato o seu problema acontece já que você descriptografa com uma chave que não é a mesma que a usada para criptografar como explica Paulo. Isso acontece porque o método anotado com @Before em junit é executado antes de cada método de teste, regenerando a chave todas as vezes. como a chave é inicializada aleatoriamente, ela será diferente a cada vez.
fpacifici
1

Eu conheci esse problema devido ao sistema operacional, simples para plataforma diferente sobre a implementação do JRE.

new SecureRandom(key.getBytes())

terá o mesmo valor no Windows, mas será diferente no Linux. Portanto, no Linux precisa ser alterado para

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(128, secureRandom);

"SHA1PRNG" é o algoritmo usado, você pode consultar aqui para obter mais informações sobre algoritmos.

Bejond
fonte
0

Isso também pode ser um problema quando você insere a senha errada para sua chave de assinatura.

O único eu
fonte
Este é realmente um comentário, não uma resposta. Com um pouco mais de reputação, você poderá postar comentários .
Adrian Mole
isso não é uma resposta
zig razor