OpenSSL AES GCM Convenience Wrapper in C

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
6
down vote
favorite
I wrote this code for inclusion in a repository of encryption code examples. The cryptographic primitives in use are required for compatibility with the other code samples.
It's been a long while since I've used C, so I expect I will have made many mistakes. What can I do to improve the below code? It is quite lengthy, so apologies in advance.
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define SCEE_ALGORITHM EVP_aes_128_gcm
#define SCEE_KEY_LENGTH 16
#define SCEE_TAG_LENGTH 16
#define SCEE_NONCE_LENGTH 12
#define SCEE_SALT_LENGTH 16
#define SCEE_PBKDF2_ITERATIONS 32767
#define SCEE_PBKDF2_HASH EVP_sha256
#define SCEE_OK 0
#define SCEE_ERROR_RAND 1
#define SCEE_ERROR_CTX_NEW 2
#define SCEE_ERROR_CTX_ALGORITHM 3
#define SCEE_ERROR_CTX_KEY_NONCE 4
#define SCEE_ERROR_CRYPT 5
#define SCEE_ERROR_CRYPT_FINAL 6
#define SCEE_ERROR_CRYPT_TAG 7
#define SCEE_ERROR_CRYPT_TAG_INVALID 8
#define SCEE_ERROR_B64 9
#define SCEE_ERROR_PBKDF2 10
#define SCEE_B64_ENCODE 0
#define SCEE_B64_DECODE 1
#define SCEE_CRYPT_ENCRYPT 0
#define SCEE_CRYPT_DECRYPT 1
size_t b64_get_length(size_t current_size, int operation);
int b64_encode(const uint8_t* bytes, size_t length, char* str);
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out);
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length);
size_t crypt_string_get_length(size_t current_size, int operation);
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out);
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce);
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext);
// Base64.
size_t b64_get_length(size_t current_size, int operation)
if (operation == SCEE_B64_ENCODE)
return ((current_size + 2) / 3) * 4 + 1;
else
return (current_size * 3) / 4;
int b64_encode(const uint8_t* bytes, size_t length, char* str)
if (!EVP_EncodeBlock(str, bytes, length))
return SCEE_ERROR_B64;
return SCEE_OK;
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out)
int pad_count = 0;
if (str[length - 1] == '=') pad_count++;
if (str[length - 2] == '=') pad_count++;
int decode_size = EVP_DecodeBlock(bytes, str, length);
if (decode_size == -1)
return SCEE_ERROR_B64;
*decode_size_out = (size_t)decode_size - pad_count;
return SCEE_OK;
// PBKDF2.
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length)
if (!PKCS5_PBKDF2_HMAC(password, password_length, salt, salt_length, iterations, digest, key_length, key_out))
return SCEE_ERROR_PBKDF2;
return SCEE_OK;
// Encrypt/Decrypt String.
size_t crypt_string_get_length(size_t current_size, int operation)
if (operation == SCEE_CRYPT_ENCRYPT)
return b64_get_length(current_size + SCEE_SALT_LENGTH + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH, SCEE_B64_ENCODE);
else
size_t temp_length = b64_get_length(current_size, SCEE_B64_DECODE);
return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1;
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out)
// Generate a 128-bit salt using a CSPRNG.
uint8_t salt[SCEE_SALT_LENGTH];
if (!RAND_bytes(salt, SCEE_SALT_LENGTH)) return SCEE_ERROR_RAND;
// Use PBKDF2 to derive a key.
uint8_t key[SCEE_KEY_LENGTH];
int r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
// Encrypt and prepend salt.
size_t ciphertext_and_nonce_length = plaintext_length + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH;
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
r = encrypt(plaintext, plaintext_length, key, ciphertext_and_nonce);
if (r != SCEE_OK) return r;
size_t ciphertext_and_nonce_and_salt_length = ciphertext_and_nonce_length + SCEE_SALT_LENGTH;
uint8_t ciphertext_and_nonce_and_salt[ciphertext_and_nonce_and_salt_length];
memcpy(ciphertext_and_nonce_and_salt, salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce, ciphertext_and_nonce_length);
return b64_encode(ciphertext_and_nonce_and_salt, ciphertext_and_nonce_and_salt_length, ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out)
// Decode the base64.
size_t actual_size;
size_t max_size = b64_get_length(base64_length, SCEE_B64_DECODE);
uint8_t ciphertext_and_nonce_and_salt[max_size];
int r = b64_decode(base64_ciphertext_and_nonce_and_salt, base64_length, ciphertext_and_nonce_and_salt, &actual_size);
if (r != SCEE_OK) return r;
// Retrieve the salt and ciphertext.
size_t ciphertext_and_nonce_length = actual_size - SCEE_SALT_LENGTH;
uint8_t salt[SCEE_SALT_LENGTH];
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
memcpy(salt, ciphertext_and_nonce_and_salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce, ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce_length);
// Use PBKDF2 to derive the key.
uint8_t key[SCEE_KEY_LENGTH];
r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
*plaintext_length_out = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
plaintext_out[*plaintext_length_out] = '';
// Decrypt and return result.
return decrypt(ciphertext_and_nonce, ciphertext_and_nonce_length, key, plaintext_out);
// Encrypt/Decrypt.
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce)
// Generate a 96-bit nonce using a CSPRNG.
uint8_t nonce[SCEE_NONCE_LENGTH];
if (!RAND_bytes(nonce, SCEE_NONCE_LENGTH)) return SCEE_ERROR_RAND;
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_EncryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Encrypt and prepend nonce.
int temp_length;
uint8_t ciphertext[plaintext_length];
if (!EVP_EncryptUpdate(ctx, ciphertext, &temp_length, plaintext, plaintext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_EncryptFinal_ex(ctx, ciphertext + temp_length, &temp_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_FINAL;
uint8_t tag[SCEE_TAG_LENGTH];
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SCEE_TAG_LENGTH, tag))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG;
memcpy(ciphertext_and_nonce, nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext, plaintext_length);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH + plaintext_length, tag, SCEE_TAG_LENGTH);
return SCEE_OK;
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext)
// Retrieve the nonce and ciphertext.
size_t ciphertext_length = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
uint8_t ciphertext[ciphertext_length];
uint8_t nonce[SCEE_NONCE_LENGTH];
uint8_t tag[SCEE_NONCE_LENGTH];
memcpy(nonce, ciphertext_and_nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext, ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext_length);
memcpy(tag, ciphertext_and_nonce + SCEE_NONCE_LENGTH + ciphertext_length, SCEE_TAG_LENGTH);
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_DecryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Decrypt and return result.
int temp_length;
if (!EVP_DecryptUpdate(ctx, plaintext, &temp_length, ciphertext, ciphertext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SCEE_TAG_LENGTH, tag))
return SCEE_ERROR_CRYPT_TAG;
if (EVP_DecryptFinal_ex(ctx, plaintext + temp_length, &temp_length) < 1)
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG_INVALID;
return SCEE_OK;
c cryptography openssl
add a comment |Â
up vote
6
down vote
favorite
I wrote this code for inclusion in a repository of encryption code examples. The cryptographic primitives in use are required for compatibility with the other code samples.
It's been a long while since I've used C, so I expect I will have made many mistakes. What can I do to improve the below code? It is quite lengthy, so apologies in advance.
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define SCEE_ALGORITHM EVP_aes_128_gcm
#define SCEE_KEY_LENGTH 16
#define SCEE_TAG_LENGTH 16
#define SCEE_NONCE_LENGTH 12
#define SCEE_SALT_LENGTH 16
#define SCEE_PBKDF2_ITERATIONS 32767
#define SCEE_PBKDF2_HASH EVP_sha256
#define SCEE_OK 0
#define SCEE_ERROR_RAND 1
#define SCEE_ERROR_CTX_NEW 2
#define SCEE_ERROR_CTX_ALGORITHM 3
#define SCEE_ERROR_CTX_KEY_NONCE 4
#define SCEE_ERROR_CRYPT 5
#define SCEE_ERROR_CRYPT_FINAL 6
#define SCEE_ERROR_CRYPT_TAG 7
#define SCEE_ERROR_CRYPT_TAG_INVALID 8
#define SCEE_ERROR_B64 9
#define SCEE_ERROR_PBKDF2 10
#define SCEE_B64_ENCODE 0
#define SCEE_B64_DECODE 1
#define SCEE_CRYPT_ENCRYPT 0
#define SCEE_CRYPT_DECRYPT 1
size_t b64_get_length(size_t current_size, int operation);
int b64_encode(const uint8_t* bytes, size_t length, char* str);
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out);
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length);
size_t crypt_string_get_length(size_t current_size, int operation);
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out);
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce);
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext);
// Base64.
size_t b64_get_length(size_t current_size, int operation)
if (operation == SCEE_B64_ENCODE)
return ((current_size + 2) / 3) * 4 + 1;
else
return (current_size * 3) / 4;
int b64_encode(const uint8_t* bytes, size_t length, char* str)
if (!EVP_EncodeBlock(str, bytes, length))
return SCEE_ERROR_B64;
return SCEE_OK;
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out)
int pad_count = 0;
if (str[length - 1] == '=') pad_count++;
if (str[length - 2] == '=') pad_count++;
int decode_size = EVP_DecodeBlock(bytes, str, length);
if (decode_size == -1)
return SCEE_ERROR_B64;
*decode_size_out = (size_t)decode_size - pad_count;
return SCEE_OK;
// PBKDF2.
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length)
if (!PKCS5_PBKDF2_HMAC(password, password_length, salt, salt_length, iterations, digest, key_length, key_out))
return SCEE_ERROR_PBKDF2;
return SCEE_OK;
// Encrypt/Decrypt String.
size_t crypt_string_get_length(size_t current_size, int operation)
if (operation == SCEE_CRYPT_ENCRYPT)
return b64_get_length(current_size + SCEE_SALT_LENGTH + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH, SCEE_B64_ENCODE);
else
size_t temp_length = b64_get_length(current_size, SCEE_B64_DECODE);
return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1;
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out)
// Generate a 128-bit salt using a CSPRNG.
uint8_t salt[SCEE_SALT_LENGTH];
if (!RAND_bytes(salt, SCEE_SALT_LENGTH)) return SCEE_ERROR_RAND;
// Use PBKDF2 to derive a key.
uint8_t key[SCEE_KEY_LENGTH];
int r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
// Encrypt and prepend salt.
size_t ciphertext_and_nonce_length = plaintext_length + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH;
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
r = encrypt(plaintext, plaintext_length, key, ciphertext_and_nonce);
if (r != SCEE_OK) return r;
size_t ciphertext_and_nonce_and_salt_length = ciphertext_and_nonce_length + SCEE_SALT_LENGTH;
uint8_t ciphertext_and_nonce_and_salt[ciphertext_and_nonce_and_salt_length];
memcpy(ciphertext_and_nonce_and_salt, salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce, ciphertext_and_nonce_length);
return b64_encode(ciphertext_and_nonce_and_salt, ciphertext_and_nonce_and_salt_length, ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out)
// Decode the base64.
size_t actual_size;
size_t max_size = b64_get_length(base64_length, SCEE_B64_DECODE);
uint8_t ciphertext_and_nonce_and_salt[max_size];
int r = b64_decode(base64_ciphertext_and_nonce_and_salt, base64_length, ciphertext_and_nonce_and_salt, &actual_size);
if (r != SCEE_OK) return r;
// Retrieve the salt and ciphertext.
size_t ciphertext_and_nonce_length = actual_size - SCEE_SALT_LENGTH;
uint8_t salt[SCEE_SALT_LENGTH];
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
memcpy(salt, ciphertext_and_nonce_and_salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce, ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce_length);
// Use PBKDF2 to derive the key.
uint8_t key[SCEE_KEY_LENGTH];
r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
*plaintext_length_out = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
plaintext_out[*plaintext_length_out] = '';
// Decrypt and return result.
return decrypt(ciphertext_and_nonce, ciphertext_and_nonce_length, key, plaintext_out);
// Encrypt/Decrypt.
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce)
// Generate a 96-bit nonce using a CSPRNG.
uint8_t nonce[SCEE_NONCE_LENGTH];
if (!RAND_bytes(nonce, SCEE_NONCE_LENGTH)) return SCEE_ERROR_RAND;
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_EncryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Encrypt and prepend nonce.
int temp_length;
uint8_t ciphertext[plaintext_length];
if (!EVP_EncryptUpdate(ctx, ciphertext, &temp_length, plaintext, plaintext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_EncryptFinal_ex(ctx, ciphertext + temp_length, &temp_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_FINAL;
uint8_t tag[SCEE_TAG_LENGTH];
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SCEE_TAG_LENGTH, tag))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG;
memcpy(ciphertext_and_nonce, nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext, plaintext_length);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH + plaintext_length, tag, SCEE_TAG_LENGTH);
return SCEE_OK;
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext)
// Retrieve the nonce and ciphertext.
size_t ciphertext_length = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
uint8_t ciphertext[ciphertext_length];
uint8_t nonce[SCEE_NONCE_LENGTH];
uint8_t tag[SCEE_NONCE_LENGTH];
memcpy(nonce, ciphertext_and_nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext, ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext_length);
memcpy(tag, ciphertext_and_nonce + SCEE_NONCE_LENGTH + ciphertext_length, SCEE_TAG_LENGTH);
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_DecryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Decrypt and return result.
int temp_length;
if (!EVP_DecryptUpdate(ctx, plaintext, &temp_length, ciphertext, ciphertext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SCEE_TAG_LENGTH, tag))
return SCEE_ERROR_CRYPT_TAG;
if (EVP_DecryptFinal_ex(ctx, plaintext + temp_length, &temp_length) < 1)
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG_INVALID;
return SCEE_OK;
c cryptography openssl
Rather than having all the function declarations at the top, you might want to create a header file to provide entry points to a user. This may be a copy and paste issue but the vertical spacing is inconsistent.
â pacmaninbw
May 15 at 15:54
add a comment |Â
up vote
6
down vote
favorite
up vote
6
down vote
favorite
I wrote this code for inclusion in a repository of encryption code examples. The cryptographic primitives in use are required for compatibility with the other code samples.
It's been a long while since I've used C, so I expect I will have made many mistakes. What can I do to improve the below code? It is quite lengthy, so apologies in advance.
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define SCEE_ALGORITHM EVP_aes_128_gcm
#define SCEE_KEY_LENGTH 16
#define SCEE_TAG_LENGTH 16
#define SCEE_NONCE_LENGTH 12
#define SCEE_SALT_LENGTH 16
#define SCEE_PBKDF2_ITERATIONS 32767
#define SCEE_PBKDF2_HASH EVP_sha256
#define SCEE_OK 0
#define SCEE_ERROR_RAND 1
#define SCEE_ERROR_CTX_NEW 2
#define SCEE_ERROR_CTX_ALGORITHM 3
#define SCEE_ERROR_CTX_KEY_NONCE 4
#define SCEE_ERROR_CRYPT 5
#define SCEE_ERROR_CRYPT_FINAL 6
#define SCEE_ERROR_CRYPT_TAG 7
#define SCEE_ERROR_CRYPT_TAG_INVALID 8
#define SCEE_ERROR_B64 9
#define SCEE_ERROR_PBKDF2 10
#define SCEE_B64_ENCODE 0
#define SCEE_B64_DECODE 1
#define SCEE_CRYPT_ENCRYPT 0
#define SCEE_CRYPT_DECRYPT 1
size_t b64_get_length(size_t current_size, int operation);
int b64_encode(const uint8_t* bytes, size_t length, char* str);
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out);
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length);
size_t crypt_string_get_length(size_t current_size, int operation);
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out);
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce);
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext);
// Base64.
size_t b64_get_length(size_t current_size, int operation)
if (operation == SCEE_B64_ENCODE)
return ((current_size + 2) / 3) * 4 + 1;
else
return (current_size * 3) / 4;
int b64_encode(const uint8_t* bytes, size_t length, char* str)
if (!EVP_EncodeBlock(str, bytes, length))
return SCEE_ERROR_B64;
return SCEE_OK;
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out)
int pad_count = 0;
if (str[length - 1] == '=') pad_count++;
if (str[length - 2] == '=') pad_count++;
int decode_size = EVP_DecodeBlock(bytes, str, length);
if (decode_size == -1)
return SCEE_ERROR_B64;
*decode_size_out = (size_t)decode_size - pad_count;
return SCEE_OK;
// PBKDF2.
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length)
if (!PKCS5_PBKDF2_HMAC(password, password_length, salt, salt_length, iterations, digest, key_length, key_out))
return SCEE_ERROR_PBKDF2;
return SCEE_OK;
// Encrypt/Decrypt String.
size_t crypt_string_get_length(size_t current_size, int operation)
if (operation == SCEE_CRYPT_ENCRYPT)
return b64_get_length(current_size + SCEE_SALT_LENGTH + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH, SCEE_B64_ENCODE);
else
size_t temp_length = b64_get_length(current_size, SCEE_B64_DECODE);
return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1;
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out)
// Generate a 128-bit salt using a CSPRNG.
uint8_t salt[SCEE_SALT_LENGTH];
if (!RAND_bytes(salt, SCEE_SALT_LENGTH)) return SCEE_ERROR_RAND;
// Use PBKDF2 to derive a key.
uint8_t key[SCEE_KEY_LENGTH];
int r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
// Encrypt and prepend salt.
size_t ciphertext_and_nonce_length = plaintext_length + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH;
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
r = encrypt(plaintext, plaintext_length, key, ciphertext_and_nonce);
if (r != SCEE_OK) return r;
size_t ciphertext_and_nonce_and_salt_length = ciphertext_and_nonce_length + SCEE_SALT_LENGTH;
uint8_t ciphertext_and_nonce_and_salt[ciphertext_and_nonce_and_salt_length];
memcpy(ciphertext_and_nonce_and_salt, salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce, ciphertext_and_nonce_length);
return b64_encode(ciphertext_and_nonce_and_salt, ciphertext_and_nonce_and_salt_length, ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out)
// Decode the base64.
size_t actual_size;
size_t max_size = b64_get_length(base64_length, SCEE_B64_DECODE);
uint8_t ciphertext_and_nonce_and_salt[max_size];
int r = b64_decode(base64_ciphertext_and_nonce_and_salt, base64_length, ciphertext_and_nonce_and_salt, &actual_size);
if (r != SCEE_OK) return r;
// Retrieve the salt and ciphertext.
size_t ciphertext_and_nonce_length = actual_size - SCEE_SALT_LENGTH;
uint8_t salt[SCEE_SALT_LENGTH];
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
memcpy(salt, ciphertext_and_nonce_and_salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce, ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce_length);
// Use PBKDF2 to derive the key.
uint8_t key[SCEE_KEY_LENGTH];
r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
*plaintext_length_out = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
plaintext_out[*plaintext_length_out] = '';
// Decrypt and return result.
return decrypt(ciphertext_and_nonce, ciphertext_and_nonce_length, key, plaintext_out);
// Encrypt/Decrypt.
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce)
// Generate a 96-bit nonce using a CSPRNG.
uint8_t nonce[SCEE_NONCE_LENGTH];
if (!RAND_bytes(nonce, SCEE_NONCE_LENGTH)) return SCEE_ERROR_RAND;
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_EncryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Encrypt and prepend nonce.
int temp_length;
uint8_t ciphertext[plaintext_length];
if (!EVP_EncryptUpdate(ctx, ciphertext, &temp_length, plaintext, plaintext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_EncryptFinal_ex(ctx, ciphertext + temp_length, &temp_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_FINAL;
uint8_t tag[SCEE_TAG_LENGTH];
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SCEE_TAG_LENGTH, tag))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG;
memcpy(ciphertext_and_nonce, nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext, plaintext_length);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH + plaintext_length, tag, SCEE_TAG_LENGTH);
return SCEE_OK;
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext)
// Retrieve the nonce and ciphertext.
size_t ciphertext_length = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
uint8_t ciphertext[ciphertext_length];
uint8_t nonce[SCEE_NONCE_LENGTH];
uint8_t tag[SCEE_NONCE_LENGTH];
memcpy(nonce, ciphertext_and_nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext, ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext_length);
memcpy(tag, ciphertext_and_nonce + SCEE_NONCE_LENGTH + ciphertext_length, SCEE_TAG_LENGTH);
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_DecryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Decrypt and return result.
int temp_length;
if (!EVP_DecryptUpdate(ctx, plaintext, &temp_length, ciphertext, ciphertext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SCEE_TAG_LENGTH, tag))
return SCEE_ERROR_CRYPT_TAG;
if (EVP_DecryptFinal_ex(ctx, plaintext + temp_length, &temp_length) < 1)
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG_INVALID;
return SCEE_OK;
c cryptography openssl
I wrote this code for inclusion in a repository of encryption code examples. The cryptographic primitives in use are required for compatibility with the other code samples.
It's been a long while since I've used C, so I expect I will have made many mistakes. What can I do to improve the below code? It is quite lengthy, so apologies in advance.
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define SCEE_ALGORITHM EVP_aes_128_gcm
#define SCEE_KEY_LENGTH 16
#define SCEE_TAG_LENGTH 16
#define SCEE_NONCE_LENGTH 12
#define SCEE_SALT_LENGTH 16
#define SCEE_PBKDF2_ITERATIONS 32767
#define SCEE_PBKDF2_HASH EVP_sha256
#define SCEE_OK 0
#define SCEE_ERROR_RAND 1
#define SCEE_ERROR_CTX_NEW 2
#define SCEE_ERROR_CTX_ALGORITHM 3
#define SCEE_ERROR_CTX_KEY_NONCE 4
#define SCEE_ERROR_CRYPT 5
#define SCEE_ERROR_CRYPT_FINAL 6
#define SCEE_ERROR_CRYPT_TAG 7
#define SCEE_ERROR_CRYPT_TAG_INVALID 8
#define SCEE_ERROR_B64 9
#define SCEE_ERROR_PBKDF2 10
#define SCEE_B64_ENCODE 0
#define SCEE_B64_DECODE 1
#define SCEE_CRYPT_ENCRYPT 0
#define SCEE_CRYPT_DECRYPT 1
size_t b64_get_length(size_t current_size, int operation);
int b64_encode(const uint8_t* bytes, size_t length, char* str);
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out);
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length);
size_t crypt_string_get_length(size_t current_size, int operation);
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out);
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce);
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext);
// Base64.
size_t b64_get_length(size_t current_size, int operation)
if (operation == SCEE_B64_ENCODE)
return ((current_size + 2) / 3) * 4 + 1;
else
return (current_size * 3) / 4;
int b64_encode(const uint8_t* bytes, size_t length, char* str)
if (!EVP_EncodeBlock(str, bytes, length))
return SCEE_ERROR_B64;
return SCEE_OK;
int b64_decode(const char* str, size_t length, uint8_t* bytes, size_t* decode_size_out)
int pad_count = 0;
if (str[length - 1] == '=') pad_count++;
if (str[length - 2] == '=') pad_count++;
int decode_size = EVP_DecodeBlock(bytes, str, length);
if (decode_size == -1)
return SCEE_ERROR_B64;
*decode_size_out = (size_t)decode_size - pad_count;
return SCEE_OK;
// PBKDF2.
int pbkdf2(const char* password, size_t password_length, const uint8_t* salt, size_t salt_length, int iterations, const EVP_MD* digest, uint8_t* key_out, size_t key_length)
if (!PKCS5_PBKDF2_HMAC(password, password_length, salt, salt_length, iterations, digest, key_length, key_out))
return SCEE_ERROR_PBKDF2;
return SCEE_OK;
// Encrypt/Decrypt String.
size_t crypt_string_get_length(size_t current_size, int operation)
if (operation == SCEE_CRYPT_ENCRYPT)
return b64_get_length(current_size + SCEE_SALT_LENGTH + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH, SCEE_B64_ENCODE);
else
size_t temp_length = b64_get_length(current_size, SCEE_B64_DECODE);
return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1;
int encrypt_string(const char* plaintext, size_t plaintext_length, const char* password, size_t password_length, char* ciphertext_out)
// Generate a 128-bit salt using a CSPRNG.
uint8_t salt[SCEE_SALT_LENGTH];
if (!RAND_bytes(salt, SCEE_SALT_LENGTH)) return SCEE_ERROR_RAND;
// Use PBKDF2 to derive a key.
uint8_t key[SCEE_KEY_LENGTH];
int r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
// Encrypt and prepend salt.
size_t ciphertext_and_nonce_length = plaintext_length + SCEE_NONCE_LENGTH + SCEE_TAG_LENGTH;
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
r = encrypt(plaintext, plaintext_length, key, ciphertext_and_nonce);
if (r != SCEE_OK) return r;
size_t ciphertext_and_nonce_and_salt_length = ciphertext_and_nonce_length + SCEE_SALT_LENGTH;
uint8_t ciphertext_and_nonce_and_salt[ciphertext_and_nonce_and_salt_length];
memcpy(ciphertext_and_nonce_and_salt, salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce, ciphertext_and_nonce_length);
return b64_encode(ciphertext_and_nonce_and_salt, ciphertext_and_nonce_and_salt_length, ciphertext_out);
int decrypt_string(const char* base64_ciphertext_and_nonce_and_salt, size_t base64_length, const char* password, size_t password_length, char* plaintext_out, size_t* plaintext_length_out)
// Decode the base64.
size_t actual_size;
size_t max_size = b64_get_length(base64_length, SCEE_B64_DECODE);
uint8_t ciphertext_and_nonce_and_salt[max_size];
int r = b64_decode(base64_ciphertext_and_nonce_and_salt, base64_length, ciphertext_and_nonce_and_salt, &actual_size);
if (r != SCEE_OK) return r;
// Retrieve the salt and ciphertext.
size_t ciphertext_and_nonce_length = actual_size - SCEE_SALT_LENGTH;
uint8_t salt[SCEE_SALT_LENGTH];
uint8_t ciphertext_and_nonce[ciphertext_and_nonce_length];
memcpy(salt, ciphertext_and_nonce_and_salt, SCEE_SALT_LENGTH);
memcpy(ciphertext_and_nonce, ciphertext_and_nonce_and_salt + SCEE_SALT_LENGTH, ciphertext_and_nonce_length);
// Use PBKDF2 to derive the key.
uint8_t key[SCEE_KEY_LENGTH];
r = pbkdf2(password, password_length, salt, SCEE_SALT_LENGTH, SCEE_PBKDF2_ITERATIONS, SCEE_PBKDF2_HASH(), key, SCEE_KEY_LENGTH);
if (r != SCEE_OK) return r;
*plaintext_length_out = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
plaintext_out[*plaintext_length_out] = '';
// Decrypt and return result.
return decrypt(ciphertext_and_nonce, ciphertext_and_nonce_length, key, plaintext_out);
// Encrypt/Decrypt.
int encrypt(const uint8_t* plaintext, size_t plaintext_length, const uint8_t* key, uint8_t* ciphertext_and_nonce)
// Generate a 96-bit nonce using a CSPRNG.
uint8_t nonce[SCEE_NONCE_LENGTH];
if (!RAND_bytes(nonce, SCEE_NONCE_LENGTH)) return SCEE_ERROR_RAND;
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_EncryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Encrypt and prepend nonce.
int temp_length;
uint8_t ciphertext[plaintext_length];
if (!EVP_EncryptUpdate(ctx, ciphertext, &temp_length, plaintext, plaintext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_EncryptFinal_ex(ctx, ciphertext + temp_length, &temp_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_FINAL;
uint8_t tag[SCEE_TAG_LENGTH];
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SCEE_TAG_LENGTH, tag))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG;
memcpy(ciphertext_and_nonce, nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext, plaintext_length);
memcpy(ciphertext_and_nonce + SCEE_NONCE_LENGTH + plaintext_length, tag, SCEE_TAG_LENGTH);
return SCEE_OK;
int decrypt(const uint8_t* ciphertext_and_nonce, size_t ciphertext_and_nonce_length, const uint8_t* key, uint8_t* plaintext)
// Retrieve the nonce and ciphertext.
size_t ciphertext_length = ciphertext_and_nonce_length - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH;
uint8_t ciphertext[ciphertext_length];
uint8_t nonce[SCEE_NONCE_LENGTH];
uint8_t tag[SCEE_NONCE_LENGTH];
memcpy(nonce, ciphertext_and_nonce, SCEE_NONCE_LENGTH);
memcpy(ciphertext, ciphertext_and_nonce + SCEE_NONCE_LENGTH, ciphertext_length);
memcpy(tag, ciphertext_and_nonce + SCEE_NONCE_LENGTH + ciphertext_length, SCEE_TAG_LENGTH);
// Create the cipher context and initialize.
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return SCEE_ERROR_CTX_NEW;
if (!EVP_DecryptInit_ex(ctx, SCEE_ALGORITHM(), NULL, NULL, NULL))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_ALGORITHM;
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CTX_KEY_NONCE;
// Decrypt and return result.
int temp_length;
if (!EVP_DecryptUpdate(ctx, plaintext, &temp_length, ciphertext, ciphertext_length))
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT;
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, SCEE_TAG_LENGTH, tag))
return SCEE_ERROR_CRYPT_TAG;
if (EVP_DecryptFinal_ex(ctx, plaintext + temp_length, &temp_length) < 1)
EVP_CIPHER_CTX_free(ctx);
return SCEE_ERROR_CRYPT_TAG_INVALID;
return SCEE_OK;
c cryptography openssl
asked May 15 at 12:31
Luke Park
18016
18016
Rather than having all the function declarations at the top, you might want to create a header file to provide entry points to a user. This may be a copy and paste issue but the vertical spacing is inconsistent.
â pacmaninbw
May 15 at 15:54
add a comment |Â
Rather than having all the function declarations at the top, you might want to create a header file to provide entry points to a user. This may be a copy and paste issue but the vertical spacing is inconsistent.
â pacmaninbw
May 15 at 15:54
Rather than having all the function declarations at the top, you might want to create a header file to provide entry points to a user. This may be a copy and paste issue but the vertical spacing is inconsistent.
â pacmaninbw
May 15 at 15:54
Rather than having all the function declarations at the top, you might want to create a header file to provide entry points to a user. This may be a copy and paste issue but the vertical spacing is inconsistent.
â pacmaninbw
May 15 at 15:54
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
1
down vote
Remarks:
b64_get_lengthmay return a larger (for decoding) or smaller
(for encoding) than the amount that was base64 encoded because of
the padding;- it's arguably better to create two methods rather than
b64_get_lengthto determine the length, one for encoding and one
for decoding, similarly forcrypt_string_get_length- there is
hardly any code reuse anyway. return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1: the+ 1looks like a bad way to fix the code
and it is not specified why it is used (OK, after reading the code
you'll find that it is because of the null-terminator, but still).- making the number of iterations and including it into the result would be a good idea; another would be to include a protocol version
instead (do please create a protocol description, even if just in
MarkDown or similar) - note that 32Ki iterations is already on the
low side. - you could directly encrypt to
ciphertext_and_nonce_and_salt_length, having an additional buffer
/ copy seems wasteful (see also the next point). - instead of
memcopyyou should simply use pointer arithmetic to set the location of the salt, nonce and ciphertext. - you do not need a nonce if your key always changes: and the salt will make sure of that. Just use an all zero nonce (but document why it isn't required in case somebody "fixes" your code and uses a static salt or similar).
- after use you may want to zero out the password and key so they won't remain in memory.
Otherwise the crypto seems fine and the constants make sense. I don't see too much other things wrong with it.
A note on password verification
PBKDF2 with SHA-256 should be restricted to 256 bits of output. However, that would still leave you with 128 bits. You could use this to verify the password is correct by including it to the ciphertext. The other bits of the key would still be independent from it, so this is secure.
It has the advantage that you don't have to decrypt all the data - say a DVD file of 4 GiB - before you know that the password was indeed correct. It could also be used to distinguish between broken files (e.g. incomplete ones) and bad password / keys.
Of course an attacker could alter the ciphertext to simulate either situation.
As for handling large amounts of data: implement streaming using calls to update. I presume that this is for relatively small strings, but you may want to document that.
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
add a comment |Â
up vote
0
down vote
Some other minor stuff that Maarten didn't cover.
You write in some places:
if (foo) return bar;
else return baz;
And in others:
if (foo) return bar;
return baz;
I prefer the latter. The else is not needed. But whatever you do, stay consistent.
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
Remarks:
b64_get_lengthmay return a larger (for decoding) or smaller
(for encoding) than the amount that was base64 encoded because of
the padding;- it's arguably better to create two methods rather than
b64_get_lengthto determine the length, one for encoding and one
for decoding, similarly forcrypt_string_get_length- there is
hardly any code reuse anyway. return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1: the+ 1looks like a bad way to fix the code
and it is not specified why it is used (OK, after reading the code
you'll find that it is because of the null-terminator, but still).- making the number of iterations and including it into the result would be a good idea; another would be to include a protocol version
instead (do please create a protocol description, even if just in
MarkDown or similar) - note that 32Ki iterations is already on the
low side. - you could directly encrypt to
ciphertext_and_nonce_and_salt_length, having an additional buffer
/ copy seems wasteful (see also the next point). - instead of
memcopyyou should simply use pointer arithmetic to set the location of the salt, nonce and ciphertext. - you do not need a nonce if your key always changes: and the salt will make sure of that. Just use an all zero nonce (but document why it isn't required in case somebody "fixes" your code and uses a static salt or similar).
- after use you may want to zero out the password and key so they won't remain in memory.
Otherwise the crypto seems fine and the constants make sense. I don't see too much other things wrong with it.
A note on password verification
PBKDF2 with SHA-256 should be restricted to 256 bits of output. However, that would still leave you with 128 bits. You could use this to verify the password is correct by including it to the ciphertext. The other bits of the key would still be independent from it, so this is secure.
It has the advantage that you don't have to decrypt all the data - say a DVD file of 4 GiB - before you know that the password was indeed correct. It could also be used to distinguish between broken files (e.g. incomplete ones) and bad password / keys.
Of course an attacker could alter the ciphertext to simulate either situation.
As for handling large amounts of data: implement streaming using calls to update. I presume that this is for relatively small strings, but you may want to document that.
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
add a comment |Â
up vote
1
down vote
Remarks:
b64_get_lengthmay return a larger (for decoding) or smaller
(for encoding) than the amount that was base64 encoded because of
the padding;- it's arguably better to create two methods rather than
b64_get_lengthto determine the length, one for encoding and one
for decoding, similarly forcrypt_string_get_length- there is
hardly any code reuse anyway. return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1: the+ 1looks like a bad way to fix the code
and it is not specified why it is used (OK, after reading the code
you'll find that it is because of the null-terminator, but still).- making the number of iterations and including it into the result would be a good idea; another would be to include a protocol version
instead (do please create a protocol description, even if just in
MarkDown or similar) - note that 32Ki iterations is already on the
low side. - you could directly encrypt to
ciphertext_and_nonce_and_salt_length, having an additional buffer
/ copy seems wasteful (see also the next point). - instead of
memcopyyou should simply use pointer arithmetic to set the location of the salt, nonce and ciphertext. - you do not need a nonce if your key always changes: and the salt will make sure of that. Just use an all zero nonce (but document why it isn't required in case somebody "fixes" your code and uses a static salt or similar).
- after use you may want to zero out the password and key so they won't remain in memory.
Otherwise the crypto seems fine and the constants make sense. I don't see too much other things wrong with it.
A note on password verification
PBKDF2 with SHA-256 should be restricted to 256 bits of output. However, that would still leave you with 128 bits. You could use this to verify the password is correct by including it to the ciphertext. The other bits of the key would still be independent from it, so this is secure.
It has the advantage that you don't have to decrypt all the data - say a DVD file of 4 GiB - before you know that the password was indeed correct. It could also be used to distinguish between broken files (e.g. incomplete ones) and bad password / keys.
Of course an attacker could alter the ciphertext to simulate either situation.
As for handling large amounts of data: implement streaming using calls to update. I presume that this is for relatively small strings, but you may want to document that.
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
add a comment |Â
up vote
1
down vote
up vote
1
down vote
Remarks:
b64_get_lengthmay return a larger (for decoding) or smaller
(for encoding) than the amount that was base64 encoded because of
the padding;- it's arguably better to create two methods rather than
b64_get_lengthto determine the length, one for encoding and one
for decoding, similarly forcrypt_string_get_length- there is
hardly any code reuse anyway. return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1: the+ 1looks like a bad way to fix the code
and it is not specified why it is used (OK, after reading the code
you'll find that it is because of the null-terminator, but still).- making the number of iterations and including it into the result would be a good idea; another would be to include a protocol version
instead (do please create a protocol description, even if just in
MarkDown or similar) - note that 32Ki iterations is already on the
low side. - you could directly encrypt to
ciphertext_and_nonce_and_salt_length, having an additional buffer
/ copy seems wasteful (see also the next point). - instead of
memcopyyou should simply use pointer arithmetic to set the location of the salt, nonce and ciphertext. - you do not need a nonce if your key always changes: and the salt will make sure of that. Just use an all zero nonce (but document why it isn't required in case somebody "fixes" your code and uses a static salt or similar).
- after use you may want to zero out the password and key so they won't remain in memory.
Otherwise the crypto seems fine and the constants make sense. I don't see too much other things wrong with it.
A note on password verification
PBKDF2 with SHA-256 should be restricted to 256 bits of output. However, that would still leave you with 128 bits. You could use this to verify the password is correct by including it to the ciphertext. The other bits of the key would still be independent from it, so this is secure.
It has the advantage that you don't have to decrypt all the data - say a DVD file of 4 GiB - before you know that the password was indeed correct. It could also be used to distinguish between broken files (e.g. incomplete ones) and bad password / keys.
Of course an attacker could alter the ciphertext to simulate either situation.
As for handling large amounts of data: implement streaming using calls to update. I presume that this is for relatively small strings, but you may want to document that.
Remarks:
b64_get_lengthmay return a larger (for decoding) or smaller
(for encoding) than the amount that was base64 encoded because of
the padding;- it's arguably better to create two methods rather than
b64_get_lengthto determine the length, one for encoding and one
for decoding, similarly forcrypt_string_get_length- there is
hardly any code reuse anyway. return temp_length - SCEE_SALT_LENGTH - SCEE_NONCE_LENGTH - SCEE_TAG_LENGTH + 1: the+ 1looks like a bad way to fix the code
and it is not specified why it is used (OK, after reading the code
you'll find that it is because of the null-terminator, but still).- making the number of iterations and including it into the result would be a good idea; another would be to include a protocol version
instead (do please create a protocol description, even if just in
MarkDown or similar) - note that 32Ki iterations is already on the
low side. - you could directly encrypt to
ciphertext_and_nonce_and_salt_length, having an additional buffer
/ copy seems wasteful (see also the next point). - instead of
memcopyyou should simply use pointer arithmetic to set the location of the salt, nonce and ciphertext. - you do not need a nonce if your key always changes: and the salt will make sure of that. Just use an all zero nonce (but document why it isn't required in case somebody "fixes" your code and uses a static salt or similar).
- after use you may want to zero out the password and key so they won't remain in memory.
Otherwise the crypto seems fine and the constants make sense. I don't see too much other things wrong with it.
A note on password verification
PBKDF2 with SHA-256 should be restricted to 256 bits of output. However, that would still leave you with 128 bits. You could use this to verify the password is correct by including it to the ciphertext. The other bits of the key would still be independent from it, so this is secure.
It has the advantage that you don't have to decrypt all the data - say a DVD file of 4 GiB - before you know that the password was indeed correct. It could also be used to distinguish between broken files (e.g. incomplete ones) and bad password / keys.
Of course an attacker could alter the ciphertext to simulate either situation.
As for handling large amounts of data: implement streaming using calls to update. I presume that this is for relatively small strings, but you may want to document that.
answered May 28 at 20:53
Maarten Bodewes
417211
417211
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
add a comment |Â
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Sorry for the late reply... I don't often visit code review...
â Maarten Bodewes
May 28 at 20:54
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
Thanks for your reply. I like your idea regarding the unused 128-bits of PBKDF2 output, I've never thought of using it like that before. Thanks for your analysis.
â Luke Park
May 28 at 21:00
add a comment |Â
up vote
0
down vote
Some other minor stuff that Maarten didn't cover.
You write in some places:
if (foo) return bar;
else return baz;
And in others:
if (foo) return bar;
return baz;
I prefer the latter. The else is not needed. But whatever you do, stay consistent.
add a comment |Â
up vote
0
down vote
Some other minor stuff that Maarten didn't cover.
You write in some places:
if (foo) return bar;
else return baz;
And in others:
if (foo) return bar;
return baz;
I prefer the latter. The else is not needed. But whatever you do, stay consistent.
add a comment |Â
up vote
0
down vote
up vote
0
down vote
Some other minor stuff that Maarten didn't cover.
You write in some places:
if (foo) return bar;
else return baz;
And in others:
if (foo) return bar;
return baz;
I prefer the latter. The else is not needed. But whatever you do, stay consistent.
Some other minor stuff that Maarten didn't cover.
You write in some places:
if (foo) return bar;
else return baz;
And in others:
if (foo) return bar;
return baz;
I prefer the latter. The else is not needed. But whatever you do, stay consistent.
answered May 28 at 21:07
Reinderien
900415
900415
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194449%2fopenssl-aes-gcm-convenience-wrapper-in-c%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Rather than having all the function declarations at the top, you might want to create a header file to provide entry points to a user. This may be a copy and paste issue but the vertical spacing is inconsistent.
â pacmaninbw
May 15 at 15:54