Asynchronous programming in the Amazon S3 Encryption Client for Java - Amazon S3 Encryption Client

Asynchronous programming in the Amazon S3 Encryption Client for Java

Version 3.x of the Amazon S3 Encryption Client provides a nonblocking asynchronous client that implements high concurrency across a few threads. The asynchronous client enables you to perform requests sequentially without waiting to view results between each request.

The default Amazon S3 Encryption Client uses synchronous methods that block your thread’s execution until the client receives a response from Amazon S3. The asynchronous client returns immediately, giving control back to the calling thread without waiting for a response. Because an asynchronous method returns before a response is available, you need a way to get the response when it’s ready. The methods in version 3.x of the Amazon S3 Encryption Client return CompletableFuture objects that allow you to access the response when it’s ready.

Instantiating the asynchronous client

To use the asynchronous client, you must specify the S3AsyncEncryptionClient builder and the builder parameter that identifies your wrapping key when you instantiate your client. The Amazon S3 Encryption Client supports the following wrapping keys: symmetric AWS KMS keys, raw AES-GCM keys, and raw RSA keys.

Note

If you use Raw RSA or Raw AES-GCM wrapping keys, you are responsible for generating, storing, and protecting the key material, preferably in a hardware security module (HSM) or key management system.

The following examples instantiate the asynchronous Amazon S3 Encryption Client with the default decryption mode. This means that all objects will be decrypted using the fully supported buffered decryption mode. For more information, see Decryption modes (Version 3.x and later).

KMS key

To specify a KMS key as your wrapping key, instantiate your client with the kmsKeyId builder parameter. The value of the kmsKeyId parameter can be any valid KMS key identifier. For details, see Key identifiers in the AWS Key Management Service Developer Guide.

// v3 class v3KMSKeyExample { public static void main(String[] args) { S3Client v3Client = S3EncryptionClient.builder() .kmsKeyId(kmsKeyId) .build(); } }
Raw AES key

To specify a raw AES key (javax.crypto.SecretKey) as your wrapping key, instantiate your client with the aesKey builder parameter.

// v3 class v3AESKeyExample { public static void main(String[] args) { S3Client v3Client = S3EncryptionClient.builder() .aesKey(aesKey) .build(); } }
Raw RSA key

To specify a raw RSA key (java.security.KeyPair) as your wrapping key, instantiate your client with the rsaKeyPair builder parameter. You can specify an entire RSA key pair or a partial RSA key pair. The value of the rsaKeyPair parameter must include both the public and private keys in the key pair to perform both encrypt and decrypt operations. You can specify the public key to enable the Amazon S3 Encryption Client to perform encrypt operations, or the private key to enable decrypt operations as needed. By specifying a partial key pair you can limit the exposure of your keys. For examples using a partial key pair, see the amazon-s3-encryption-client-java GitHub repository.

To instantiate version 3.x of the client to perform both encrypt and decrypt operations, specify both the public and private keys of your key pair.

// v3 class v3RSAKeyPairExample { public static void main(String[] args) { S3Client v3Client = S3EncryptionClient.builder() .rsaKeyPair(rsaKeyPair) .build(); } }

To instantiate the client to encrypt only, specify the public key. If you specify the public key alone, all GetObject calls will fail because the private key is required to decrypt.

// v3 class v3RSAKeyPairExample { public static void main(String[] args) { S3Client v3Client = S3EncryptionClient.builder() .rsaKeyPair(new PartialRsaKeyPair(null, rsaKeyPair.getPublic())) .build(); } }

To instantiate the client to decrypt only, specify the private key. If you specify the private key alone, all PutObject calls will fail because the public key is required to encrypt.

// v3 class v3RSAKeyPairExample { public static void main(String[] args) { S3Client v3Client = S3EncryptionClient.builder() .rsaKeyPair(new PartialRsaKeyPair(rsaKeyPair.getPrivate(), null)) .build(); } }

You can customize your asynchronous client by specifying different builder parameters to enable the features you need. By default, version 3.x of the Amazon S3 Encryption Client does not support legacy decryption, ranged 'GET' requests, or multipart uploads (via the high-level API).

For example, if you need to decrypt data keys that were encrypted with a legacy wrapping algorithm, specify the enableLegacyWrappingAlgorithms parameter when you instantiate your client. The following example specifies a raw AES key as the wrapping key.

// v3 class v3EnableLegacyModesAsyncClientExample { public static void main(String[] args) { S3AsyncClient v3Client = S3AsyncEncryptionClient.builder() .aesKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) .build(); } }

Encrypt and decrypt with the asynchronous client

The following walkthrough demonstrates how encrypt and decrypt asynchronously with version 3.x of the Amazon S3 Encryption Client.

  1. Instantiate your asynchronous client with the S3AsyncEncryptionClient builder.

    The following example specifies a raw AES key as the wrapping key.

    // v3 class v3EnableAsyncClientExample { public static void main(String[] args) { S3AsyncClient v3Client = S3AsyncEncryptionClient.builder() .aesKey(AES_KEY) .build(); } }
  2. Call PutObject to encrypt a plaintext object and upload it to Amazon S3.

    The asynchronous client stores the response to confirm that the PutObject request completed when you call GetObject in the future.

    // v3 class v3PutObjectAsyncClientExample { public static void main(String[] args) { CompletableFuture<PutObjectResponse> futurePut = v3AsyncClient.putObject(builder -> builder .bucket(bucket) .key(objectKey) .build(), AsyncRequestBody.fromString(objectContent)); // Block on completion of the futurePut futurePut.join(); } }
  3. Call GetObject to download and decrypt the encrypted object.

    // v3 class v3GetObjectAsyncClientExample { public static void main(String[] args) { CompletableFuture<ResponseBytes<GetObjectResponse>> futureGet = v3AsyncClient.getObject(builder -> builder .bucket(bucket) .key(objectKey) .build(), AsyncResponseTransformer.toBytes()); // Wait for the future to complete ResponseBytes<GetObjectResponse> getResponse = futureGet.join(); } }
  4. Optional: verify that the decrypted object matches the original plaintext object that you uploaded.

    assert output.equals(objectContent);
  5. The Amazon S3 Encryption Client implements the AutoClosable interface, which automatically calls close() when you exit a try-with-resources block for which the object has been declared in the resource specification header. As a best practice, you should either use try-with-resources or explicitly call the close() method.

    s3Client.close();
Note

The default decryption mode cannot decrypt objects larger than 64 MB. This decryption mode automatically buffers stream contents into memory to prevent the release of unauthenticated objects. If you attempt to decrypt an object larger than 64 MB, you will receive an exception directing you to enable the delayed authentication decryption mode. For more information, see Decryption modes.