AES256 CBC encryption returning empty string

Hi everyone.
I’m currently looking into AES256 CBC and how to encrypt/decrypt plaintext using Elixir. The Erlang crypto module provides the crypto_one_time/5 function which seems like the right function to use.
https://www.erlang.org/docs/28/apps/crypto/crypto#crypto_one_time/5
The code I used:

iv = :crypto.strong_rand_bytes(16)
key = :crypto.strong_rand_bytes(32)
data_to_encrypt = "the_plaintext"
:crypto.crypto_one_time(:aes_256_cbc, key, iv, data_to_encrypt, true)

The problem I’m facing is that the crypto_one_time/5 function returns an empty string.

crypto.info/1 returns:

:crypto.info
%{
  otp_crypto_version: ~c"5.5.1",
  compile_type: :normal,
  link_type: :dynamic,
  cryptolib_version_compiled: ~c"OpenSSL 3.3.2 3 Sep 2024",
  cryptolib_version_linked: ~c"OpenSSL 3.4.0 22 Oct 2024",
  fips_provider_available: false
}

aec_256_cbc gets listed when using :crypto.supports()

:crypto.supports[:ciphers]
[:chacha20, :aes_256_ofb, :aes_192_ofb, :aes_128_ofb, :sm4_ctr, :sm4_ofb,
 :sm4_cfb, :sm4_ecb, :sm4_cbc, :blowfish_ecb, :blowfish_ofb64, :blowfish_cfb64,
 :blowfish_cbc, :des_ede3_cfb, :des_ecb, :des_cfb, :des_cbc, :rc4, :rc2_cbc,
 :aes_128_cbc, :aes_192_cbc, :aes_256_cbc, :aes_128_cfb128, :aes_192_cfb128,
 :aes_256_cfb128, :aes_128_cfb8, :aes_192_cfb8, :aes_256_cfb8, :aes_128_ecb,
 :aes_192_ecb, :aes_256_ecb, :sm4_gcm, :sm4_ccm, :chacha20_poly1305,
 :aes_256_gcm, :aes_256_ccm, :aes_192_gcm, :aes_192_ccm, :aes_128_gcm,
 :aes_128_ccm, :aes_256_ctr, :aes_192_ctr, :aes_128_ctr, :des_ede3_cbc,
 :aes_cbc, :aes_ccm, :aes_cfb128, :aes_cfb8, :aes_ctr, :aes_ecb, ...]

Any help would be greatly appreciated.

I am not familiar with how this API works, but is the third parameter Plaintext a valid one?

Thank you for your quick response.
It’s the data which should get encrypted.
I’ll edit the question to make more clear.

1 Like

There’s something going on here with the length of the input - making the plaintext longer eventually gives a non-empty result, at exactly 16 bytes :thinking:

iex(19)> data_to_encrypt = "the_plaintext1"
"the_plaintext1"

iex(20)> :crypto.crypto_one_time(:aes_256_cbc, key, iv, data_to_encrypt, true)
""

iex(21)> data_to_encrypt = "the_plaintext12"
"the_plaintext12"

iex(22)> :crypto.crypto_one_time(:aes_256_cbc, key, iv, data_to_encrypt, true)
""

iex(23)> data_to_encrypt = "the_plaintext123"
"the_plaintext123"

iex(24)> :crypto.crypto_one_time(:aes_256_cbc, key, iv, data_to_encrypt, true)
<<112, 120, 31, 19, 167, 197, 128, 142, 183, 99, 178, 94, 193, 254, 84, 224>>

iex(25)> byte_size(data_to_encrypt)
16
1 Like

16 bytes (128 bits) is the block size of AES, so perhaps you are expected to pad the input yourself? (I don’t know much about cryptography)

1 Like

Further review turns up the padding option, which mentions this important fact:

This option handles padding in the last block. If not set, no padding is done and any bytes in the last unfilled block is silently discarded.

You can see this happening with a longer-than-16-byte string:

iex(28)> data_to_encrypt = "the_plaintext1234"
"the_plaintext1234"

iex(29)> byte_size(data_to_encrypt)
17

iex(30)> :crypto.crypto_one_time(:aes_256_cbc, key, iv, data_to_encrypt, true)
<<112, 120, 31, 19, 167, 197, 128, 142, 183, 99, 178, 94, 193, 254, 84, 224>>

iex(31)> byte_size(v(30))
16

The last byte is silently dropped because the block wasn’t full.

Explicitly passing a value for padding solves the original issue:

iex(26)> data_to_encrypt = "the_plaintext"
"the_plaintext"
iex(27)> :crypto.crypto_one_time(:aes_256_cbc, key, iv, data_to_encrypt, encrypt: true, padding: :pkcs_padding)
<<128, 42, 49, 230, 33, 26, 157, 34, 189, 47, 164, 21, 246, 50, 96, 230>>
5 Likes

Thank you very much guys :+1:

That is quite a gotcha if you use it to encrypt some data and don’t double-check the result! Always read the docs, I guess :slight_smile:

In general that is an API that should not be used if you aren’t aware what you are doing (so unless you are cryptography expert or you are trying to follow well known protocol description). Otherwise you will burn yourself.

1 Like

I see, if it’s something that’s standardized that makes a lot more sense.