A
program that illustrates the use of the Externalizable
interface is given here. Note that the program uses Java’s security API. We need not worry about the security code
while learning the importance of the Externalizable interface.
Program Code
import java.io.*;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
public class ExternalizableTestApp {
public static void main(String args[]) throws IOException {
try {
Customer customer = new Customer(1, "1234-5678-9876"); System.out.println("Before saving object: "); System.out.println("ID:" + customer.getId() + " CC:" + customer.getCreditCard());
ObjectOutputStream outStream = new ObjectOutputStream(
new FileOutputStream("customer.dat")); outStream.writeObject(customer); outStream.close();
ObjectInputStream inputStream = new ObjectInputStream( new FileInputStream("customer.dat")); customer = (Customer) inputStream.readObject();
System.out.println("After retrieving object: ");
System.out.println("ID:" + customer.getId() + " CC:" + customer.getCreditCard());
inputStream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
class Customer implements Externalizable {
private int id;
private String creditCard;
private static Cipher cipher;
private static SecretKeySpec skeySpec;
static {
try {
createCipher();
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
public String getCreditCard() {
return creditCard;
}
public int getId() {
return id;
}
public Customer() {
id = 0;
creditCard = "";
}
public Customer(int id, String ccNumber) {
this.id = id;
this.creditCard = ccNumber;
}
public void writeExternal(ObjectOutput out) throws IOException {
try {
out.write(id);
encrypt();
out.writeUTF(creditCard);
System.out.println("After encryption: ");
System.out.println("ID:" + id + " CC:" + creditCard); } catch (Exception ex) {
ex.printStackTrace();
} }
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
try {
id = in.read();
String str = in.readUTF();
decrypt(str);
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static void createCipher() throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
skeySpec = new SecretKeySpec(raw, "AES");
// Instantiate the cipher
cipher = Cipher.getInstance("AES");
}
private void encrypt() throws Exception {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] buff = cipher.doFinal(creditCard.getBytes());
creditCard = new String(buff);
}
private void decrypt(String str) throws Exception {
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] buff = cipher.doFinal(str.getBytes());
creditCard = new String(buff);
}
}
E xplanation
The
main function creates an instance of the Customer object:
Customer customer = new Customer(1, "1234-5678-9876");
The
first parameter to the Customer constructor is the customer ID, and the second parameter is the credit card number. We will be saving this customer information to
a disk file. However, before saving the customer instance, we must encrypt the
credit card information so that anybody with access to the disk file will not
be able to steal the customer credit card information. The main function creates the
customer.dat file for writing the
customer data and then writes the Customer object by calling its writeObject method:
ObjectOutputStream
outStream = new ObjectOutputStream(
new
FileOutputStream("customer.dat"));
outStream.writeObject(customer);
Before
the object is serialized, the Customer object ensures that its credit card
field is encrypted, as explained shortly. After saving the object, the program
closes the data file and reopens it for reading the saved information:
ObjectInputStream
inputStream = new ObjectInputStream(
new
FileInputStream("customer.dat"));
The
readObject method now reads back the
stored information and re-creates the customer object:
customer = (Customer) inputStream.readObject();
Before
the object is fully initialized, it ensures that its credit card information is
decrypted. The program prints the object’s state to the user console before
saving it to disk and after retrieving it from disk. When we run the program,
the following output is shown:
ID:1 CC:1234-5678-9876
After encryption:
ID:1 CC:\MT?s?/?X|[YQ.
After retrieving object:
ID:1 CC:1234-5678-9876
The
output also shows the intermediate state after the credit card field is
encrypted. Now, let’s look at the implementation of the Customer class. This
class implements the Externalizable interface:
class
Customer implements Externalizable {
As part of the implementation, it must implement the two interface methods writeExternal and readExternal. We’ll look at the writeExternal method first:
public void writeExternal(ObjectOutput out) throws IOException {
try {
out.write(id);
encrypt();
out.writeUTF(creditCard);
In
this method, we first write the id field to the output stream. The encrypt
method encrypts the creditCard field of the Customer class. After encryption,
the program writes it to disk by calling the writeUTF method of the
output stream. If we examine the contents of the disk file, we’ll find only the
encrypted version of the credit card information stored in the file.
The readExternal method provides the decryption of the creditCard field:
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
try {
id = in.read();
String str = in.readUTF();
decrypt(str);
The
method first reads the id, followed by the encrypted credit
card information. The decrypt method decrypts this
information and copies the plain text to the creditCard field of the
Customer object. Thus, the program always sees the plain text (the unencrypted
version) of the credit card information. However, the stored data always
contains the encrypted version of this field. The readExternal and
writeExternal methods do this trick transparently.
The
rest of the code in the Customer class uses the security API. Be sure to
refer to the security API in Java documentation for further
details.
Jonas Stuart
18-Apr-2017