Because until now, openssl enc
does not support AES-256-GCM, I've written the following C source code to do what openssl enc
would do:
(compile this way gcc -Wall -lcrypto -o aes256gcm aes256gcm.c
)
// AES-256-GCM with libcrypto
// gcc -Wall -lcrypto -o aes256gcm aes256gcm.c
// tag is 16 bytes long
// no AAD (Additional Associated Data)
// output format: tag is written just after cipher text (see RFC-5116, sections 5.1 and 5.2)
// KEY=a6a7ee7abe681c9c4cede8e3366a9ded96b92668ea5e26a31a4b0856341ed224
// IV=87b7225d16ea2ae1f41d0b13fdce9bba
// echo -n 'plain text' | ./aes256gcm $KEY $IV | od -t x1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
EVP_CIPHER_CTX *ctx = NULL;
unsigned char *iv = NULL;
unsigned char *buf_plain = NULL;
unsigned char *buf_cipher = NULL;
typedef enum { false, true } bool;
void freeCrypto() {
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
ctx = NULL;
}
CRYPTO_cleanup_all_ex_data();
ERR_free_strings();
if (iv) {
free(iv);
iv = NULL;
}
if (buf_plain) {
free(buf_plain);
buf_plain = NULL;
}
if (buf_cipher) {
free(buf_cipher);
buf_cipher = NULL;
}
}
void handleCryptoError() {
fprintf(stderr, "ERROR\n");
ERR_print_errors_fp(stderr);
freeCrypto();
exit(1);
}
bool isValidHexChar(char c) {
return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}
unsigned char hex2uchar(char *hex) {
unsigned char ret;
if (hex[0] >= 'a' && hex[0] <= 'f') ret = (hex[0] - 'a' + 10) * 16;
else ret = (hex[0] - '0') * 16;
if (hex[1] >= 'a' && hex[1] <= 'f') ret += hex[1] - 'a' + 10;
else ret += hex[1] - '0';
return ret;
}
int main(int ac, char **av, char **ae)
{
const EVP_CIPHER *cipher;
unsigned char key[32];
int iv_len, len, i;
unsigned char tag[16];
if (ac != 3) {
fprintf(stderr, "usage: %s KEY IV\n", av[0]);
return 1;
}
char *key_txt = av[1];
char *iv_txt = av[2];
ERR_load_crypto_strings();
if (strlen(key_txt) != 2 * sizeof key) {
fprintf(stderr, "invalid key size\n");
freeCrypto();
return 1;
}
if (strlen(iv_txt) < 2 || strlen(iv_txt) % 2) {
fprintf(stderr, "invalid IV size\n");
freeCrypto();
return 1;
}
iv_len = strlen(iv_txt) / 2;
if (!(iv = malloc(iv_len))) {
perror("malloc");
freeCrypto();
return 1;
}
if (!(buf_plain = malloc(iv_len))) {
perror("malloc");
freeCrypto();
return 1;
}
if (!(buf_cipher = malloc(iv_len))) {
perror("malloc");
freeCrypto();
return 1;
}
for (i = 0; i < sizeof key; i++) {
if (!isValidHexChar(key_txt[2*i]) || !isValidHexChar(key_txt[2*i+1])) handleCryptoError();
key[i] = hex2uchar(key_txt + 2*i);
}
for (i = 0; i < iv_len; i++) {
if (!isValidHexChar(iv_txt[2*i]) || !isValidHexChar(iv_txt[2*i+1])) handleCryptoError();
iv[i] = hex2uchar(iv_txt + 2*i);
}
if (!(ctx = EVP_CIPHER_CTX_new())) handleCryptoError();
if (!(cipher = EVP_aes_256_gcm())) handleCryptoError();
if (1 != EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL)) handleCryptoError();
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) handleCryptoError();
if (1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) handleCryptoError();
do {
size_t ret = fread(buf_plain, 1, iv_len, stdin);
if (!ret) {
if (ferror(stdin)) {
perror("fread");
freeCrypto();
return 1;
}
if (feof(stdin)) break;
}
if (1 != EVP_EncryptUpdate(ctx, buf_cipher, &len, buf_plain, ret)) handleCryptoError();
if (len && !fwrite(buf_cipher, len, 1, stdout)) {
if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
else perror("fwrite");
freeCrypto();
return 1;
}
} while (1);
if (1 != EVP_EncryptFinal_ex(ctx, buf_cipher, &len)) handleCryptoError();
if (len && !fwrite(buf_cipher, len, 1, stdout)) {
if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
else perror("fwrite");
freeCrypto();
return 1;
}
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag)) handleCryptoError();
if (!fwrite(tag, sizeof tag, 1, stdout)) {
if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
else perror("fwrite");
freeCrypto();
return 1;
}
fflush(stdout);
freeCrypto();
return 0;
}
And here is how to decrypt what the above program has encrypted:
(compile this way gcc -Wall -lcrypto -o aes256gcm-decrypt aes256gcm-decrypt.c
)
// AES-256-GCM with libcrypto
// gcc -Wall -lcrypto -o aes256gcm-decrypt aes256gcm-decrypt.c
// tag is 16 bytes long
// no AAD (Additional Associated Data)
// input format: tag is read just after cipher text (see RFC-5116, sections 5.1 and 5.2)
// KEY=a6a7ee7abe681c9c4cede8e3366a9ded96b92668ea5e26a31a4b0856341ed224
// IV=87b7225d16ea2ae1f41d0b13fdce9bba
// cat ciphertext | ./aes256gcm-decrypt $KEY $IV
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
EVP_CIPHER_CTX *ctx = NULL;
unsigned char *iv = NULL;
unsigned char *buf_plain = NULL;
unsigned char *buf_cipher = NULL;
unsigned char *input = NULL;
typedef enum { false, true } bool;
void freeCrypto() {
if (input) free(input);
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
ctx = NULL;
}
CRYPTO_cleanup_all_ex_data();
ERR_free_strings();
if (iv) {
free(iv);
iv = NULL;
}
if (buf_plain) {
free(buf_plain);
buf_plain = NULL;
}
if (buf_cipher) {
free(buf_cipher);
buf_cipher = NULL;
}
}
void handleCryptoError() {
fprintf(stderr, "ERROR\n");
ERR_print_errors_fp(stderr);
freeCrypto();
exit(1);
}
bool isValidHexChar(char c) {
return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}
unsigned char hex2uchar(char *hex) {
unsigned char ret;
if (hex[0] >= 'a' && hex[0] <= 'f') ret = (hex[0] - 'a' + 10) * 16;
else ret = (hex[0] - '0') * 16;
if (hex[1] >= 'a' && hex[1] <= 'f') ret += hex[1] - 'a' + 10;
else ret += hex[1] - '0';
return ret;
}
unsigned char *loadInput(int *plen) {
int len = 0;
unsigned char *buf = NULL;
unsigned char *old_buf;
do {
int c = fgetc(stdin);
if (c == EOF) break;
if (c < 0) {
perror("fgetc");
exit(1);
}
len++;
old_buf = buf;
buf = malloc(len);
if (buf < 0) {
perror("malloc");
exit(1);
}
if (len > 1) bcopy(old_buf, buf, len - 1);
buf[len - 1] = c;
if (old_buf) free(old_buf);
} while (1);
*plen = len;
return buf;
}
int main(int ac, char **av, char **ae)
{
const EVP_CIPHER *cipher;
unsigned char key[32];
int iv_len, len, i;
unsigned char *current;
int input_len;
if (ac != 3) {
fprintf(stderr, "usage: %s KEY IV\n", av[0]);
return 1;
}
char *key_txt = av[1];
char *iv_txt = av[2];
input = loadInput(&input_len);
current = input;
ERR_load_crypto_strings();
if (strlen(key_txt) != 2 * sizeof key) {
fprintf(stderr, "invalid key size\n");
freeCrypto();
return 1;
}
if (strlen(iv_txt) < 2 || strlen(iv_txt) % 2) {
fprintf(stderr, "invalid IV size\n");
freeCrypto();
return 1;
}
iv_len = strlen(iv_txt) / 2;
if (!(iv = malloc(iv_len))) {
perror("malloc");
freeCrypto();
return 1;
}
if (!(buf_plain = malloc(iv_len))) {
perror("malloc");
freeCrypto();
return 1;
}
if (!(buf_cipher = malloc(iv_len))) {
perror("malloc");
freeCrypto();
return 1;
}
for (i = 0; i < sizeof key; i++) {
if (!isValidHexChar(key_txt[2*i]) || !isValidHexChar(key_txt[2*i+1])) handleCryptoError();
key[i] = hex2uchar(key_txt + 2*i);
}
for (i = 0; i < iv_len; i++) {
if (!isValidHexChar(iv_txt[2*i]) || !isValidHexChar(iv_txt[2*i+1])) handleCryptoError();
iv[i] = hex2uchar(iv_txt + 2*i);
}
if (!(ctx = EVP_CIPHER_CTX_new())) handleCryptoError();
if (!(cipher = EVP_aes_256_gcm())) handleCryptoError();
if (1 != EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL)) handleCryptoError();
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) handleCryptoError();
if (1 != EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleCryptoError();
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, input + input_len - 16)) handleCryptoError();
do {
int nbytes = input + input_len - 16 - current;
if (nbytes > iv_len) nbytes = iv_len;
if (!nbytes) break;
bcopy(current, buf_plain, nbytes);
current += nbytes;
if (1 != EVP_DecryptUpdate(ctx, buf_cipher, &len, buf_plain, nbytes)) handleCryptoError();
if (len && !fwrite(buf_cipher, len, 1, stdout)) {
if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
else perror("fwrite");
freeCrypto();
return 1;
}
} while (1);
// correct tag is checked here
if (EVP_DecryptFinal_ex(ctx, buf_cipher, &len) <= 0) handleCryptoError();
if (len && !fwrite(buf_cipher, len, 1, stdout)) {
if (feof(stderr)) fprintf(stderr, "EOF on output stream\n");
else perror("fwrite");
freeCrypto();
return 1;
}
fflush(stdout);
freeCrypto();
return 0;
}
openssl enc
and the actual functions in the library. So I must write my own utility if I have to encrypt something using AESxxx-GCM. – Binarus Jul 05 '16 at 07:43ssk-keygen -o
, the new format and the new PBKDF (which gains its strength mainly from its slowness). Actually, my motivation to ask was to combine OpenSSH's new PBKDF with AES256-GCM for encrypting the private keys (ssh-keygen -o
seems to use AES128-GCM). So my next question would have been how to plug in another PBKDF into OpenSSL ... but I first wanted to understand the issues withopenssl enc
. – Binarus Jul 05 '16 at 07:47ssh-keygen -o
(or ed25519) defaults to (bcrypt plus) aes256-cbc, not aes128-gcm, but you can override with-Z aes{128,256}-gcm@openssh.com
(undocumented AFAICS). – dave_thompson_085 Jul 06 '16 at 03:53PEM_{read,write}{,_bio}_{manythings}
in NOTES says "The old PrivateKey routines are retained for compatibility. New applications should [use PKCS8] because ... more secure (they use an iteration count of 2048 whereas the traditional routines use a count of 1) ...." and just after that in PEM ENCRYPTION FORMAT "The old PrivateKey routines ... [use] EVP_BytesToKey [with salt and] an iteration count of 1 ...." -- and this gist (though not exact wording) goes back to at least 0.9.8m in 2010, the earliest I can easily check. – dave_thompson_085 Jul 06 '16 at 04:00enc
on the other hand, no such warning (or even detail) I've noticed. Oh, and yes OpenSSHcipher.c
does do the extra 'control' calls needed for GCM -- in routines that are used both for comms and for new-format privatekey files. – dave_thompson_085 Jul 06 '16 at 04:01ssh-keygen -o
and its undocumented features. Regarding the PBKDF, as far as I have understood, even 2048 rounds are unsafe if the hashing function can be computed extremely fast, like MD5 or SHA1 (I have read that a single consumer GPU can compute about 1 billion hashed per second for such functions). So I think OpenSSL can solve the problem only by using other PBKDFs which are very slow by design, for example bcrypt (likessh-keygen -o
). – Binarus Jul 08 '16 at 06:01ssh-keygen -o
and converts them to Putty's format. The initial strong encryption of those keys will be lost by converting them (e.g. due to the utility's weak PBKDF). I hope the developers will provide a reasonable method for securely encrypting the private keys in the future (or, better, allow direct usage of unaltered keys which have been generated byssh-keygen
). – Binarus Jul 14 '16 at 18:41