Encryption/Decryption
The JCE supports encryption and decryption using symmetric algorithms (such as DES and RC4) and asymmetric algorithms (such as RSA and ElGamal). The algorithms may be stream or block ciphers, with each algorithm supporting different modes, padding or even algorithm-specific parameters.
This section details the following:
>Cipher Input and Output Streams
The Cipher Class
The basic interface used to encipher or decipher data is the javax.crypto.Cipher class. The class provides the necessary mechanism for encrypting and decrypting data using arbitrary algorithms from any of the installed providers.
To create a Cipher instance, use one of the Cipher.getInstance() methods. This method will accept a transformation string and an optional provider name. The transformation string is used to specify the encryption algorithm as well as the cipher mode and padding. The transformation is specified in the form:
>"algorithm"
>"algorithm/mode/padding"
In the first instance, we are requesting the algorithm with its default mode and padding mechanism. The second instance fully qualifies all options. For a list of support algorithms consult the provider's documentation. Some common transformations are:
>"RC4"
>"DES/CBC/PKCS5Padding"
>"RSA/ECB/PKCS1Padding"
The following code will create a cipher for performing RC4 encryption or decryption, a cipher for doing RSA in ECB mode with PKCS#1 padding provided by the ABA provider and a cipher for performing DESede encryption/decryption in CBC mode with PKCS#5 padding:
Cipher rc4Cipher = Cipher.getInstance("RC4");
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
Cipher desEdeCipher =
Cipher.getInstance("DESede/CBC/PKCS5Padding");
Once we have a Cipher instance, we will need to initialize the Cipher for encryption or decryption. We will also need to provide a Key (see Key Management).
Key desKey, rsaKey;
desCipher.init(Cipher.ENCRYPT_MODE, desKey);
rsaCipher.init(Cipher.DECRYPT_MODE, rsaKey);
As you can see, the first value passed to the Cipher.init() method indicates whether we are initializing for encryption or decryption. The second argument provides the key to use during encryption or decryption.
There are a number of other initialization methods for providing algorithm specific parameters (such as Initialization Vectors, the number of rounds to use etc.). See Algorithm Parameters for more information.
Now that our Cipher is initialized, we can start processing data. To do so we use the Cipher.update() and Cipher.doFinal() methods. The Cipher.update() methods may be used to incrementally process data. Once all the data is processed, one of the Cipher.doFinal() methods must be called.
In the simplest usage, a single Cipher.doFinal() call may be passed all the data:
byte[] plainText = "hello world".getBytes();
byte[] cipherText = desCipher.doFinal(plainText);
Once the Cipher.doFinal() method has been called, the Cipher instance will be reset to the state it was in after the last call to the Cipher.init() method. That means the Cipher may be reused to encipher or decipher more data using the same Key and parameters that were specified in the initialization.
Cipher Input and Output Streams
Rather than deal with the complications of buffering enciphered or deciphered data produced by the Cipher.update() methods, it may be desirable to use a Java Input/Output Stream type interface. Fortunately, the JCE provides such a mechanism.
The javax.crypto.CipherInputStream and javax.crypto.CipherOutputStream are based on the Java IO filter streams. This allows them to process data and pass on that data to an underlying stream.
To create a cipher stream, firstly create and initialize a javax.crypto.Cipher instance and the underlying stream and then instantiate the required stream type with these two arguments.
For example, the following code fragment will create a CipherOutputStream that will encipher its data (using DES) and pass the result to a ByteArrayOutputStream. We can access the ciphertext by calling ByteArrayOutputStream.toByteArray().
Key desKey;
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, desKey);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
CipherOutputStream cout =
new CipherOutputStream(bout, cipher);
cout.write("hello world".getBytes());
cout.close();
byte[] cipherText = bout.toByteArray();
Once we can encipher and decipher data using a simple stream, interface, we can create much more complicated scenarios. For example, the OutputStream could just as easily be a SocketOutputStream, or we could construct an ObjectOutputStream on top of our cipher stream and encipher Java objects directly.
SealedObject
The javax.crypto.SealedObject class provides the mechanism to encipher a Serializable object. This class allows the application to encipher a Java object and then recover the object, all through a simple interface. The SealedObject is also serializable, to simplify the transport and storage of the enciphered objects.
A SealedObject can be constructed through either serialization or by its constructor. The constructor is used to create a new enciphered object. The constructor's arguments are the object to encipher and the Cipher to use. The provided Cipher instance must be initialized for encryption before the SealedObject is created. This means calling a Cipher.init() method with Cipher.ENCRYPT_MODE as the mode, the required encryption Key and any algorithm parameters.
The following fragment will create a new SealedObject containing the enciphered String "hello world":
Key desKey = ...
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, deskey);
SealedObject so = new SealedObject("hello world", cipher);
To recover the original object, the SealedObject.getObject() methods may be used. These methods take either a Cipher or Key object. When providing the Cipher parameter, the instance must be initialized in the Cipher.DECRYPT_MODE mode, with the appropriate decryption key and the same algorithm parameters as the original Cipher. When providing a Key parameter, the encryption algorithm and algorithm parameters are extracted from the SealedObject.
The following fragment will extract a SealedObject object from an ObjectInputStream and then recover the protected object:
ObjectInputStream oin ...
Key desKey = ...
SealedObject so = (SealedObject)oin.readObject();
String plainText = (String)so.getObject(deskey);
One important security aspect to note with this class is that it does not use a digital signature to ensure the object has not been tampered with in its serialized form. It is therefore possible that the object could be altered in storage or transport without detection. Fortunately, the JCA provides the java.security.SignedObject mechanism, which can be used in conjunction with the SealedObject class to avoid this problem. (See Key Conversion for a discussion on the SignedObject class).
Algorithm Parameters
Some cipher algorithms support parameterization. For example, the DES cipher in CBC mode can have an initialization vector as an algorithm parameter and other ciphers may have a selectable block size or round count. The JCE provides support for algorithm-independent initialization via the java.security.spec.AlgorithmParameterSpec and java.security.AlgorithmParameters classes.
The java.security.spec.AlgorithmParameterSpec derived classes can be constructed programatically by an application. The following classes are provided by the JCA/JCE:
java.security.spec | |
---|---|
DSAParameterSpec | Used to specify the parameters used with the DSA algorithm. The parameters consist of the base g, prime p and sub-prime q. |
javax.crypto.spec | |
---|---|
DHGenParameterSpec | The set of parameters used for generating Diffie-Hellman parameters for use in Diffie-Hellman key agreement. |
DHParameterSpec | The set of parameters used with Diffie-Hellman as specified in PKCS#3. |
IvParameterSpec | An initialization vector for use with a feedback cipher. That is an array of bytes of length equal to the block size of the cipher. |
RC2ParameterSpec | Parameters for the RC2 algorithm. The parameters are the effective key size and an optional 8-byte initialization vector (only in feedback mode). |
RC5ParameterSpec | Parameters for the RC5 algorithm. The parameters are a version number, number of rounds, a word size and an optional initialization vector (only in feedback mode). |
Your provider may also include more classes for passing parameters to the algorithms it implements.
The JCA also has mechanisms for dealing with the provider-dependent AlgorithmParameters. This class is used as an opaque representation of the parameters for a given algorithm and allows an application to store persistently the parameters used by a Cipher.
There are three situations where an application may encounter an AlgorithmParameters instance:
1.Cipher.getParameters()
After a Cipher has been initialized, it may have generated a set of parameters (based on supplied and/or default values). The value returned by the getParameters() method allows the Cipher to be re-initialized to exactly the same state.
2.AlgorithmParameters.getInstance()
Rather than generating the parameters via the Cipher class, it is possible to generate them either based on an encoded format or an AlgorithmParameterSpec instance. To do so create an uninitialized instance using the getInstance method and then initialize it by calling the appropriate init() method.
3.AlgorithmParameterGenerator.getParameters()
Finally, a set of parameters can be generated using the AlgorithmParameterGenerator. First, a generator is created for the required algorithm using the getInstance() method. Then the generator is initialized by calling one of the init() methods, finally to create the instance use the getParameters method.
This class provides the concept of algorithm-independent parameter generation, in that the initialization can be based on a "size" and a source of randomness. In this case the "size" value is interpreted differently for each algorithm.