javax.crypto.BadPaddingException: 给定的最终块未在 CipherInputStream 中正确填充

javax.crypto.BadPaddingException: Given final block not properly padded in CipherInputStream

这个问题可能已经有人问过了,但由于我很难理解密码学,尤其是密钥处理,我不确定如何处理这个异常,也不知道在哪里/如何找到解决方案。

我正在尝试在加密文件中写入和读取对象。 为此,我将 CipherInputStream 包装在 DataInputStream 下。

我收到 BadPadding 异常,这可能意味着我对密钥的处理有误。

请帮帮我。

Main.java:

package main;

import java.util.Arrays;

import io.Io;

public class Main {
    static final String FIRST_TEXT_TO_CRYPTO = "First text";
    static final boolean BOOLEAN_CRYPTO = true;
    static final String SECOND_TEXT_TO_CRYPTO = "First text";
    static final String KEY = "key123";

    public static void main(String[] args) {
        Io.encryptAndWriteAll(Arrays.asList(new Stuff(FIRST_TEXT_TO_CRYPTO, BOOLEAN_CRYPTO, SECOND_TEXT_TO_CRYPTO)),
                KEY);

        for (Stuff stuff : Io.readAndDecryptAllFromLocation(KEY)) {
            System.out.println(stuff);
        }
    }
}

Stuff.java

package main;

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;

public class Stuff {
    SimpleStringProperty firstString = new SimpleStringProperty(), secondString = new SimpleStringProperty();
    SimpleBooleanProperty bool = new SimpleBooleanProperty();

    public Stuff(String firstString, boolean bool, String secondString) {
        this.firstString.set(firstString);
        this.secondString.set(secondString);
        this.bool.set(bool);
    }

    @Override
    public String toString() {
        return String.format("FirstString: %s; Boolean: %B; SecondString: %s", getFirstString(), isBool(),
                getSecondString());
    }

    public final SimpleStringProperty firstStringProperty() {
        return this.firstString;
    }

    public final String getFirstString() {
        return this.firstStringProperty().get();
    }

    public final void setFirstString(final String firstString) {
        this.firstStringProperty().set(firstString);
    }

    public final SimpleStringProperty secondStringProperty() {
        return this.secondString;
    }

    public final String getSecondString() {
        return this.secondStringProperty().get();
    }

    public final void setSecondString(final String secondString) {
        this.secondStringProperty().set(secondString);
    }

    public final SimpleBooleanProperty boolProperty() {
        return this.bool;
    }

    public final boolean isBool() {
        return this.boolProperty().get();
    }

    public final void setBool(final boolean bool) {
        this.boolProperty().set(bool);
    }

}

Io.java:

package io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;

import main.Stuff;

public class Io {
    private static final File FILE = new File("file.file");

    public static void encryptAndWriteAll(List<Stuff> stuffs, String key) {
        try (FileOutputStream fos = new FileOutputStream(FILE);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                CipherOutputStream cos = new CipherOutputStream(bos, new Crypto(key).getEncryptionCipher());
                DataOutputStream dos = new DataOutputStream(cos)) {
            for (Stuff stuff : stuffs) {
                dos.writeUTF(stuff.getFirstString());
                dos.writeBoolean(stuff.isBool());
                dos.writeUTF(stuff.getSecondString());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static List<Stuff> readAndDecryptAllFromLocation(String key) {
        try (FileInputStream fis = new FileInputStream(FILE);
                BufferedInputStream bis = new BufferedInputStream(fis);
                CipherInputStream cis = new CipherInputStream(bis, new Crypto(key).getDecryptionCipher());
                DataInputStream dis = new DataInputStream(cis)) {
            ArrayList<Stuff> stuffs = new ArrayList<>();
            try {
                for (;;)
                    // The next line throws
                    // ("at io.Io.readAndDecryptAllFromLocation(Io.java:52)")
                    stuffs.add(new Stuff(dis.readUTF(), dis.readBoolean(), dis.readUTF()));
            } catch (EOFException e) {
                System.out.println("EOF");
                e.printStackTrace();
            } catch (UTFDataFormatException e) {
                e.printStackTrace();
            }
            return stuffs;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Crypto.java:

package io;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";
    private byte[] paddedKey;

    public Crypto(String key) {
        paddedKey = addPaddingToKey(key);
    }

    private byte[] addPaddingToKey(String key) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            return Arrays.copyOf(digest.digest(key.getBytes()), 16);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Key getKey() {
        return new SecretKeySpec(paddedKey, ALGORITHM);
    }

    public Cipher getEncryptionCipher() {
        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, getKey());
            return cipher;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Cipher getDecryptionCipher() {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, getKey());
            return cipher;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

}

异常:

java.io.IOException: javax.crypto.BadPaddingException: Given final block not properly padded
    at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:121)
    at javax.crypto.CipherInputStream.read(CipherInputStream.java:239)
    at java.io.DataInputStream.readFully(DataInputStream.java:195)
    at java.io.DataInputStream.readUTF(DataInputStream.java:609)
    at java.io.DataInputStream.readUTF(DataInputStream.java:564)
    at io.Io.readAndDecryptAllFromLocation(Io.java:52)
    at main.Main.main(Main.java:17)
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:975)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:833)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at javax.crypto.Cipher.doFinal(Cipher.java:2048)
    at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:118)
    ... 6 more
Exception in thread "main" java.lang.NullPointerException
    at main.Main.main(Main.java:17)

您正在使用 AES/CBC,这需要 IV 参数。如果您在加密时未指定 IV 参数,则会生成并使用随机 IV。

您可以随机生成 IV 并将其添加到加密数据中,因为 IV 不需要保密。