Dado que el bloque final no está debidamente acolchado

115

Estoy tratando de implementar un algoritmo de cifrado basado en contraseña, pero obtengo esta excepción:

javax.crypto.BadPaddingException: el bloque final dado no se rellenó correctamente

¿Cuál podría ser el problema?

Aquí está mi 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;
    }
}

(La prueba 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
fuente

Respuestas:

197

Si intenta descifrar los datos rellenados con PKCS5 con la clave incorrecta, y luego los desempaqueta (lo que hace la clase Cipher automáticamente), lo más probable es que obtenga la BadPaddingException (probablemente con un poco menos de 255/256, alrededor del 99,61% ), porque el relleno tiene una estructura especial que se valida durante el desbloqueo y muy pocas teclas producirían un relleno válido.

Por lo tanto, si obtiene esta excepción, descárguela y trátela como "clave incorrecta".

Esto también puede suceder cuando proporciona una contraseña incorrecta, que luego se utiliza para obtener la clave de un almacén de claves, o que se convierte en una clave mediante una función de generación de claves.

Por supuesto, también puede ocurrir un relleno incorrecto si sus datos se corrompen durante el transporte.

Dicho esto, hay algunos comentarios de seguridad sobre su esquema:

  • Para el cifrado basado en contraseña, debe usar SecretKeyFactory y PBEKeySpec en lugar de usar SecureRandom con KeyGenerator. La razón es que SecureRandom podría ser un algoritmo diferente en cada implementación de Java, dándole una clave diferente. SecretKeyFactory realiza la derivación de claves de una manera definida (y de una manera que se considera segura, si selecciona el algoritmo correcto).

  • No utilice el modo ECB. Cifra cada bloque de forma independiente, lo que significa que los bloques de texto sin formato idénticos también dan bloques de texto cifrado siempre idénticos.

    Utilice preferiblemente un modo de funcionamiento seguro , como CBC (Encadenamiento de bloques cifrados) o CTR (Contador). Alternativamente, use un modo que también incluya autenticación, como GCM (modo Galois-Counter) o CCM (Contador con CBC-MAC), vea el siguiente punto.

  • Normalmente, no solo desea confidencialidad, sino también autenticación, lo que garantiza que el mensaje no se manipule. (Esto también evita los ataques de texto cifrado elegido en su cifrado, es decir, ayuda a la confidencialidad). Por lo tanto, agregue un MAC (código de autenticación de mensaje) a su mensaje, o use un modo de cifrado que incluya autenticación (consulte el punto anterior).

  • DES tiene un tamaño de clave efectivo de solo 56 bits. Este espacio clave es bastante pequeño, puede ser forzado brutalmente en algunas horas por un atacante dedicado. Si genera su clave con una contraseña, esto será aún más rápido. Además, DES tiene un tamaño de bloque de solo 64 bits, lo que agrega algunas debilidades más en los modos de encadenamiento. En su lugar, utilice un algoritmo moderno como AES, que tiene un tamaño de bloque de 128 bits y un tamaño de clave de 128 bits (para la variante estándar).

Paŭlo Ebermann
fuente
1
Solo quiero confirmar. Soy nuevo en el cifrado y este es mi escenario, estoy usando cifrado AES. en mi función de cifrar / descifrar, estoy usando una clave de cifrado. Usé una clave de cifrado incorrecta para descifrar y obtuve esto javax.crypto.BadPaddingException: Given final block not properly padded. ¿Debería tratar esto como una clave incorrecta?
kenicky
Para que quede claro, esto también puede suceder cuando se proporciona la contraseña incorrecta para un archivo de almacén de claves, como un archivo .p12, que es lo que me acaba de pasar.
Warren Dew
2
@WarrenDew "Contraseña incorrecta para un archivo de almacén de claves" es solo un caso especial de "clave incorrecta".
Paŭlo Ebermann
@kenicky lo siento, vi tu comentario hace un momento ... sí, una clave incorrecta casi siempre causa este efecto. (Por supuesto, los datos corruptos son otra posibilidad.)
Paŭlo Ebermann
@ PaŭloEbermann Estoy de acuerdo, pero no creo que eso sea necesariamente obvio de inmediato, ya que es diferente a la situación en la publicación original donde el programador tiene control sobre la clave y el descifrado. Sin embargo, encontré tu respuesta lo suficientemente útil como para votarla.
Warren Dew
1

Dependiendo del algoritmo de criptografía que esté utilizando, es posible que deba agregar algunos bytes de relleno al final antes de encriptar una matriz de bytes para que la longitud de la matriz de bytes sea múltiplo del tamaño del bloque:

Específicamente en su caso, el esquema de relleno que eligió es PKCS5, que se describe aquí: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Supongo que tiene el problema cuando intenta cifrar)

Puede elegir su esquema de relleno cuando crea una instancia del objeto Cipher. Los valores admitidos dependen del proveedor de seguridad que esté utilizando.

Por cierto, ¿está seguro de que desea utilizar un mecanismo de cifrado simétrico para cifrar contraseñas? ¿No sería mejor un hash unidireccional? Si realmente necesita poder descifrar contraseñas, DES es una solución bastante débil, puede estar interesado en usar algo más fuerte como AES si necesita permanecer con un algoritmo simétrico.

fpacifici
fuente
1
Entonces, ¿podría publicar el código que intenta cifrar / descifrar? (y verifique que la matriz de bytes que intenta descifrar no sea más grande que el tamaño del bloque)
fpacifici
1
Soy muy nuevo en Java y también en criptografía, por lo que todavía no conozco mejores formas de cifrar. Solo quiero hacer esto que probablemente buscar mejores formas de implementarlo.
Altrim
¿Pueden actualizar el enlace porque no funciona? @fpacifici y actualicé mi publicación Incluí la prueba JUnit que prueba el cifrado y el descifrado
Altrim
Corregido (lo siento, error de copiar y pegar). De todos modos, de hecho, su problema ocurre ya que descifra con una clave que no es la misma que se usa para el cifrado como explicó Paulo. Esto sucede ya que el método anotado con @Before en junit se ejecuta antes de cada método de prueba, regenerando así la clave cada vez. dado que la clave se inicializa aleatoriamente, será diferente cada vez.
fpacifici
1

Me encontré con este problema debido al sistema operativo, simple a una plataforma diferente sobre la implementación de JRE.

new SecureRandom(key.getBytes())

obtendrá el mismo valor en Windows, mientras que es diferente en Linux. Entonces en Linux debe cambiarse a

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

"SHA1PRNG" es el algoritmo utilizado, puede consultar aquí para obtener más información sobre los algoritmos.

Bejond
fuente
0

Esto también puede ser un problema cuando ingresa una contraseña incorrecta para su clave de firma.

El único yo
fuente
Esto es realmente un comentario, no una respuesta. Con un poco más de reputación, podrá publicar comentarios .
Adrian Mole
esto no es una respuesta
zig razor