Sealed Objects
The Java Cryptography Extension discussed in the last chapter provides a SealedObject class that can encrypt objects written onto an object output stream using any available cipher. Most of the time, I suspect, you'll either encrypt the entire object output stream by chaining it to a cipher output stream, or you won't encrypt anything at all. However, if there's some reason to encrypt only some of the objects you're writing to the stream, you can make them sealed objects.
The javax.crypto.SealedObject class wraps a serializable object in an encrypted digital lockbox. The sealed object is serializable so that it can be written onto object output streams and read from object input streams as normal. However, the object inside the sealed object can only be deserialized by someone who knows the key.
public class SealedObject extends Object implements Serializable
The big advantage to using sealed objects rather than encrypting the entire output stream is that the sealed objects contain all necessary parameters for decryption (algorithm used, initialization vector, salt, iteration count). All the receiver of the sealed object needs to know is the key; there doesn't necessarily have to be any prior agreement about these other aspects of encryption.
You seal an object with the SealedObject( ) constructor. The constructor takes as arguments the object to be sealed, which must be serializable, and the properly initialized Cipher object with which to encrypt the object:
public SealedObject(Serializable object, Cipher c) throws IOException, IllegalBlockSizeException
Inside the constructor, the object is immediately serialized by an object output stream chained to a byte array output stream. The byte array is then stored in a private field that is encrypted using the Cipher object c. The cipher's algorithms and parameters are also stored. Thus, the state of the original object written onto the ultimate object output stream is the state of the object when it was sealed; subsequent changes it may undergo between being sealed and being written are not reflected in the sealed object. Since serialization takes place immediately inside the constructor, the constructor throws a NotSerializableException if the object argument cannot be serialized. It throws an IllegalBlockSizeException if c is a block cipher with no padding and the length of the serialized object's contents is not an integral multiple of the block size.
You unseal an object by first reading the sealed object from an object input stream and then invoking one of the three getObject( ) methods to return the original object. All of these methods require you to supply a key and an algorithm.
Example 13-8 is a very simple program that writes an encrypted java.awt.Point object into the file point.des. First a file output stream is opened to the file point.des and then chained to the ObjectOutputStream oin. As in the last chapter, a fixed DES key called desKey is built from a fixed array of bytes and used to construct a Cipher object called des. des is initialized in encryption mode with the key. Finally, both the des Cipher object and the Point object tdp are passed into the SealedObject( ) constructor to create a SealedObject so. Since SealedObject implements Serializable, this can be written on the ObjectOutputStream oout as any other serializable object. At this point, this program closes oout and exits. However, the same Cipher object des could be used to create more sealed objects from serializable objects, and these could also be written onto the stream if you had more objects to serialize.
Example 13-8. SealedPoint
import java.security.*; import java.io.*; import javax.crypto.*; import javax.crypto.spec.*; import java.awt.*; public class SealedPoint { public static void main(String[] args) throws GeneralSecurityException, IOException { Point tdp = new Point(32, 45); FileOutputStream fout = new FileOutputStream("point.des"); ObjectOutputStream oout = new ObjectOutputStream(fout); // Create a key. byte[] desKeyData = {(byte) 0x90, (byte) 0x67, (byte) 0x3E, (byte) 0xE6, (byte) 0x42, (byte) 0x15, (byte) 0x7A, (byte) 0xA3 }; DESKeySpec desKeySpec = new DESKeySpec(desKeyData); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey desKey = keyFactory.generateSecret(desKeySpec); // Use Data Encryption Standard. Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding"); des.init(Cipher.ENCRYPT_MODE, desKey); SealedObject so = new SealedObject(tdp, des); oout.writeObject(so); oout.close( ); } } |
Reading a sealed object from an object input stream is easy. You read it exactly as you read any other object from an object input stream. For example:
FileInputStream fin = new FileInputStream(filename); ObjectInputStream oin = new ObjectInputStream(fin); SealedObject so = (SealedObject) oin.readObject( );
Once you've read the object, unsealing it to retrieve the original object is straightforward, provided you know the key. Three getObject( ) methods return the original object:
public final Object getObject(Key key) throws IOException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeyException public final Object getObject(Cipher c) throws IOException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException public final Object getObject(Key key, String provider) throws IOException, ClassNotFoundException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException
The first variant is the most useful since it only requires the key. It does not require you to create and initialize a Cipher object. You will need to know the algorithm in order to know what kind of key to create, but that information is available from the getAlgorithm( ) method:
public final String getAlgorithm( )
For example:
if (so.getAlgorithm( ).startsWith("DES")) { byte[] desKeyData = {(byte) 0x90, (byte) 0x67, (byte) 0x3E, (byte) 0xE6, (byte) 0x42, (byte) 0x15, (byte) 0x7A, (byte) 0xA3, }; DESKeySpec desKeySpec = new DESKeySpec(desKeyData); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey desKey = keyFactory.generateSecret(desKeySpec); Object o = so.getObject(desKey); }
Example 13-9 reads the sealed object from the point.des file written by Example 13-8, unseals the object, and prints it on System.out.
Example 13-9. UnsealPoint
import java.security.*; import java.io.*; import javax.crypto.*; import javax.crypto.spec.*; import java.awt.*; public class UnsealPoint { public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { FileInputStream fin = new FileInputStream("point.des"); ObjectInputStream oin = new ObjectInputStream(fin); // Create a key. byte[] desKeyData = {(byte) 0x90, (byte) 0x67, (byte) 0x3E, (byte) 0xE6, (byte) 0x42, (byte) 0x15, (byte) 0x7A, (byte) 0xA3 }; DESKeySpec desKeySpec = new DESKeySpec(desKeyData); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey desKey = keyFactory.generateSecret(desKeySpec); SealedObject so = (SealedObject) oin.readObject( ); Point p = (Point) so.getObject(desKey); System.out.println(p); oin.close( ); } } |