Đã đưa ra khối cuối cùng không được đệm đúng cách


115

Tôi đang cố gắng triển khai thuật toán mã hóa dựa trên mật khẩu, nhưng tôi nhận được ngoại lệ này:

javax.crypto.BadPaddingException: Cho khối cuối cùng không được đệm đúng cách

Rắc rối có thể là cái gì?

Đây là mã của tôi:

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

(Bài kiểm tra 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.
        }
    }
}

Câu trả lời:


197

Nếu bạn cố gắng giải mã dữ liệu được đệm PKCS5 bằng phím sai, sau đó giải mã dữ liệu đó (được thực hiện bởi lớp Cipher tự động), rất có thể bạn sẽ nhận được BadPaddingException (có thể nhỏ hơn một chút 255/256, khoảng 99,61% ), vì phần đệm có cấu trúc đặc biệt được xác nhận trong quá trình mở bàn phím và rất ít phím có thể tạo ra phần đệm hợp lệ.

Vì vậy, nếu bạn nhận được ngoại lệ này, hãy nắm bắt nó và coi nó như "chìa khóa sai".

Điều này cũng có thể xảy ra khi bạn cung cấp mật khẩu sai, mật khẩu này sau đó được sử dụng để lấy khóa từ kho khóa hoặc được chuyển đổi thành khóa bằng chức năng tạo khóa.

Tất nhiên, phần đệm xấu cũng có thể xảy ra nếu dữ liệu của bạn bị hỏng trong quá trình vận chuyển.

Điều đó nói rằng, có một số nhận xét bảo mật về kế hoạch của bạn:

  • Đối với mã hóa dựa trên mật khẩu, bạn nên sử dụng SecretKeyFactory và PBEKeySpec thay vì sử dụng SecureRandom với KeyGenerator. Lý do là vì SecureRandom có ​​thể là một thuật toán khác nhau trên mỗi triển khai Java, cung cấp cho bạn một khóa khác nhau. SecretKeyFactory thực hiện việc lấy khóa theo một cách đã xác định (và một cách được coi là an toàn, nếu bạn chọn đúng thuật toán).

  • Không sử dụng chế độ ECB. Nó mã hóa từng khối một cách độc lập, có nghĩa là các khối văn bản thuần túy giống hệt nhau cũng cung cấp các khối bản mã luôn giống hệt nhau.

    Tốt hơn là sử dụng một chế độ hoạt động an toàn , như CBC (Chuỗi khối mật mã) hoặc CTR (Bộ đếm). Ngoài ra, hãy sử dụng một chế độ cũng bao gồm xác thực, như GCM (chế độ Galois-Counter) hoặc CCM (Counter với CBC-MAC), xem điểm tiếp theo.

  • Bạn thường không chỉ muốn bảo mật mà còn cả xác thực, điều này đảm bảo tin nhắn không bị giả mạo. (Điều này cũng ngăn chặn các cuộc tấn công đã chọn vào bản mã của bạn, tức là giúp bảo mật.) Vì vậy, hãy thêm MAC (mã xác thực tin nhắn) vào tin nhắn của bạn hoặc sử dụng chế độ mật mã bao gồm xác thực (xem phần trước).

  • DES có kích thước khóa hiệu dụng chỉ 56 bit. Không gian chính này khá nhỏ, nó có thể bị kẻ gian chuyên dụng cưỡng bức trong vài giờ. Nếu bạn tạo khóa của mình bằng mật khẩu, việc này sẽ nhanh hơn. Ngoài ra, DES có kích thước khối chỉ 64 bit, điều này làm tăng thêm một số điểm yếu trong chế độ chuỗi. Thay vào đó, hãy sử dụng thuật toán hiện đại như AES, có kích thước khối là 128 bit và kích thước khóa là 128 bit (đối với biến thể tiêu chuẩn).


1
Tôi chỉ muốn xác nhận. Tôi mới sử dụng mã hóa và đây là kịch bản của tôi, tôi đang sử dụng mã hóa AES. trong chức năng mã hóa / giải mã của mình, tôi đang sử dụng khóa mã hóa. Tôi đã sử dụng sai khóa mã hóa khi giải mã và tôi nhận được điều này javax.crypto.BadPaddingException: Given final block not properly padded. Tôi có nên coi đây là một chìa khóa sai?
kenicky

Nói rõ hơn, điều này cũng có thể xảy ra khi cung cấp sai mật khẩu cho tệp lưu trữ khóa, chẳng hạn như tệp .p12, đó là điều vừa xảy ra với tôi.
Warren Dew

2
@WarrenDew "Sai mật khẩu cho tệp lưu trữ khóa" chỉ là một trường hợp đặc biệt của "khóa sai".
Paŭlo Ebermann 17/09/15

@kenicky xin lỗi, tôi thấy bình luận của bạn vừa rồi ... vâng, một phím sai hầu như luôn gây ra hiệu ứng này. (Tất nhiên, dữ liệu bị hỏng là một khả năng khác.)
Paulo Ebermann

@ PaŭloEbermann Tôi đồng ý, nhưng tôi không nghĩ điều đó nhất thiết phải rõ ràng ngay lập tức, vì nó khác với tình huống trong bài đăng ban đầu, nơi lập trình viên có quyền kiểm soát khóa và giải mã. Tuy nhiên, tôi thấy câu trả lời của bạn đủ hữu ích để ủng hộ nó.
Warren Dew

1

tùy thuộc vào thuật toán mật mã bạn đang sử dụng, bạn có thể phải thêm một số byte đệm vào cuối trước khi mã hóa mảng byte sao cho độ dài của mảng byte bằng bội số kích thước khối:

Cụ thể trong trường hợp của bạn, lược đồ đệm bạn chọn là PKCS5 được mô tả tại đây: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Tôi cho rằng bạn gặp sự cố khi cố gắng mã hóa)

Bạn có thể chọn lược đồ đệm của mình khi khởi tạo đối tượng Mật mã. Các giá trị được hỗ trợ phụ thuộc vào nhà cung cấp bảo mật mà bạn đang sử dụng.

Nhân tiện, bạn có chắc chắn muốn sử dụng cơ chế mã hóa đối xứng để mã hóa mật khẩu không? Sẽ không phải là một cách băm tốt hơn? Nếu bạn thực sự cần có khả năng giải mã mật khẩu, DES là một giải pháp khá yếu, bạn có thể quan tâm đến việc sử dụng thứ gì đó mạnh hơn như AES nếu bạn cần ở lại với một thuật toán đối xứng.


1
vì vậy bạn có thể vui lòng đăng mã cố gắng mã hóa / giải mã không? (và kiểm tra xem các mảng byte bạn cố gắng để giải mã không phải là lớn hơn kích thước khối)
fpacifici

1
Tôi rất mới đối với Java và cả Cryptography nên tôi vẫn chưa biết cách tốt hơn để mã hóa. Tôi chỉ muốn hoàn thành công việc này hơn là tìm kiếm những cách tốt hơn để thực hiện nó.
Altrim

bạn có thể cập nhật liên kết vì nó không hoạt động @fpacifici và tôi đã cập nhật bài đăng của mình, tôi đã bao gồm bài kiểm tra JUnit kiểm tra mã hóa và giải mã
Altrim

Đã sửa (xin lỗi sao chép lỗi dán). Dù sao, thực sự vấn đề của bạn xảy ra vì bạn giải mã bằng một khóa không giống với khóa được sử dụng để mã hóa như Paulo giải thích. Điều này xảy ra vì phương thức được chú thích bằng @Before trong junit được thực thi trước mọi phương pháp thử nghiệm, do đó, luôn tạo lại khóa. vì khóa được khởi tạo ngẫu nhiên nên mỗi lần khóa sẽ khác nhau.
fpacifici

1

Tôi gặp sự cố này do hệ thống vận hành, nền tảng đơn giản đến khác nhau về triển khai JRE.

new SecureRandom(key.getBytes())

sẽ nhận được cùng một giá trị trong Windows, trong khi nó khác trong Linux. Vì vậy, trong Linux cần được thay đổi thành

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

"SHA1PRNG" là thuật toán được sử dụng, bạn có thể tham khảo tại đây để biết thêm thông tin về thuật toán.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.