최종 블록이 제대로 채워지지 않은 경우


115

암호 기반 암호화 알고리즘을 구현하려고하는데이 예외가 발생합니다.

javax.crypto.BadPaddingException : 주어진 최종 블록이 제대로 채워지지 않았습니다.

무엇이 문제일까요?

내 코드는 다음과 같습니다.

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

(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.
        }
    }
}

답변:


197

잘못된 키로 PKCS5 패딩 된 데이터를 복호화 한 다음 패딩을 해제하면 (Cipher 클래스에서 자동으로 수행됨) BadPaddingException이 발생할 가능성이 큽니다 (약 255/256 미만, 약 99.61 %) ), 패딩은 패딩을 해제하는 동안 유효성이 검사되는 특수 구조를 가지고 있고 매우 적은 수의 키가 유효한 패딩을 생성하기 때문입니다.

따라서이 예외가 발생하면이를 포착하여 "잘못된 키"로 취급하십시오.

이는 잘못된 비밀번호를 제공 한 경우 키 저장소에서 키를 가져 오는 데 사용되거나 키 생성 기능을 사용하여 키로 변환되는 경우에도 발생할 수 있습니다.

물론 전송 중에 데이터가 손상된 경우에도 잘못된 패딩이 발생할 수 있습니다.

즉, 귀하의 계획에 대한 몇 가지 보안주의 사항이 있습니다.

  • 암호 기반 암호화의 경우 KeyGenerator와 함께 SecureRandom을 사용하는 대신 SecretKeyFactory 및 PBEKeySpec을 사용해야합니다. 그 이유는 SecureRandom이 각 Java 구현에서 다른 알고리즘이되어 다른 키를 제공 할 수 있기 때문입니다. SecretKeyFactory는 정의 된 방식 (그리고 올바른 알고리즘을 선택하면 안전하다고 간주되는 방식)으로 키 파생을 수행합니다.

  • ECB 모드를 사용하지 마십시오. 각 블록을 독립적으로 암호화하므로 동일한 일반 텍스트 블록도 항상 동일한 암호문 블록을 제공합니다.

    CBC (Cipher block chaining) 또는 CTR (Counter)과 같은 보안 작동 모드를 사용하는 것이 좋습니다 . 또는 GCM (Galois-Counter 모드) 또는 CCM (CBC-MAC이있는 카운터)과 같은 인증도 포함하는 모드를 사용하십시오. 다음 사항을 참조하십시오.

  • 일반적으로 기밀성뿐만 아니라 메시지가 변조되지 않도록하는 인증도 원합니다. (이는 또한 암호에 대한 선택된 암호문 공격을 방지합니다. 즉, 기밀 유지에 도움이됩니다.) 따라서 메시지에 MAC (메시지 인증 코드)를 추가하거나 인증을 포함하는 암호 모드를 사용하십시오 (이전 요점 참조).

  • DES의 유효 키 크기는 56 비트에 불과합니다. 이 키 공간은 매우 작으며 전담 공격자가 몇 시간 내에 무차별 대입 할 수 있습니다. 비밀번호로 키를 생성하면 더 빨라집니다. 또한 DES의 블록 크기는 64 비트에 불과하므로 연결 모드에서 더 많은 약점이 추가됩니다. 대신 블록 크기가 128 비트이고 키 크기가 128 비트 (표준 변형의 경우) 인 AES와 같은 최신 알고리즘을 사용하십시오.


1
확인하고 싶습니다. 저는 암호화를 처음 사용하고 이것이 제 시나리오이며 AES 암호화를 사용하고 있습니다. 암호화 / 복호화 기능에서 암호화 키를 사용하고 있습니다. 암호 해독에 잘못된 암호화 키를 사용했는데이 javax.crypto.BadPaddingException: Given final block not properly padded. 이 키를 잘못된 키로 취급해야합니까?
kenicky

명확하게 말하면 .p12 파일과 같은 키 저장소 파일에 잘못된 암호를 제공 할 때도 발생할 수 있습니다.
Warren Dew

2
@WarrenDew "키 저장소 파일에 대한 잘못된 암호"는 "잘못된 키"의 특별한 경우입니다.
Paŭlo Ebermann 2015

@kenicky 죄송합니다, 방금 귀하의 의견을 봤습니다 ... 예, 잘못된 키는 거의 항상이 효과를 유발합니다. (물론 데이터 손상 가능성도 있습니다.)
Paŭlo

@ PaŭloEbermann 동의하지만, 프로그래머가 키와 암호 해독을 제어 할 수있는 원래 게시물의 상황과 다르기 때문에 이것이 반드시 즉시 명백하다고 생각하지는 않습니다. 나는 당신의 대답이 그것을 찬성하기에 충분히 유용하다고 생각했습니다.
Warren Dew

1

사용중인 암호화 알고리즘에 따라 바이트 배열의 길이가 블록 크기의 배수가되도록 바이트 배열을 암호화하기 전에 끝에 패딩 바이트를 추가해야 할 수 있습니다.

특히 귀하의 경우 선택한 패딩 스키마는 PKCS5이며 여기에 설명되어 있습니다. http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(암호화하려고 할 때 문제가 있다고 가정합니다)

Cipher 오브젝트를 인스턴스화 할 때 패딩 스키마를 선택할 수 있습니다. 지원되는 값은 사용중인 보안 공급자에 따라 다릅니다.

그런데 대칭 암호화 메커니즘을 사용하여 암호를 암호화 하시겠습니까? 편도 해시가 더 좋지 않을까요? 정말로 암호를 해독 할 수 있어야하는 경우 DES는 매우 약한 솔루션입니다. 대칭 알고리즘을 유지해야하는 경우 AES와 같은 더 강력한 것을 사용하는 데 관심이있을 수 있습니다.


1
암호화 / 복호화를 시도하는 코드를 게시 해 주시겠습니까? (그리고 해독하려는 바이트 배열이 블록 크기보다 크지 않은지 확인하십시오.)
fpacifici

1
저는 Java와 Cryptography를 처음 접했기 때문에 암호화를 수행하는 더 좋은 방법을 아직 모릅니다. 나는 그것을 구현하는 더 나은 방법을 찾는 것보다 이것을 끝내고 싶습니다.
Altrim 2011

@fpacifici가 작동하지 않기 때문에 링크를 업데이트 할 수 있습니까? 그리고 내 게시물을 업데이트했습니다. 암호화 및 암호 해독을 테스트하는 JUnit 테스트를 포함했습니다
Altrim

수정되었습니다 (복사 붙여 넣기 오류). 어쨌든 Paulo가 설명한 것처럼 암호화에 사용되는 것과 동일하지 않은 키로 암호를 해독하기 때문에 실제로 문제가 발생합니다. 이는 junit의 @Before 주석이 달린 메소드가 모든 테스트 메소드보다 먼저 실행되기 때문에 발생하므로 매번 키를 다시 생성합니다. 키는 임의로 초기화되기 때문에 매번 달라집니다.
fpacifici 2011

1

이 문제는 JRE 구현에 대한 간단한 다른 플랫폼의 운영 체제로 인해 발생했습니다.

new SecureRandom(key.getBytes())

Windows에서는 동일한 값을 가져 오지만 Linux에서는 다릅니다. 따라서 Linux에서는 다음으로 변경해야합니다.

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

"SHA1PRNG"는 사용되는 알고리즘입니다. 여기 에서 알고리즘에 대한 자세한 정보를 참조 할 수 있습니다 .


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.