为啥我的 AES 加密会引发 InvalidKeyException?

Posted

技术标签:

【中文标题】为啥我的 AES 加密会引发 InvalidKeyException?【英文标题】:Why does my AES encryption throws an InvalidKeyException?为什么我的 AES 加密会引发 InvalidKeyException? 【发布时间】:2011-10-03 21:27:11 【问题描述】:

我目前正在开发一个使用密钥加密/解密特定文件的功能。我写了三个类,一个生成密钥,一个用密钥加密文件,一个解密。

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

java.security.InvalidKeyException:缺少参数

我认为我在将密钥流式传输到我的 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();
    

【问题讨论】:

你能发布堆栈跟踪吗?会更容易:] 抱歉 :) java.security.InvalidKeyException: 缺少参数 是我的错,当使用密码块链接时,需要使用 IV。我更改为不需要 IV 的 ECB 模式 请记住,ECB 模式不安全,它会泄露信息。***页面 en.wikipedia.org/wiki/…> 上有一个很好的说明(字面意思)信息泄露。为了更好的安全性,请使用 CBC 或 CTR 模式。 【参考方案1】:

如果您使用像 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();
    

【讨论】:

意识到 CBC 请求者和 IV 所以我更改为 ECB 模式。无论如何,欧洲央行模式对于我想要完成的事情来说都很好。感谢您的回复 @Jimmy:你不应该使用 ECB 模式,它不安全。相反,生成一个随机 IV 并将其存储为文件的第一个块。 (这只是 16 字节的开销。) 如何生成随机 IV 并将其存储在文件的第一个块中会比 ECB 模式更安全,因为 IV 是可见的? @adamitj 根据this page IV不需要保密。它所需要的只是随机且不可预测:defuse.ca/cbcmodeiv.htm 请注意,CBC 使用前一个块的密文作为下一个块的 IV,不像 ECB 对每个块使用相同的 IV。这导致输出的伪随机性更高。至于 IV 的存储,Bruce Schneier 是这样说的。如果你隐藏了第一个 IV,并且你的密文是 N 块长,因为每个密文块都是下一个块的 IV,你仍然有 N-1 个 IV 可见。

以上是关于为啥我的 AES 加密会引发 InvalidKeyException?的主要内容,如果未能解决你的问题,请参考以下文章

java aes加密与网上在线加密不同。谁能告诉我为啥?求个正确的例子,谢谢了!

Python解压AES-128加密文件

Amazon S3 存储桶加密 - KMS 与 AES 256

php openssl aes-256-cbc key长度自动匹配了128的长度,为啥

为啥windows下aes解密android上的加密文件失败

PHP AES 加密 PKCS5Padding