Amazon Server Side Encryption with Key Management

Amazon recently announced its new "Server Side Encryption with Customer Keys" feature for S3. Our blog post explains why the feature is incomplete in the absence of key management infrastructure, such as provided in a cloud-friendly manner by Porticor.

This article demonstrates how SSE-C can be integrated with Porticor's key management, using a concrete code sample written in Java. We use Amazon's Java SDK, and access the Porticor REST API using our published sample Java code.

The following simple application generates an encryption key, and uses it to upload a file (/bin/ls) to a bucket on S3. Then, we download the file from S3 back into the local file system.

To learn more about Porticor, download the white paper at: http://www.porticor.com/porticor-key-management-white-paper/, or try it yourself by registering to an evaluation at: http://www.porticor.com/register/.

Terminology alert: the term key is overused. It refers to the name of the object on S3, as well as to the encryption key used to encrypt the object. Sigh.

Without further ado, to the code:

package com.porticor.vpd.api.sample;

import java.util.Arrays;
import java.util.Properties;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.SSECustomerKey;

class SampleClient {

    static private byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
                    .digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    final static String PROPERTY_FILE = "client.properties";

    /**
     * Upload a file to S3, with an explicit encryption key. Requires Amazon SDK
     * 1.8.0.
     */
    static private void uploadToS3(String s3KeyName, String uploadFileName,
            byte[] encryptionKey) {
        String bucketName = getBucketName();
        File file = new File(uploadFileName);

        SSECustomerKey sseKey = new SSECustomerKey(encryptionKey);

        AmazonS3 s3client = new AmazonS3Client(new ProfileCredentialsProvider());
        s3client.putObject(new PutObjectRequest(bucketName, s3KeyName, file)
                .withSSECustomerKey(sseKey));
    }

    /**
     * Download an encrypted file from S3, decrypted by S3 SSE with a customer
     * encryption key
     */
    static private void downloadFromS3(String s3KeyName, String targetFileName,
            byte[] encryptionKey) {
        String bucketName = getBucketName();
        File file = new File(targetFileName);

        SSECustomerKey sseKey = new SSECustomerKey(encryptionKey);

        AmazonS3 s3client = new AmazonS3Client(new ProfileCredentialsProvider());
        ObjectMetadata object = s3client.getObject(new GetObjectRequest(
                bucketName, s3KeyName).withSSECustomerKey(sseKey), file);
    }

    private static String getBucketName() {
        Properties prop = new Properties();
        try {
            prop.load(new FileInputStream(PROPERTY_FILE));
        } catch (IOException e2) {
            System.out.println("Could not fetch properties");
        }
        return prop.getProperty("bucket");
    }

    /**
     * Create an encryption key as a protected item, then use it to upload to
     * S3. As a sanity check, download the object back from S3.
     */
    static public void main(String args[]) {
        final int KEY_LEN = 32; // AES-256
        Endpoint appliance = new Endpoint();

        int unixTime = appliance.getTime(); // unauthenticated

        // Authenticate to the appliance, get a temporary credential
        String tempCred = appliance.getTempCred();

        // Clean up the protected item, if it exists from a previous run
        appliance.clearProtectedItem("s3_sample_key", tempCred);
        
        // Generate the key, fetch it and convert it to binary format
        appliance.createProtectedItemGenerate("s3_sample_key", KEY_LEN, tempCred);
        byte[] encryptionKey = hexStringToByteArray(appliance.getProtectedItem(
                "s3_sample_key", tempCred));
        // Upload the file to S3
        uploadToS3("my_protected_file", "/bin/ls", encryptionKey);
        // And now download it, sending the same key to S3
        downloadFromS3("my_protected_file", "copy_of_ls", encryptionKey);
        
        appliance.deleteProtectedItem("s3_sample_key", tempCred);
    }
}