File Encryption

In order to encrypt a file, we need to know the public key of its recipient - the party who can decrypt the file. These arguments are passed to the encryptFile() method.

The encryptFile() method will:

1.generate a random session key,

2.encrypt the session key with the recipient's public key,

3.initialize the bulk cipher with the session key,

4.encode the bulk cipher's algorithm parameters,

5.initialize the MAC algorithm,

6.process the input file, and

7.create the output from the various components.

Step 1 - Generate a Random Session Key

To achieve acceptable performance during file encryption and decryption, we need to use a symmetric-key cipher. This symmetric key, which we will call the session key, will be encrypted (using the recipient's public key) and then stored with the encrypted file. Rather than simply using the same key for each file, we need to generate a random key for each encryption.

The KeyGenerator mechanism is used to create random SecretKey key objects. A provider-based instance is created using the KeyGenerator.getInstance() method.

This instance can then be initialized using one of the KeyGenerator.init() methods. In the simplest case, no initialization is required, in which case the provider's default initialization is used. Alternatively, initialization can request a key of the given key size, or other key parameters by using a java.security.AlgorithmParameterSpec class.

The following method will create a new random SecretKey for the given algorithm and provider using the default initialization:

SecretKey generateSecretKey(String algorithm, String 
                            provider)
{
   KeyGenerator keyGen = KeyGenerator.getInstance(
      algorithm, provider);

   return keyGen.generateKey();
}

Step 2 - Encrypt the Session Key

Once we have generated the session key, we need to encrypt it using the recipient's public key. In this way we can safely transmit the session key such that only the recipient can recover the actual key. The Gemalto SAFENET provider includes a special interface to its KeyStore to provide session key encryption.

The au.com.safenet.crypto.WrappingKeyStore class extends the standard KeyStore mechanism to provide "key wrapping" which enables a session key to be generated in the hardware, then encrypted on the hardware and exported in an encrypted form. This means that the session key is never visible outside the hardware.

The WrappingKeyStore.wrapKey() method accepts three arguments: two keys and a transformation string. The first Key is the RSA PublicKey used to perform the encryption, the second Key is the DES key we wish to encrypt. The final parameter, the transformation string, describes the encryption method that should be used to encrypt the key. Currently, this string may be RSA/ECB/PKCS1Padding or RSA/ECB/NoPadding.

static final String PROVIDER = "SAFENET";
static final String WRAP_KEYSTORE = "CRYPTOKI";
static final String WRAP_TRANSFORM =
                        "RSA/ECB/PKCS1Padding";

byte[] encryptKey(PublicKey wrapKey, SecretKey key)
{
   WrappingKeyStore keyStore;
   keyStore = WrappingKeyStore.getInstance(WRAP_KEYSTORE,
                                           PROVIDER);
   keyStore.load(null, null);
   return keyStore.wrapKey(wrapKey, WRAP_TRANSFORM, key);
}

Step 3 - Create and Initialize the Bulk Cipher

This application will simply use the default AlgorithmParameters for the bulk encryption algorithm. Therefore, the initialization of our Cipher is quite simple:

static final String PROVIDER = "SAFENET";
static final String BULK_ALGORITHM = "DES";

   Cipher bulkCipher = Cipher.getInstance(BULK_ALGORITHM,
                                          PROVIDER);
   bulkCipher.init(Cipher.ENCRYPT_MODE, secretKey);

Step 4 - Encode Algorithm Parameters

The only algorithm parameter supported by the Gemalto SAFENET provider is an initialization vector. An initialization vector is used in a block cipher when it is operating in a feedback mode: DES in CBC mode for example. During encryption, the initialization vector is used to prime the cipher. However, unlike the key, its value is not secret.

The cipher used to decrypt the data stream must be initialized with the same initialization vector for the decryption to succeed.

The following method will return the algorithm parameters encoded into a byte array. For now, we just return the IV directly as this is the only supported algorithm parameter.

byte[] encodeParameters(Cipher cipher)
{
   byte[] iv = cipher.getIV();
   return iv;
}

Step 5 - Initialize the MAC Algorithm

In this example we will use a MAC algorithm instead of a signature algorithm. The significant difference here is that the MAC will only tell us if the encrypted document has been tampered with, it will not authenticate the sender.

static final String PROVIDER = "SAFENET";
static final String MAC_ALGORITHM = "DESMac";

Mac mac = Mac.getInstance(MAC_ALGORITHM, PROVIDER);
mac.init(secretKey);

Step 6 - Process the Input File

We are now ready to process the input file to generate the encrypted output and the MAC. The following method will accept the initialized Cipher, Mac and input/output streams. The data on the InputStream will be read in blocks (of some arbitrary size), then processed by the Mac instance and then encrypted with the Cipher instance.

The encrypted data will then be written to the OutputStream. This method will return the MAC as a byte array.

static final int READ_BUFFER = 50;

byte[] encrypt(Cipher cipher, Mac mac, InputStream in,
               OutputStream out)
{
   byte[] block = new byte[READ_BUFFER];
   int len;
   while ((len = in.read(block)) != -1)
   {
      /*
       * update our MAC value
       */
      mac.update(block, 0, len);

      /*
       * encrypt the data
       */
      byte[] enc = cipher.update(block, 0, len);
      if (enc != null)
      {
         /*
          * output the encrypted data
          */
         out.write(enc);
      }
   }

   /*
    * output the final block if required
    */
   byte[] finalBlock = cipher.doFinal();
   if (finalBlock != null)
   {
      out.write(finalBlock);
   }

   return mac.doFinal();
}

Step 7 - Create the Encrypted Output

Now that we have written the various building blocks, we can construct the final encryptFile() method:

static final String PROVIDER = "SAFENET";
static final String BULK_ALGORITHM = "DES";
static final String BULK_TRANSFORM =
                    "DES/CBC/PKCS5Padding";
static final String MAC_ALGORITHM = "DESMac";

void encryptFile(InputStream in, OutputStream out,          
                 PublicKey publicKey)
{
   /*
    * Create a random SecretKey and encrypt it using
    * the recipient's PublicKey
    */
   SecretKey secretKey = generateSecretKey(BULK_ALGORITHM,
                                           PROVIDER);
   byte[] wrappedKey = encryptKey(publicKey, secretKey);

   /*
    * Create and initialise the Cipher used to encrypt the
      document
    */
   Cipher bulkCipher =
             Cipher.getInstance(BULK_TRANSFORM,PROVIDER);
   bulkCipher.init(Cipher.ENCRYPT_MODE, secretKey);

   /*
    * Encode the algorithm parameters for the Cipher
    */
   byte[] algParams = encodeParameters(bulkCipher);

   /*
    * Create the Mac instance and initialise it with our
    * session key
    */
   Mac mac = Mac.getInstance(MAC_ALGORITHM, PROVIDER);
   mac.init(secretKey);

   /*
    * Encrypt the document to an internal buffer and
    * calculate the MAC value of the plain text
    */
   ByteArrayOutputStream bOut =
                            new ByteArrayOutputStream();
   byte[] macValue = encrypt(bulkCipher, mac, in, bOut);

   /*
    * Encode the output file
    */
   DataOutputStream dOut = new DataOutputStream(out);

   /*
    * Write out the key
    */
   dOut.writeInt(wrappedKey.length);
   dOut.write(wrappedKey);

   /*
    * Write out the parameters, note these may be null
    */
   if (algParams != null)
   {
      dOut.writeInt(algParams.length);
      dOut.write(algParams);
   }
   else
   {
      dOut.writeInt(0);
   }

   /*
    * Write out the MAC
    */
   dOut.writeInt(macValue.length);
   dOut.write(macValue);

   /*
    * And finally the encrypted document
    */
   bOut.writeTo(dOut);
}