Hello-World AWS Encryption SDK C/C++ program

In one of my projects, I need to encrypt and decrypt data in C, specifically using the AWS Encryption SDK. The SDK supports C, but the documentation is not very clear, in my opinion, so I decided to share my hello-world implementation with my learnings.

Setup

I’m working on Ubuntu, however, not all distributions are supported. Check the official documentation for details.

The setup instructions are described on the GitHub page, for the most part, they’re clear.

NOTE: Some unit tests kept failing due to timeouts, I located them and disabled them.

Credentials and permissions

Obtain and set up your credentials and make sure that your credentials have the right permissions.

KMS key

Go to the AWS Key Management Service and create a symmetric key, we’ll need its ARN later on.

encrypt.cpp

#include <iostream>
#include <fstream>

#include <aws/cryptosdk/cpp/kms_keyring.h>
#include <aws/cryptosdk/session.h>
#include <aws/cryptosdk/enc_ctx.h>

#include "basen.hpp"

# define OUTPUT_LENGTH 1024

/* Allocates a hash table for holding the encryption context and puts a few sample values in it. */
int set_up_enc_ctx(struct aws_allocator *alloc, struct aws_hash_table *enc_ctx) {
    if (AWS_OP_SUCCESS != aws_cryptosdk_enc_ctx_init(alloc, enc_ctx)) {
        return AWS_OP_ERR;
    }

    AWS_STATIC_STRING_FROM_LITERAL(enc_ctx_key, "username");
    AWS_STATIC_STRING_FROM_LITERAL(enc_ctx_value, "rendon");

    int was_created;
    if (AWS_OP_SUCCESS != aws_hash_table_put(enc_ctx, enc_ctx_key, (void *)enc_ctx_value, &was_created)) {
        aws_cryptosdk_enc_ctx_clean_up(enc_ctx);
        return AWS_OP_ERR;
    }
    if (was_created != 1) {
        return AWS_OP_ERR;
    }
    return AWS_OP_SUCCESS;
}

int main(int argc, char **argv) {
    if (argc < 2) {
        std::cerr << "You must provide a key ARN" << std::endl;
        exit(1);
    }

    // Initialization
    aws_cryptosdk_load_error_strings();
    Aws::SDKOptions options;
    Aws::InitAPI(options);

    // Step 1: Construct the keyring
    const char* key_arn = argv[1];
    struct aws_cryptosdk_keyring* kms_keyring = Aws::Cryptosdk::KmsKeyring::Builder().Build(key_arn);


    // Step 2: Create a session
    struct aws_allocator *alloc = aws_default_allocator();
    struct aws_cryptosdk_session* session = aws_cryptosdk_session_new_from_keyring_2(
            alloc,
            AWS_CRYPTOSDK_ENCRYPT,
            kms_keyring
    );
    if (!session) {
        std::cerr << "Unable to create session" << std::endl;
        abort();
    }

    aws_cryptosdk_keyring_release(kms_keyring);

    // Step 3: Set the size of the plaintext data
    const char* input = R"({"key": "value"})";
    const size_t input_length = strlen(input);
    if (aws_cryptosdk_session_set_message_size(session, input_length) != AWS_OP_SUCCESS) {
        std::cerr << "Unable to set input size" << std::endl;
        abort();
    }

    // Step 4: Set the encryption context
    // You'll need to include "aws/cryptosdk/enc_ctx.h" for this step

    struct aws_hash_table encryption_ctx;
    if (set_up_enc_ctx(alloc, &encryption_ctx) != AWS_OP_SUCCESS) {
        std::cerr << "Unable to set up encryption context" << std::endl;
        abort();
    }

    // Get a mutable pointer to the encryption context
    struct aws_hash_table* session_enc_ctx = aws_cryptosdk_session_get_enc_ctx_ptr_mut(session);
    if (!session_enc_ctx) {
        std::cerr << "Unable to get mutable encryption context" << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    // We copy the contents of our encryption context into the session's.
    if (aws_cryptosdk_enc_ctx_clone(alloc, session_enc_ctx, &encryption_ctx) != AWS_OP_SUCCESS) {
        std::cerr << "Unable to set encryption context" << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    // Step 5: Encrypt the string
    uint8_t output[OUTPUT_LENGTH];
    size_t out_bytes_written = 0;

    size_t in_bytes_read = 0;
    int enc_result = aws_cryptosdk_session_process(
            session,
            output, OUTPUT_LENGTH, &out_bytes_written,
            (const uint8_t*) input, input_length, &in_bytes_read
    );
    if (enc_result != AWS_OP_SUCCESS) {
        std::cerr << "Encryption failed: " << aws_error_str(aws_last_error()) << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    // Step 6: Clean up the session
    if (!aws_cryptosdk_session_is_done(session)) {
        aws_cryptosdk_session_destroy(session);
        std::cerr << "Session is not done" << std::endl;
        abort();
    }

    if (in_bytes_read != input_length) {
        abort();
    }
    aws_cryptosdk_session_destroy(session);

    std::cout << "Encryption succeeded\n";

    std::string encoded;
    bn::encode_b64(output, output + out_bytes_written, std::back_inserter(encoded));

    std::ofstream file("ciphertext.txt");
    file << encoded;
    file.close();

    Aws::ShutdownAPI(options);
    aws_cryptosdk_enc_ctx_clean_up(&encryption_ctx);
    return 0;
}

Build & run

g++ -o encrypt encrypt.cpp\
    -std=c++11 -lcrypto -laws-encryption-sdk\
    -laws-encryption-sdk-cpp -laws-c-common -laws-cpp-sdk-kms\
    -laws-cpp-sdk-core -laws-crt-cpp

./decrypt arn:aws:kms:us-west-2:111111111111:key/dddddddd-4444-4444-4444-999999999999

Where arn:aws:kms:us-west-2:111111111111:key/dddddddd-4444-4444-4444-999999999999 is the ARN of your symmetric key.

decrypt.cpp

#include <iostream>
#include <fstream>

#include <aws/cryptosdk/cpp/kms_keyring.h>
#include <aws/cryptosdk/session.h>

#include "basen.hpp"

# define OUTPUT_LENGTH 1024
int main(int argc, char **argv) {
    if (argc < 2) {
        std::cerr << "You must provide a key ARN" << std::endl;
        exit(1);
    }

    // Initialization
    aws_cryptosdk_load_error_strings();
    Aws::SDKOptions options;
    Aws::InitAPI(options);

    // Step 1: Construct the keyring
    const char* key_arn = argv[1];
    struct aws_cryptosdk_keyring* kms_keyring = Aws::Cryptosdk::KmsKeyring::Builder().Build(key_arn);


    // Step 2: Create a session
    struct aws_allocator *alloc = aws_default_allocator();
    struct aws_cryptosdk_session* session = aws_cryptosdk_session_new_from_keyring_2(
            alloc,
            AWS_CRYPTOSDK_DECRYPT,
            kms_keyring
    );
    if (!session) {
        std::cerr << "Unable to create session" << std::endl;
        abort();
    }

    aws_cryptosdk_keyring_release(kms_keyring);

    std::ifstream file("ciphertext.txt");
    std::string encoded_ciphertext;
    file >> encoded_ciphertext;
    file.close();

    std::vector<uint8_t> ciphertext;
    bn::decode_b64(encoded_ciphertext.begin(), encoded_ciphertext.end(), std::back_inserter(ciphertext));

    uint8_t* input = ciphertext.data();
    size_t input_length = ciphertext.size();
    uint8_t output[OUTPUT_LENGTH];
    size_t out_bytes_written;
    size_t in_bytes_read;
    int result = aws_cryptosdk_session_process(
            session,
            output, OUTPUT_LENGTH, &out_bytes_written,
            input, input_length, &in_bytes_read
    );

    if (result != AWS_OP_SUCCESS) {
        std::cerr << "Decryption failed: " << aws_error_str(aws_last_error()) << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    if (!aws_cryptosdk_session_is_done(session)) {
        std::cerr << "Session is not done" << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    if (in_bytes_read != input_length) {
        std::cerr << "Consumed fewer bytes than expected" << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    const struct aws_hash_table* session_enc_ctx = aws_cryptosdk_session_get_enc_ctx_ptr(session);
    if (!session_enc_ctx) {
        std::cerr << "Unable to get encryption context" << std::endl;
        aws_cryptosdk_session_destroy(session);
        abort();
    }

    std::cout << "Encryption context:" << std::endl;
    for (struct aws_hash_iter iter = aws_hash_iter_begin(session_enc_ctx); !aws_hash_iter_done(&iter); aws_hash_iter_next(&iter)) {
        auto key = (struct aws_string *) iter.element.key;
        auto val = (struct aws_string *) iter.element.value;
        std::cout << "Key: " << aws_string_c_str(key) << "\n";
        std::cout << "Value: " << aws_string_c_str(val) << "\n";
    }

    std::cout << "Plaintext: ";
    for (int idx = 0; idx < out_bytes_written; idx++) {
        std::cout << output[idx];
    }
    std::cout << std::endl;

    aws_cryptosdk_session_destroy(session);
    Aws::ShutdownAPI(options);
    return 0;
}

This code makes use of the base-n package, which I simply copy-pasted into my folder.

Build & run

g++ -o decrypt decrypt.cpp\
    -std=c++11 -lcrypto -laws-encryption-sdk\
    -laws-encryption-sdk-cpp -laws-c-common -laws-cpp-sdk-kms\
    -laws-cpp-sdk-core -laws-crt-cpp

./decrypt arn:aws:kms:us-west-2:111111111111:key/dddddddd-4444-4444-4444-999999999999

That’s it, I hope that helps.

This entry was posted in Posts, SoftwareEngineering. Bookmark the permalink.