What is a Cipher stream?
Cipher streams act as streams except that they use a Cipher to process the data before it is passed to the underlying stream. This allows you to encrypt and decrypt data as you process it. One use of this is that you can encrypt data being saved to disc as you are writing it. You can also decrypt the data again when you read it back in.
Cipher streams can prove very useful if you know how to use them. In brief, in order to use them you need to:
- Create a Key
- Create a Cipher for encryption / decryption
- Create a CipherInputStream or CipherOutputStream
How can I specify a key for the cipher?
When you wish to encrpyt or decrypt data you need a key to “lock” and “unlock” the data. It should go without saying you need the same key to decrypt data as you did to encrypt it. Generating a key can be done in 3 easy steps.
- Get an instance of KeyGenerator for your chosen encryption type. There are several types that are supported by all implementations of the Java Platform (from version 1.4). You just need to pass the type as a string to the getInstance method (found at the above link).
For example to get a generator for AES you would do the following.KeyGenerator kg = KeyGenerator.getInstance("AES");
- Next you need to initialise the key generator. There are several init methods with different signatures to give greater or lesser control. The one I will be focusing on for simplicity is the following method.
KeyGenerator.init(SecureRandom random)
When you initialise the key generator you use a SecureRandom object to create a random number generator for the key generator. Again there are several constructors for this but the one I will details accepts a byte array. This byte array is used as the seed for the key. In order to get the correct key for encryption and decryption you need to provide the same byte array for each key.
For example to create a key generator with a specified key you would do the following.kg.init(new SecureRandom(new byte[]{7, 2, 3}));
- Finally you need to get the SecretKey you will use with the Cipher. This step is the simplest as you only need to do the following.
SecretKey key = kg.generateKey();
With that final step you will have generate the key required for encryption and decryption.
How can I create a Cipher for encrpytion?
To encrypt data you will need to create a Cipher for the correct type for the data.
Creating the Cipher is done in 2 steps.
- First we get an instance of Cipher for your chosen encryption type. There are several types that are supported by all implementations of the Java Platform (from version 1.4). You just need to pass the type as a string to the getInstance method (found at the above link).
For example to get a Cipher for AES you would do the following.Cipher c = Cipher.getInstance("AES");
- Second we need to initialise the Cipher. When initialising the Cipher you specify the mode it should use and the key.
For example to get a Cipher to encrypt data you would do the following:c.init(Cipher.ENCRYPT_MODE, key);
Now you have a Cipher you can use them with Cipher Streams.
How can I create a Cipher for decrpytion?
To decrypt data you will need to create a Cipher for the correct type for the data.
Creating the Cipher is done in 2 steps.
- First we get an instance of Cipher for your chosen encryption type. There are several types that are supported by all implementations of the Java Platform (from version 1.4). You just need to pass the type as a string to the getInstance method (found at the above link).
For example to get a Cipher for AES you would do the following.Cipher c = Cipher.getInstance("AES");
- Second we need to initialise the Cipher. When initialising the Cipher you specify the mode it should use and the key.
For example to get a Cipher to decrypt data you would do the following.c.init(Cipher.DECRYPT_MODE, key);
Now you have a Cipher you can use them with Cipher Streams.
How can I create a CipherInputStream and CipherOutputStream?
As mentioned previously CipherInputStream and CipherOutputStream both function as input and output streams with one addition, they use a Cipher to process the data before it is passed to the underlying stream. This means that once you have created the object you can treat it as a stream knowing that the data is being encrypted or decrypted.
Both of these classes have the same constructor input, the first being a Stream to match the Cipher object (InputStream for CipherInputStream and OutputStream for CipherOutputStream).
Below is an example using both of these objects to write an encrypted string to a file then read back in the decrypted string.
import javax.crypto.*; import java.io.*; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; public class CipherStreamExample { public static void main(final String[] args) { final CipherStreamExample cip = new CipherStreamExample(); cip.runExperiments(); } private void runExperiments() { CipherOutputStream output = null; CipherInputStream input = null; FileOutputStream fileOutput = null; FileInputStream fileInput = null; try { fileOutput = new FileOutputStream("CipherOutput.txt"); fileInput = new FileInputStream("CipherOutput.txt"); final KeyGenerator kg = KeyGenerator.getInstance("AES"); kg.init(new SecureRandom(new byte[]{1, 2, 3})); final SecretKey key = kg.generateKey(); final Cipher c = Cipher.getInstance("AES"); c.init(Cipher.ENCRYPT_MODE, key); output = new CipherOutputStream(fileOutput, c); final PrintWriter pw = new PrintWriter(output); pw.println("Cipher Streams are working correctly."); pw.flush(); pw.close(); final KeyGenerator kg2 = KeyGenerator.getInstance("AES"); kg2.init(new SecureRandom(new byte[]{1, 2, 3})); final SecretKey key2 = kg2.generateKey(); final Cipher c2 = Cipher.getInstance("AES"); c2.init(Cipher.DECRYPT_MODE, key2); input = new CipherInputStream(fileInput, c2); final InputStreamReader r = new InputStreamReader(input); final BufferedReader reader = new BufferedReader(r); final String line = reader.readLine(); System.out.println("Line : " + line); } catch (NoSuchAlgorithmException e) { System.out.println("Specified Algorithm does not exist"); } catch (NoSuchPaddingException e) { System.out.println("Specified Padding does not exist"); } catch (FileNotFoundException e) { System.out.println("Could not find specified file to read / write to"); } catch (InvalidKeyException e) { System.out.println("Specified key is invalid"); } catch (IOException e) { System.out.println("IOException from BufferedReader when reading file"); } finally { try { if (fileInput != null) { fileInput.close(); } if (fileOutput != null) { fileOutput.flush(); fileOutput.close(); } if (output != null) { output.flush(); output.close(); } if (input != null) { input.close(); } }catch (IOException e) { e.printStackTrace(); } } } }