为什么我的AES加密会抛出InvalidKeyException异常?

46

我正在开发一个加密/解密特定文件的函数,使用秘钥进行加解密。我编写了三个类,一个用于生成秘钥,一个用于使用秘钥加密文件,还有一个用于解密。

生成秘钥和加密文件都正常工作,但是当我尝试解密文件时,在第c.init(Cipher.DECRYPT_MODE, keySpec);行抛出异常:

java.security.InvalidKeyException: Parameters missing

我觉得我在将秘钥流传到我的byte[]时做错了什么,或者在解密文件时出现了问题。

以下是三个类的简单说明:KeyHandler创建AES秘钥并将其存储在硬盘上。当前为测试目的,键/明文/加密/解密文件的名称是硬编码的。

EncryptionHandler将磁盘上的.txt文件转换为字节,使用秘钥加密文件,然后使用CipherOutputStream将加密的字节写入磁盘。

DecryptionHandler当然是EncryptionHandler的相反操作。

代码如下:

    public class KeyHandler {
        Scanner scan = new Scanner(System.in);

        public KeyHandler(){
            try {
                startMenu();
            } catch (Exception e) {
                System.out.println("fel någonstanns :)");
            }
        }

        public void startMenu() throws Exception{

            System.out.println("Hej. Med detta program kan du generera en hemlig nyckel"+"\n"+"Vill du:"+"\n"+ "1. Generera en nyckel"+"\n"+"2. Avsluta");
            int val=Integer.parseInt(scan.nextLine());
        do{ 
            switch (val){
            case 1: generateKey(); break;
            case 2: System.exit(1);

            default: System.out.println("Du måste välja val 1 eller 2");
            }
        }
            while (val!=3);
        }

        public void generateKey() throws Exception{
            try {
                KeyGenerator gen = KeyGenerator.getInstance("AES");
                gen.init(128);

                SecretKey key=gen.generateKey();
                byte[] keyBytes = key.getEncoded();
                System.out.print("Ge nyckeln ett filnamn: ");
                String filename = scan.next();
                System.out.println("Genererar nyckeln...");
                FileOutputStream fileOut = new FileOutputStream(filename);
                fileOut.write(keyBytes);
                fileOut.close();
                System.out.println("Nyckeln är genererad med filnamnet: "+filename+"...");
                System.exit(1);
                 } catch (NoSuchAlgorithmException e) {
                    }

        }

        public static void main(String[] args){
            new KeyHandler();
        }

    }


    public class EncryptHandler {
        private String encryptedDataString;
        private Cipher ecipher; 

        AlgorithmParameterSpec paramSpec;
        byte[] iv;

        public EncryptHandler(String dataString, String secretKey, String encryptedDataString){
            this.encryptedDataString=encryptedDataString;
            try {
                encryptFile(dataString, secretKey);
            } catch (Exception e) {

            }
        }

            public void encryptFile(String dataString, String secretKey) throws Exception{

                    FileInputStream fis = new FileInputStream(secretKey);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();

                    int theByte = 0;
                    while ((theByte = fis.read()) != -1)
                    {
                      baos.write(theByte);
                    }
                    fis.close();

                    byte[] keyBytes = baos.toByteArray();
                    baos.close();
                    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

                try 
                { 
                ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
                ecipher.init(Cipher.ENCRYPT_MODE, keySpec);     

                } 
                catch (Exception e) 
                    { 
                e.printStackTrace(); 
            } 

                System.out.println("Encrypting file...");
                try 
                { 

                    encryptStream(new FileInputStream(dataString),new FileOutputStream(encryptedDataString)); 
                }
                catch(Exception e){
                e.printStackTrace();
                }

                }
    public void encryptStream(InputStream in, OutputStream out){
                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
                byte[] buf = new byte[1024]; 
                try { 
                out = new CipherOutputStream(out, ecipher); 

             // read the cleartext and write it to out
                int numRead = 0; 
                while ((numRead = in.read(buf)) >= 0) {
                out.write(buf, 0, numRead); 

                }
                bOut.writeTo(out);
                out.close();
                bOut.reset();

                } 
                catch (java.io.IOException e) 
                { 
                } 

                }


        public static void main(String[] args){
            String data = "test.txt";
            String keyFileName="a";
            String encryptedFile="krypterad.txt";
            //String encryptedFile =args[2];
            new EncryptHandler(data, keyFileName, encryptedFile);
        }

    }


public class DecryptHandler {
    public DecryptHandler(){

    try {
        decryptFile();
    } catch (Exception e) {
        System.out.println("något gick fel :) ");
        }
    }


    public void decryptFile()throws Exception{
        byte[] buf = new byte[1024]; 
        String keyFilename = "hemlig";
        FileInputStream fis = new FileInputStream(keyFilename);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        int theByte = 0;
        while ((theByte = fis.read()) != -1)
        {
          baos.write(theByte);
        }
        fis.close();

        byte[] keyBytes = baos.toByteArray();
        baos.close();
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");


        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
                System.out.println("här");
        c.init(Cipher.DECRYPT_MODE, keySpec); 

        System.out.println("Decrypting file...");
        try{
        decryptStream(new FileInputStream("krypterad.txt"),new FileOutputStream("Dekryperad.txt"), c, buf);
        }
            catch (java.io.IOException e){

            }
            System.out.println("File decrypted!");
        }
    public void decryptStream(InputStream in, OutputStream out, Cipher dcipher, byte[] buf){
        try 
        { 

        in = new CipherInputStream(in, dcipher); 

        // Read in the decrypted bytes and write the cleartext to out 
        int numRead = 0; 


        while ((numRead = in.read(buf)) >= 0) 
        { 
        out.write(buf, 0, numRead);

        } 
        out.close();


        } 
        catch (java.io.IOException e){ 

        } 
    } 
    public static void main(String[]args){
        new DecryptHandler();
    }
}

2
你能贴出堆栈跟踪吗?这样会更容易 :] - zacheusz
抱歉 :) java.security.InvalidKeyException:缺少参数。 - John Snow
我的错,使用密码块链接模式需要使用IV。我改用不需要IV的ECB模式。 - John Snow
请注意,ECB模式不安全,会泄露信息。在维基百科页面 http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 上有一个很好的(确切地说是图形化的)信息泄漏示例。为了获得更好的安全性,请使用CBC或CTR模式。 - rossum
1个回答

61
如果您使用类似CBC的块链接模式,则还需要向Cipher提供IvParameterSpec。因此,您可以像这样初始化IvParameterSpec:
    // build the initialization vector.  This example is all zeros, but it 
    // could be any value or generated using a random number generator.
    byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    IvParameterSpec ivspec = new IvParameterSpec(iv);

那么对于加密,修改您初始化密码器的代码如下:

ecipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);

当你解密时:

c.init(Cipher.DECRYPT_MODE, keySpec, ivspec);

因此,您的完整代码应看起来像这样(对我有效):

    public class KeyHandler {

    Scanner scan = new Scanner(System.in);

    public KeyHandler() {
        try {
            startMenu();
        } catch (Exception e) {
            System.out.println("fel någonstanns :)");
        }
    }

    public void startMenu() throws Exception {

        System.out.println("Hej. Med detta program kan du generera en hemlig nyckel" + "\n" + "Vill du:" + "\n" + "1. Generera en nyckel" + "\n" + "2. Avsluta");
        int val = Integer.parseInt(scan.nextLine());
        do {
            switch (val) {
                case 1:
                    generateKey();
                    break;
                case 2:
                    System.exit(1);

                default:
                    System.out.println("Du måste välja val 1 eller 2");
            }
        } while (val != 3);
    }

    public void generateKey() throws Exception {
        try {
            KeyGenerator gen = KeyGenerator.getInstance("AES");
            gen.init(128);

            SecretKey key = gen.generateKey();
            byte[] keyBytes = key.getEncoded();
            System.out.print("Ge nyckeln ett filnamn: ");
            String filename = scan.next();
            System.out.println("Genererar nyckeln...");
            FileOutputStream fileOut = new FileOutputStream(filename);
            fileOut.write(keyBytes);
            fileOut.close();
            System.out.println("Nyckeln är genererad med filnamnet: " + filename + "...");
            System.exit(1);
        } catch (NoSuchAlgorithmException e) {
        }

    }

    public static void main(String[] args) {
        new KeyHandler();
    }
}

public class EncryptHandler {

    private String encryptedDataString;
    private Cipher ecipher;
    AlgorithmParameterSpec paramSpec;
    byte[] iv;

    public EncryptHandler(String dataString, String secretKey, String encryptedDataString) {
        this.encryptedDataString = encryptedDataString;
        try {
            encryptFile(dataString, secretKey);
        } catch (Exception e) {
        }
    }

    public void encryptFile(String dataString, String secretKey) throws Exception {

        FileInputStream fis = new FileInputStream(secretKey);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        int theByte = 0;
        while ((theByte = fis.read()) != -1) {
            baos.write(theByte);
        }
        fis.close();

        byte[] keyBytes = baos.toByteArray();
        baos.close();
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

        // build the initialization vector.  This example is all zeros, but it 
        // could be any value or generated using a random number generator.
        byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);

        try {
            ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            ecipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Encrypting file...");
        try {

            encryptStream(new FileInputStream(dataString), new FileOutputStream(encryptedDataString));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void encryptStream(InputStream in, OutputStream out) {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        try {
            out = new CipherOutputStream(out, ecipher);

            // read the cleartext and write it to out
            int numRead = 0;
            while ((numRead = in.read(buf)) >= 0) {
                out.write(buf, 0, numRead);

            }
            bOut.writeTo(out);
            out.close();
            bOut.reset();

        } catch (java.io.IOException e) {
        }

    }

    public static void main(String[] args) {
        String data = "test.txt";
        String keyFileName = "a";
        String encryptedFile = "krypterad.txt";
        //String encryptedFile =args[2];
        new EncryptHandler(data, keyFileName, encryptedFile);
    }
}

public class DecryptHandler {

    public DecryptHandler() {

        try {
            decryptFile();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("något gick fel :) ");
        }
    }

    public void decryptFile() throws Exception {
        byte[] buf = new byte[1024];
        String keyFilename = "hemlig";
        FileInputStream fis = new FileInputStream(keyFilename);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        int theByte = 0;
        while ((theByte = fis.read()) != -1) {
            baos.write(theByte);
        }
        fis.close();

        byte[] keyBytes = baos.toByteArray();
        baos.close();
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

        // build the initialization vector.  This example is all zeros, but it 
        // could be any value or generated using a random number generator.
        byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);

        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        System.out.println("här");
        c.init(Cipher.DECRYPT_MODE, keySpec, ivspec);

        System.out.println("Decrypting file...");
        try {
            decryptStream(new FileInputStream("krypterad.txt"), new FileOutputStream("Dekryperad.txt"), c, buf);
        } catch (java.io.IOException e) {
        }
        System.out.println("File decrypted!");
    }

    public void decryptStream(InputStream in, OutputStream out, Cipher dcipher, byte[] buf) {
        try {

            in = new CipherInputStream(in, dcipher);

            // Read in the decrypted bytes and write the cleartext to out 
            int numRead = 0;


            while ((numRead = in.read(buf)) >= 0) {
                out.write(buf, 0, numRead);

            }
            out.close();


        } catch (java.io.IOException e) {
        }
    }

    public static void main(String[] args) {
        new DecryptHandler();
    }
}

2
我意识到CBC需要一个IV,所以我改成了ECB模式。无论如何,对于我想要完成的目标来说,ECB模式已经足够了。感谢您的回复。 - John Snow
@Jimmy:你不应该使用ECB模式,它是不安全的。相反,生成一个随机IV并将其存储为文件的第一个块。(这只有16字节的开销。) - Paŭlo Ebermann
如何生成随机IV并将其存储在文件的第一个块中,这比ECB模式更安全,因为IV是可见的? - adamitj
1
根据这个页面的说法,IV不需要保密,只需要是随机和不可预测的:https://defuse.ca/cbcmodeiv.htm - L. Holanda
请注意,CBC使用前一个区块的密文作为下一个区块的IV,而不像ECB一样为每个区块使用相同的IV。这导致输出具有更多的伪随机性。至于IV的存储,Bruce Schneier是这样说的。如果您隐藏了第一个IV,并且您的密码文本长度为N个块,因为每个密码文本块都是下一个块的IV,所以您仍然可以看到N-1个IV。 - techdude

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接