Skip to content

Encryption and Decryption Algorithms

This section explains the specific algorithms that are used when values 1 through 9 are specified for the ENCRYPT_KS and DECRYPT_KS functions. Values 4 through 9 only are supported for ENCRYPTED WITH column constraints in CREATE TABLE statements, using the same algorithms.

Function ParameterAlgorithmOutput Length (Encryption)Output Length (Decryption)
1Initialize AES with a 128-bit key and encrypt (or decrypt) using Output Feedback Mode (OFB).floor((n1 * 8 + 5) / 6)floor((n1 * 6) / 8))
2Initialize AES with a 192-bit key and encrypt (or decrypt) using OFB.floor((n1 * 8 + 5) / 6)floor((n1 * 6) / 8))
3Initialize AES with a 256-bit key and encrypt (or decrypt) using OFB.floor((n1 * 8 + 5) / 6)floor((n1 * 6) / 8))
4Use 128-bit key deterministic encryption (or deterministic decryption).ceil((17 + n1) / 3) * 4(floor(n1 / 4) * 3) - 17
5Use 192-bit key deterministic encryption (or deterministic decryption).ceil((25 + n1) / 3) * 4(floor(n1 / 4) * 3) - 25
6Use 256-bit key deterministic encryption (or deterministic decryption).ceil((33 + n1) / 3) * 4(floor(n1 / 4) * 3) - 33
7Use 128-bit key randomized encryption (or randomized decryption).ceil((17 + n1) / 3) * 4(floor(n1 / 4) * 3) - 17
8Use 192-bit key randomized encryption (or randomized decryption).ceil((25 + n1) / 3) * 4(floor(n1 / 4) * 3) - 25
9Use 256-bit key randomized encryption (or randomized decryption).ceil((33 + n1) / 3) * 4(floor(n1 / 4) * 3) - 33

Randomized Encryption

The algorithm for randomized (non-deterministic) encryption follows these steps:

  1. Accept (binary) input string plaintext with length plaintext_length bytes.
  2. Accept (binary) input key with length key_length bytes.
  3. Accept (integer) input aes_keybits (must be 128, 192, or 256).
  4. Reuse/fold key to aes_keybits. The actual size of the key required depends on the algorithm selected. The actual size of the initialization vector for modes 4 to 9 is 128 bits.
  • When the input key or ivec is shorter than expected, reuse the input until the required size is reached.
  • When the input key or ivec is longer than expected, fold the extra input with the required input via XOR.
  1. Generate a cryptographically random salt with aes_keybits (likely generated via Fortuna).
  2. ciphertext = ofb_aes(plaintext, key = (key XOR salt), iv = (salt >> (aes_keybits - 128))
  3. ciphertext_length = plaintext_length
  4. outputtext = version (1 byte) + salt (aes_keybits/8) + ciphertext (ciphertext_length bytes)
  5. outputtext_length = 1 + (aes_keybits/8) + ciphertext_length

Note:

  • This encryption algorithm is specifically designed to be secure against any kind of chosen plain-text attacks (including dictionary-based attacks for low-cardinality data sets).
  • This algorithm is also resistant to length-extension attacks; that is, an attacker with encrypt access cannot pad a chosen (binary) string to an already encrypted value.
  • Use an HMAC on the outputtext separately if you need to protect against random padding.
  • ciphertext_length mirrors plaintext_length, and therefore leaks information about the input. For example, ciphertext for 'm' will be indistinguishable (in length and randomness) from ciphertext for 'f'; however, ciphertext_length for 'male' will be different from ciphertext_length for 'female'. To guard against this leakage, pad all input to the same length.

Randomized Decryption

The algorithm for randomized (non-deterministic) decryption follows these steps:

  1. Accept (binary) input string outputtext with length outputtext_length bytes.
  2. Accept (binary) input key with length key_length bytes.
  3. Accept (integer) input aes_keybits (must 128, 192, or 256).
  4. Reuse/fold key to aes_keybits. The actual size of the key required depends on the algorithm selected. The actual size of the initialization vector for modes 4 to 9 is 128 bits.
  • When the input key or ivec is shorter than expected, reuse the input until the required size is reached.
  • When the input key or ivec is longer than expected, fold the extra input with the required input via XOR.
  1. version = outputtext[0:0]
  2. salt = outputtext[1:(aes_keybits/8)]
  3. ciphertext = outputtext[(aes_keybits/8):(outputtext_length-1)]
  4. plaintext = ofb_aes(ciphertext, key = (key XOR salt), iv = (salt >> (aes_keybits - 128))
  5. plaintext_length = outputtext_length - (1 + (aes_keybits/8))

Note:

  • This decryption algorithm is not susceptible to padding-oracle attacks.
  • OFB enables decryption in a stream-cipher mode instead of requiring PKCS-like padding.
  • An attacker who has decrypt access but not encrypt access may be able to encrypt chosen plain texts. This may be a problem if a user erroneously relies on an encryption system for message-authentication; use a proper HMAC if needed to prevent message forgery.

Deterministic Encryption

The algorithm is the same as in randomized encryption, except for the choice of salt in step 5:

salt = hmac_sha256(key, plaintext) >> (256 - aes_keybits);

Note:

  • Identical plaintext strings encrypt to identical outputtext strings when you use the same key; this method enables equality comparison.
  • The same plaintext value encrypted with two different keys generates two different outputtext values.

Deterministic Decryption

The algorithm for deterministic decryption works exactly the same as the algorithm for randomized decryption.

Note:

  • padding-oracle attacks are not possible.
  • Given that deterministic decryption uses an hmac_sha256 instead of sha256, it is possible to detect and prevent random length extensions. However, for compatibility with randomized encryption, Yellowbrick decryption instead returns an undefined result.

A Warning about "Symmetric" Algorithms

Encryption algorithms 1, 2, and 3 are considered "symmetric." Users need to be careful about choosing these algorithms if the default initialization vector (IV) is being used, if the IV is well-known, or if the IV may have been leaked. In these scenarios, roles who have ENCRYPT but not DECRYPT access may be able to decipher strings by reverse engineering the bit-stream that was used to protect those strings.

For example, assume that encryption key k0 and role r0 exist in your system, that r0 is neither a superuser nor the owner of k0, and that r0 has been granted ENCRYPT but not DECRYPT access on k0.

In turn, r0 can run a query where:

cipher-text = encrypt(plain-text, k0, {1 | 2 | 3}, iv)

Although r0 cannot run an equivalent DECRYPT query, the following property holds true for binary data:

plain-text = encrypt(encrypt(plain-text, k0, {1 | 2 | 3}, iv), k0, {1 | 2 | 3}, iv)

Therefore, if r0 happens to know the iv in use, r0 can recover the plain-text from the cipher-text by reverse-engineering the bit-stream used to protect the plain-text string.