crypto.encrypt_base64

STRINGcrypto.encrypt_base64IDcipherIDmodeIDpaddingSTRINGkey_hexSTRINGiv_hexSTRINGplaintext_base64

Available inall subroutines.

Symmetric key encryption.

The key and initialization vector (IV) must be hex-encoded strings. The plaintext must be Base64-encoded. The returned ciphertext is also Base64-encoded.

This function uses standard Base64 encoding as defined in RFC 4648 Section 4, with the alphabet A-Za-z0-9+/ and = padding. The URL-safe variant (-_ instead of +/) is not supported. On input, padding is optional. On output, padding is always included.

Parameters

ParameterTypeDescription
cipherCIPHEREncryption algorithm: aes128, aes192, or aes256
modeMODEBlock cipher mode: cbc, ctr, gcm, or ccm
paddingPADDINGPadding scheme: pkcs7 or nopad
key_hexSTRINGHex-encoded encryption key
iv_hexSTRINGHex-encoded initialization vector or nonce
plaintext_base64STRINGBase64-encoded data to encrypt

Supported combinations

CipherModePaddingKey lengthIV lengthTag lengthDescription
aes128cbcpkcs7128 bits128 bitsAES-128-CBC
aes192cbcpkcs7192 bits128 bitsAES-192-CBC
aes256cbcpkcs7256 bits128 bitsAES-256-CBC
aes128cbcnopad128 bits128 bitsAES-128-CBC raw
aes192cbcnopad192 bits128 bitsAES-192-CBC raw
aes256cbcnopad256 bits128 bitsAES-256-CBC raw
aes128ctrnopad128 bits128 bitsAES-128-CTR
aes192ctrnopad192 bits128 bitsAES-192-CTR
aes256ctrnopad256 bits128 bitsAES-256-CTR
aes128gcmnopad128 bits96 bits128 bitsAES-128-GCM
aes192gcmnopad192 bits96 bits128 bitsAES-192-GCM
aes256gcmnopad256 bits96 bits128 bitsAES-256-GCM
aes128ccmnopad128 bits56 bits96 bitsAES-128-CCM
aes192ccmnopad192 bits56 bits96 bitsAES-192-CCM
aes256ccmnopad256 bits56 bits96 bitsAES-256-CCM

Basic example

declare local var.key_hex STRING;
declare local var.iv_hex STRING;
declare local var.plaintext_base64 STRING;
declare local var.ciphertext_base64 STRING;
set var.key_hex = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
set var.iv_hex = "000102030405060708090a0b";
set var.plaintext_base64 = "aGVsbG8gd29ybGQ="; # "hello world"
set var.ciphertext_base64 = crypto.encrypt_base64(aes256, gcm, nopad, var.key_hex, var.iv_hex, var.plaintext_base64);

Encryption modes

Each mode has different security properties and requirements for the iv parameter. See the sections on key generation and generating IVs and nonces below for guidance on producing these values correctly.

AES-GCM

AES-GCM is an authenticated encryption mode, meaning that an adversary cannot change the ciphertext (and thus, the decrypted plaintext) without this being detected. It performs well on modern CPUs as it can easily be parallelized.

For a given key, the iv parameter doesn't have to be unpredictable, but must be unique for every plaintext. Reusing a nonce with the same key would immediately reveal the difference between plaintexts from the ciphertexts.

AES-CTR

AES-CTR is an unauthenticated mode, meaning that an adversary can change the ciphertext, resulting in a different plaintext than the original one, without this being detectable. In fact, an adversary can trivially flip any bits they want: flipping a bit in the ciphertext will cause decryption to return the original plaintext with the same bits flipped at the same positions. For this reason, AES-CTR should never be used against an active adversary unless an additional authentication layer is implemented. Despite this limitation, performance is good on modern CPUs as it can easily be parallelized.

For a given key, the iv parameter doesn't have to be unpredictable, but must be unique for every plaintext. Reusing a nonce with the same key would immediately reveal the difference between plaintexts from the ciphertexts.

AES-CBC

AES-CBC is an unauthenticated mode, meaning that an adversary can change the ciphertext, resulting in a different plaintext than the original one, without this being detectable. Additionally, implementations of AES-CBC with padding are often vulnerable to side-channel attacks that are difficult to mitigate. Performance is also limited, as it cannot take advantage of the parallelism of modern CPUs. For these reasons, AES-CBC is not recommended for general use, but is still required by some protocols.

Unlike other modes, the iv parameter must be unpredictable by an adversary and appear to be randomly chosen from a uniform distribution. For a given key, it must also be different for every plaintext.

With the nopad padding, the plaintext length (after Base64 decoding) must be a multiple of 128 bits (16 bytes), and the output will have the same decoded length. With the pkcs7 padding, the plaintext can be of any length, but the output will have a decoded length equal to the plaintext length rounded up to 128 bits.

AES-CCM

AES-CCM is an authenticated encryption mode, meaning that an adversary cannot change the ciphertext (and thus, the decrypted plaintext) without this being detected. However, performance is limited as it cannot be parallelized. AES-CCM is still in use in legacy, constrained, and standards-driven environments, but rarely used in modern designs.

For a given key, the iv parameter doesn't have to be unpredictable, but must be unique for every plaintext. Reusing a nonce with the same key would immediately reveal the difference between plaintexts from the ciphertexts.

Key generation

Secret keys must be randomly sampled from a uniform distribution. This can be done in a terminal with the openssl command:

$ openssl rand -hex 32

The provided number is a number of bytes, so the above command generates a 256-bit key.

Generating IVs and nonces

The CBC mode requires an unpredictable initialization vector (IV) for every message. Other modes require a nonce, which must be unique for every message but not necessarily unpredictable. In distributed systems, using random nonces is the easiest option.

However, with the currently supported ciphers, using random nonces with a static key has low usage limits. In particular, AES-CTR and AES-GCM cannot safely be used with more than 2^32 inputs.

The recommended approach is to derive a per-message key and nonce from a master key and a longer nonce, ensuring that (key, nonce) pairs are not reused with distinct messages:

sub compute_message_nonce192_hex(STRING var.key_hex, STRING var.plaintext_base64) STRING {
declare local var.ikm STRING;
set var.ikm = "nonce|" + randomstr(48, "0123456789abcdef") + var.plaintext_base64;
return substr(digest.hmac_sha256(var.key_hex, var.ikm), 2, 48);
}
sub compute_message_key256_hex(STRING var.key_hex, STRING var.nonce192_hex) STRING {
return substr(digest.hmac_sha256(var.key_hex, "key256|" + var.nonce192_hex), 2, 64);
}
sub compute_message_nonce96_hex(STRING var.key_hex, STRING var.nonce192_hex) STRING {
return substr(digest.hmac_sha256(var.key_hex, "nonce96|" + var.nonce192_hex), 2, 24);
}

Complete example

The following example demonstrates encryption with automatic nonce derivation, using the helper functions defined above. The output is a string containing the 192-bit nonce (48 hex characters) followed by the Base64-encoded ciphertext:

sub encrypt_base64(STRING var.key_hex, STRING var.plaintext_base64) STRING {
declare local var.nonce192_hex STRING;
set var.nonce192_hex = compute_message_nonce192_hex(var.key_hex, var.plaintext_base64);
declare local var.msg_key_hex STRING;
set var.msg_key_hex = compute_message_key256_hex(var.key_hex, var.nonce192_hex);
declare local var.msg_nonce_hex STRING;
set var.msg_nonce_hex = compute_message_nonce96_hex(var.key_hex, var.nonce192_hex);
declare local var.encrypted STRING;
set var.encrypted = crypto.encrypt_base64(aes256, gcm, nopad, var.msg_key_hex, var.msg_nonce_hex, var.plaintext_base64);
declare local var.result STRING;
set var.result = var.nonce192_hex + var.encrypted;
return var.result;
}
sub decrypt_base64(STRING var.key_hex, STRING var.ciphertext) STRING {
# Ciphertext must contain at least the 192-bit nonce (48 hex chars) and 128-bit GCM tag
declare local var.nonce192_hex STRING;
set var.nonce192_hex = substr(var.ciphertext, 0, 48);
declare local var.msg_key_hex STRING;
set var.msg_key_hex = compute_message_key256_hex(var.key_hex, var.nonce192_hex);
declare local var.msg_nonce_hex STRING;
set var.msg_nonce_hex = compute_message_nonce96_hex(var.key_hex, var.nonce192_hex);
return crypto.decrypt_base64(aes256, gcm, nopad, var.msg_key_hex, var.msg_nonce_hex, substr(var.ciphertext, 48));
}
declare local var.key_hex STRING;
set var.key_hex = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
declare local var.plaintext_base64 STRING;
set var.plaintext_base64 = "aGVsbG8gd29ybGQ="; # "hello world"
declare local var.ciphertext STRING;
set var.ciphertext = encrypt_base64(var.key_hex, var.plaintext_base64);
declare local var.decrypted_base64 STRING;
set var.decrypted_base64 = decrypt_base64(var.key_hex, var.ciphertext);
if (fastly.error == "EBADDECRYPT") {
error 403 "Decryption failed";
} else if (fastly.error) {
error 503;
}

Errors

If the requirements for the given cipher and mode are not met, or if the hex-encoded arguments are not valid hex, then fastly.error will be set to EINVAL. The Base64 decoder is lenient and silently ignores non-Base64 characters rather than rejecting them.

If decryption fails, then fastly.error will be set to EBADDECRYPT.

Provided that the parameter lengths are correct, unauthenticated modes will never fail on decryption, even if the key is incorrect, but the computed plaintext may then not match the original one.