S3 Encryption Client v2 using AWS Java API

These examples utilize the second version of the AmazonS3EncryptionClient (AmazonS3EncryptionClientV2) as it is generally accepted as an improved offering in terms of security and vulnerability mitigation. A number of insecure encryption options like AES-CBC have been removed from the client and are effectively in a read-only mode and new objects will be encrypted instead with AES-GCM. If you need to migrate code from the v1 client, please refer to the documents provided by Amazon on how to best accomplish this.

S3 Client Encryption Options

There are three primary options or ‘levels’ of encryption that can be implemented within the AWS ecosystem:

  • SSE-S3 - Fully-managed S3 Encryption keys. This option shifts the largest amount of trust onto AWS. All keys are created and managed on your behalf requiring no work from you.
  • SSE-C - Customer managed keys. This option allows you to generate or provide your own key that S3 will implement and provides a layer of separation between AWS and your key generation infrastructure.
  • SSE-KMS - Customer-provided master key. This is almost a hybrid between SSE-C and SSE-S3 allowing you to own a master key, but still permits AWS to perform cryptological functions on your behalf and manage data keys.

Authenticated Encryption

The following are the possible encryption modes available within the S3 Client. For a better understanding of the underlying cryptography between these options, I recommend a quick visit to this article on block cipher differences within AES.

  • Authenticated Encryption CryptoMode.AuthenticatedEncryption an authenticated encryption mode. This will permit AES-OFB or AES-CTR.
  • Encryption Only (Deprecated) CryptoMode.EncryptionOnly
  • Strict Authenticated Encryption (Default) CryptoMode.StrictAuthenticationEncryption this will only permit AES-CTR as a block cipher scheme. This is the safest available option available within AES. It should be noted that using this option will throw a security exception should an S3 Object be retrieved and it is found to be not protected via authenticated encryption.

Benefits of Client-side Encryption

One of the advantages of performing client-side encryption before storing data into S3, is that only you will have access to the unencrypted data; even if you should encounter a security or data breach within your AWS environment. In the past, client-side encryption was required for certain storage criteria, such as PCI or PII data regulations, or auditor and security regulators that imposed similar constraints.

Configuring S3 Client Maven Dependencies

These examples utilize the aws-java-sdk-bom or bill of materials and allows for version numbers to be omitted from included Amazon dependencies. For more information on this, see my article on configuring Amazon dependencies.

Please note the inclusion of the Bouncy Castle Crypto library as this is required under the hood of the Amazon S3 Encryption library for a number of different operations. If you do not include this, you may receive errors when performing authenticated encryption.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.codetinkering</groupId>
    <artifactId>aws-s3-encryption-examples</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-ext-jdk15on</artifactId>
            <version>1.68</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.11.950</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

S3 Client Encryption using Master Keys

You can implement your own key value pair, or reference one from the class path or filesystem if you already have them created. In this example I am generating one at runtime to use for the sake of simplicity.

    public static final String BUCKET_NAME = "your-bucket-name-goes-here";
    public static final String OBJECT_KEY = "my-file-name.txt";
    public static final String OBJECT_CONTENT = "This text file should be encrypted";

    public static final String AWS_PROFILE_NAME = "AWSprofileName";


    public S3CryptoMasterKeyExample() throws NoSuchAlgorithmException {

        // Make sure your AWS credential profile is created and located at ~/.aws/credentials
        ProfileCredentialsProvider credentialsProvider = new ProfileCredentialsProvider(AWS_PROFILE_NAME);

        // Setup a key pair for encryption
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        // Build our S3 encryption client
        AmazonS3EncryptionV2 s3Crypto = AmazonS3EncryptionClientV2Builder.standard()
                .withRegion(Regions.US_EAST_1)
                .withCredentials(credentialsProvider)
                .withCryptoConfiguration(new CryptoConfigurationV2().withCryptoMode(CryptoMode.AuthenticatedEncryption))
                .withEncryptionMaterialsProvider(new StaticEncryptionMaterialsProvider(new EncryptionMaterials(keyPair)))
                .build();

        // Encrypt and store our object
        s3Crypto.putObject(BUCKET_NAME, OBJECT_KEY, OBJECT_CONTENT);

        // Retrieve our object
        System.out.println(s3Crypto.getObjectAsString(BUCKET_NAME, OBJECT_KEY));

        // Close our connection
        s3Crypto.shutdown();
    }

S3 Client Encryption using KMS

Note the use of the KMSEncryptionMaterialsProvider when configuring the S3EncryptionClient builder.

    public static final String BUCKET_NAME = "your-bucket-name-goes-here";
    public static final String OBJECT_KEY = "my-file-name.txt";
    public static final String OBJECT_CONTENT = "This text file should be encrypted";

    public static final String AWS_PROFILE_NAME = "AWSprofileName";

    public static final String KMS_KEY_ID = "your-kms-key-id";

    public S3CryptoKmsExample() throws NoSuchAlgorithmException {

        // Make sure your AWS credential profile is created and located at ~/.aws/credentials
        ProfileCredentialsProvider credentialsProvider = new ProfileCredentialsProvider(AWS_PROFILE_NAME);

        // Build our S3 encryption client
        AmazonS3EncryptionV2 s3Crypto = AmazonS3EncryptionClientV2Builder.standard()
                .withRegion(Regions.US_EAST_1)
                .withCredentials(credentialsProvider)
                .withCryptoConfiguration(new CryptoConfigurationV2().withCryptoMode(CryptoMode.AuthenticatedEncryption))
                .withEncryptionMaterialsProvider(new KMSEncryptionMaterialsProvider(KMS_KEY_ID))
                .build();

        // Encrypt and store our object
        s3Crypto.putObject(BUCKET_NAME, OBJECT_KEY, OBJECT_CONTENT);

        // Retrieve our object
        System.out.println(s3Crypto.getObjectAsString(BUCKET_NAME, OBJECT_KEY));

        // Close our connection
        s3Crypto.shutdown();
    }

Checkout this project from Github

git clone https://github.com/code-tinkering/aws-s3-encryption-examples
Download Zip