AWS Developer Tools Blog

Amazon S3 Encryption Client Now Available for C++ Developers

My colleague, Conor Campbell, has great news for C++ developers who need to store sensitive information in Amazon S3.

— Jonathan

Many customers have asked for an Amazon S3 Encryption Client that is compatible with the existing Java client, and today we are delighted to provide it. You can now use the AWS SDK for C++ to securely retrieve and store objects, using encryption and decryption, to and from Amazon S3.

The Amazon S3 Encryption Client encrypts your S3 objects using envelope encryption with a master key that you supply and a generated content encryption key. The content encryption key is used to encrypt the body of the S3 object, while the master key is used to encrypt the content encryption key. There are two different types of encryption materials representing the master key that you can use:

  • Simple Encryption Materials. This mode uses AES Key Wrap to encrypt and decrypt the content encryption key.
  • KMS Encryption Materials. This mode uses an AWS KMS customer master key (CMK) to encrypt and decrypt the content encryption key.

In addition, we have provided the ability to specify the crypto mode that controls the encryption for your S3 objects. Currently, we support three crypto modes:

  • Encryption Only. Uses AES-CBC
  • Authenticated Encryption. Uses AES-GCM and allows AES-CTR for Range-Get operations.
  • Strict Authenticated Encryption. This is the most secure option. It uses AES-GCM and does not allow Range-Get operations because we cannot ensure cryptographic integrity protections for the data without verifying the entire authentication tag.

Users can also choose where to store the encryption metadata for the object either in the metadata of the S3 object or in a separate instruction file. The encryption information includes the following:

  • Encrypted content encryption key
  • Initialization vector
  • Crypto tag length
  • Materials description
  • Content encryption key algorithm
  • Key wrap algorithm

The client handles all of the encryption and decryption details under the hood. All you need to upload an object to or download an object from S3 is a simple PUT or GET operation. When calling a GET operation, this client automatically detects which crypto mode and storage method to use for successful decryption– eliminating confusion for selecting an appropriate crypto configuration.

Here are a few examples.


#include <aws/core/auth/AWSCredentialsProviderChain.h>
#include <aws/s3-encryption/S3EncryptionClient.h>
#include <aws/s3-encryption/CryptoConfiguration.h>
#include <aws/s3-encryption/materials/SimpleEncryptionMaterials.h>
#include <fstream>

using namespace Aws::S3;
using namespace Aws::S3::Model;
using namespace Aws::S3Encryption;
using namespace Aws::S3Encryption::Materials;

static const char* const KEY = "s3_encryption_cpp_sample_key";
static const char* const BUCKET = "s3-encryption-cpp-bucket";
static const char* const FILE_NAME = "./localFile";

int main()
{
    Aws::SDKOptions options;
    Aws::InitAPI(options);
    {
		auto masterKey = Aws::Utils::Crypto::SymmetricCipher::GenerateKey();
		auto simpleMaterials = Aws::MakeShared("s3Encryption", masterKey);

		CryptoConfiguration cryptoConfiguration(StorageMethod::METADATA, CryptoMode::AUTHENTICATED_ENCRYPTION);

		auto credentials = Aws::MakeShared<Aws::Auth::DefaultAWSCredentialsProviderChain>("s3Encryption");

		//construct S3 encryption client
		S3EncryptionClient encryptionClient(simpleMaterials, cryptoConfiguration, credentials);

		auto textFile = Aws::MakeShared<Aws::FStream>("s3Encryption", FILE_NAME, std::ios_base::in);
		assert(textFile->is_open());

		//put an encrypted object to S3
		PutObjectRequest putObjectRequest;
		putObjectRequest.WithBucket(BUCKET)
			.WithKey(KEY).SetBody(textFile);

		auto putObjectOutcome = encryptionClient.PutObject(putObjectRequest);

		if (putObjectOutcome.IsSuccess())
		{
			std::cout << "Put object succeeded" << std::endl;
		}
		else
		{
			std::cout << "Error while putting Object " << putObjectOutcome.GetError().GetExceptionName() <<
				" " << putObjectOutcome.GetError().GetMessage() << std::endl;
		}

		//get an encrypted object from S3
		GetObjectRequest getRequest;
		getRequest.WithBucket(BUCKET)
			.WithKey(KEY);

		auto getObjectOutcome = encryptionClient.GetObject(getRequest);
		if (getObjectOutcome.IsSuccess())
		{
			std::cout << "Successfully retrieved object from s3 with value: " << std::endl;
			std::cout << getObjectOutcome.GetResult().GetBody().rdbuf() << std::endl << std::endl;;
		}
		else
		{
			std::cout << "Error while getting object " << getObjectOutcome.GetError().GetExceptionName() <<
				" " << getObjectOutcome.GetError().GetMessage() << std::endl;
		}
	}
    Aws::ShutdownAPI(options);
}

In the previous example, we are setting up the Amazon S3 Encryption Client with simple encryption materials, a crypto configuration, and the default AWS credentials provider. The crypto configuration is using the metadata as the storage method and specifying authenticated encryption mode. This encrypts the S3 object with AES-GCM and uses the master key provided within simple encryption materials to encrypt the content encryption key using AES KeyWrap. Then the client will PUT a text file to S3, where it is encrypted and stored. When a GET operation is performed on the object, it is decrypted using the encryption information stored in the metadata, and returns the original text file within the body of the S3 object.

Now, what if we wanted to store our encryption information in a separate instruction file object? And what if we wanted to use AWS KMS for our master key? Maybe we even want to increase the level of security by using strict AES-GCM instead? Well that’s an easy switch, as you can see here.


#include <aws/core/auth/AWSCredentialsProviderChain.h>
#include <aws/s3-encryption/S3EncryptionClient.h>
#include <aws/s3-encryption/CryptoConfiguration.h>
#include <aws/s3-encryption/materials/KMSEncryptionMaterials.h>

using namespace Aws::S3;
using namespace Aws::S3::Model;
using namespace Aws::S3Encryption;
using namespace Aws::S3Encryption::Materials;

static const char* const KEY = "s3_encryption_cpp_sample_key";
static const char* const BUCKET = "s3-encryption-cpp-sample-bucket";
static const char* const CUSTOMER_MASTER_KEY_ID = "ars:some_customer_master_key_id";

int main()
{
    Aws::SDKOptions options;
    Aws::InitAPI(options);
    {
		auto kmsMaterials = Aws::MakeShared<KMSEncryptionMaterials>("s3Encryption", CUSTOMER_MASTER_KEY_ID);

		CryptoConfiguration cryptoConfiguration(StorageMethod::INSTRUCTION_FILE, CryptoMode::STRICT_AUTHENTICATED_ENCRYPTION);

		auto credentials = Aws::MakeShared<Aws::Auth::DefaultAWSCredentialsProviderChain>("s3Encryption");

		//construct S3 encryption client
		S3EncryptionClient encryptionClient(kmsMaterials, cryptoConfiguration, credentials);

		auto requestStream = Aws::MakeShared<Aws::StringStream>("s3Encryption");
		*requestStream << "Hello from the S3 Encryption Client!";

		//put an encrypted object to S3
		PutObjectRequest putObjectRequest;
		putObjectRequest.WithBucket(BUCKET)
			.WithKey(KEY).SetBody(requestStream);

		auto putObjectOutcome = encryptionClient.PutObject(putObjectRequest);

		if (putObjectOutcome.IsSuccess())
		{
			std::cout << "Put object succeeded" << std::endl;
		}
		else
		{
			std::cout << "Error while putting Object " << putObjectOutcome.GetError().GetExceptionName() <<
				" " << putObjectOutcome.GetError().GetMessage() << std::endl;
		}

		//get an encrypted object from S3
		GetObjectRequest getRequest;
		getRequest.WithBucket(BUCKET)
			.WithKey(KEY);

		auto getObjectOutcome = encryptionClient.GetObject(getRequest);
		if (getObjectOutcome.IsSuccess())
		{
			std::cout << "Successfully retrieved object from s3 with value: " << std::endl;
			std::cout << getObjectOutcome.GetResult().GetBody().rdbuf() << std::endl << std::endl;;
		}
		else
		{
			std::cout << "Error while getting object " << getObjectOutcome.GetError().GetExceptionName() <<
				" " << getObjectOutcome.GetError().GetMessage() << std::endl;
		}
    }
    Aws::ShutdownAPI(options);
}

A few caveats:

  • We have not implemented Range-Get operations in Encryption Only mode. Although this is possible, this is a legacy mode and we encourage you to use the Authenticated Encryption mode instead. However, if you do Range-Get operations in this legacy mode, please let us know and we will work on providing them.
  • Currently, Apple does not support AES-GCM with 256 bit keys. As a result Authenticated Encryption and Strict Authenticated Encryption PUT operations do not work on the default Apple build configuration. You can, however, still download objects that were uploaded using Authenticated Encryption because we can use AES-CTR mode for the download. Alternatively you can build the SDK using OpenSSL for full functionality. As soon as Apple makes AES-GCM available for CommonCrypto, we will add more support.
  • We have not yet implemented Upload Part. We will be working on that next. In the meantime, if you use the TransferManager interface with the Amazon S3 Encryption Client, be sure to set the minimum part size to a size that is larger than your largest object.

The documentation for Amazon S3 Encryption Client can be found here.

This package is now available in NuGet.

We hope you enjoy the Amazon S3 Encryption Client. Please leave your feedback on GitHub and feel free to submit pull requests or feature requests.

TAGS: