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.