46

I'm currently working on a function that encrypt/decrypts a specific file with a secret key. I have written three classes, one which generates a key, one which encrypts a file with the key and one that decrypts.

Generating the key and encrypting the file works fine, but when I try to decrypt the file, an exception is thrown at line: c.init(Cipher.DECRYPT_MODE, keySpec);:

java.security.InvalidKeyException: Parameters missing

I take it I've done something wrong when streaming the secret key to my byte[] or something is wrong when decrypting the file.

Quick explanation of the three classes: KeyHandler creates a AES key and stores it on the harddrive. The name of the key/plaintext/encrypted/decrypted files is currently hardcoded for testing purposes.

EncryptionHandler transfers a .txt file on the disk into bytes, encrypts the file with the secret key and then writes the encrypted bytes to the disk using CipherOutputStream.

DecryptionHandler of course does the opposite of EncryptionHandler.

Here's the code:

    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();
    }
}
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
John Snow
  • 5,214
  • 4
  • 37
  • 44
  • 2
    could you post stacktrace? it will be easier :] – zacheusz Jul 12 '11 at 18:32
  • Sorry about that :) java.security.InvalidKeyException: Parameters missing – John Snow Jul 12 '11 at 18:46
  • Was my bad, when using Cipher Block Chaining an IV needs to be used. I changed to ECB mode which doesn't require an IV – John Snow Jul 12 '11 at 18:59
  • Bear in mind that ECB mode is not secure, it leaks information. There is an excellent illustration (literally) of the information leakage on the Wikipedia page . For better security use CBC or CTR modes. – rossum Jul 12 '11 at 22:11

1 Answers1

61

If you use a block-chaining mode like CBC, you need to provide an IvParameterSpec to the Cipher as well.

So you can initialize an IvParameterSpec like this:

    // 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);

Then for encryption, change your code where you init the cipher to this:

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

And where you decrypt:

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

So your complete code should look like this (It works for me):

    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();
    }
}
Riaan Cornelius
  • 1,933
  • 1
  • 14
  • 17
  • 2
    Realized that CBC requiers and IV so I changed to ECB mode. Anyhow ECB mode will do fine for what im trying to acomplish. Thanks for your reply – John Snow Jul 12 '11 at 19:48
  • @Jimmy: You should not use ECB mode, it is insecure. Instead, generate a random IV and store it as the first block of your file. (This is only 16 byte overhead.) – Paŭlo Ebermann Aug 24 '11 at 00:22
  • How generating a random IV and storing it in the first block of the file would be more secure than ECB mode since the IV is visible? – adamitj Apr 06 '15 at 23:09
  • 1
    @adamitj According to this page IV does not need to be secret. All it needs is to be random and unpredictable: https://defuse.ca/cbcmodeiv.htm – L. Holanda Aug 30 '16 at 21:33
  • Note that CBC uses the ciphertext of the previous block as the IV for the next block, unlike ECB which uses the same IV for each block. This results in more pseudo-randomness of the output. As for storage of the IV, Bruce Schneier puts it this way. If you hide the first IV, and your cirpher text is N block long, because each block of ciphertext is the IV for the next block you still have N-1 IVs visible. – techdude Dec 21 '16 at 18:47