Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to encrypt and decrypt with AES CBC 128 in Elixir

I have an app in Rails with following methods to encrypt and decrypt a text and communicate with java clients.

def encrypt(string, key)
    cipher = OpenSSL::Cipher::AES.new(128, :CBC)
    cipher.encrypt
    cipher.padding = 1
    cipher.key = hex_to_bin(Digest::SHA1.hexdigest(key)[0..32])
    cipher_text = cipher.update(string)
    cipher_text << cipher.final
    return bin_to_hex(cipher_text).upcase
end

def decrypt(encrypted, key)
    encrypted = hex_to_bin(encrypted.downcase)
    cipher = OpenSSL::Cipher::AES.new(128, :CBC)
    cipher.decrypt
    cipher.padding = 1
    cipher.key = hex_to_bin(Digest::SHA1.hexdigest(key)[0..32])
    d = cipher.update(encrypted)
    d << cipher.final
rescue Exception => exc
end

def hex_to_bin(str)
    [str].pack "H*"
end

def bin_to_hex(str)
    str.unpack('C*').map{ |b| "%02X" % b }.join('')
end

I need do the same in Elixir for phoenix framework. Since I'm new to Elixir I couldn't find a way for that. I Found that Elixir uses Erlang's :crypto Module for that. In documentations there was no method for AES CBC encryption.

like image 215
rezam Avatar asked Jun 04 '16 10:06

rezam


People also ask

What is AES 128 CBC encryption?

128-bit AES encryption refers to the process of concealing plaintext data using an AES key length of 128 bits. 128-bit AES encryption uses 10 transformation rounds to convert plaintext into ciphertext and is approved by the National Security Agency (NSA) to protect secret but not top-secret government information.

How encrypt AES-CBC?

To encrypt using AES-CBC: Instantiate the CBC block cipher class with the AES implementation class. Initialize it with the key and Initialization Vector (IV) for encryption. Process each block of the padded plaintext being encrypted.

Can AES work in CBC mode?

We can use some algorithms for padding block when the plaintext is not enough a block, like PKCS5 or PKCS7, it also can defend against PA attack, if we use ECB or CBC mode. Or we can use the mode of AES which support a stream of plaintext, like CFB, OFB, CTR mode.


Video Answer


4 Answers

The block_encrypt/4 function from the Erlang crypto module is the function you want. Unlike the Ruby OpenSSL bindings, the Erlang code doesn’t handle padding, so you will need to do that yourself before encrypting (and remove it after decrypting).

NOTE: As of Erlang v23, the block_encrypt/4 and block_decrypt/4 functions (and their /3 sisters) are deprecated and will be removed from the Erlang crypto module in Erlang v24. The new API functions that have replaced them are crypto_one_time/4 and crypto_one_time/5 and these functions should be used for all new Erlang/Elixir programs. The new API functions support IVs and other improvements over the old functions.

However, unless this is just a toy app for learning purposes, I would recommend not doing this kind of crypto stuff yourself if you can avoid it. Rather you should find a higher level API that takes care of the various details where you can go wrong. I have listed some potential issues with your code as it is below, as well as a suggestion of what to do instead.


The padding that OpenSSL uses (sometimes called PKCS7 padding) is fairly simple. First you need to work out how many bytes you need to add to your data to make the length into a multiple of the block size (16 for AES). Then you simply add that many bytes of that value to the end. For example if your data was 14 bytes long then you would need to add two bytes, and each of those bytes would have the value 0x02 (2 bytes each with value 2). Note that you always add padding, so if your data is already a multiple of 16 byte then you add another 16 bytes (all with value 0x10).

To strip the padding you simply look at the value of the last byte and remove that many bytes from the end (you should probably check that the padding is correct too, i.e. all the bytes have the expected value).

Here is a simple implementation in Elixir (there may be a better / clearer / more idiomatic way to do this):

# These will need to be in a module of course
def pad(data, block_size) do
  to_add = block_size - rem(byte_size(data), block_size)
  data <> to_string(:string.chars(to_add, to_add))
end

def unpad(data) do
  to_remove = :binary.last(data)
  :binary.part(data, 0, byte_size(data) - to_remove)
end

You can now use these along with the :crypto.block_encrypt function to get AES CBC encryption like your Ruby code:

# BAD, don't do this!
# This is just to reproduce your code, where you are not using 
# an initialisation vector.
@zero_iv to_string(:string.chars(0, 16))
@aes_block_size 16

def encrypt(data, key) do
  :crypto.block_encrypt(:aes_cbc128, key, @zero_iv, pad(data, @aes_block_size))
end

def decrypt(data, key) do
  padded = :crypto.block_decrypt(:aes_cbc128, key, @zero_iv, data)
  unpad(padded)
end

Some issues

Here are some potential problems with your code. This is not an exhaustive list, just some things I noticed (I am not an expert in crypto).

  1. No authentication. Unless you’re checking the authentication in another method before the code you show, then you don’t have any authentication of the messages. This is very bad. You are exposing yourself to potential padding oracle attacks (where an attacker could decrypt the messages) and things like bit-flipping attacks, where an attacker can send specially modified messages that your code might not recognise as bad, and cause some undesired action to take place.

You should be using something like HMAC. But even if you decide to use a HMAC, there are still several questions you need to work out. Where does the HMAC key come from? Can we use the same key for encryption and authentication? Do we calculate the HMAC over the plaintext or the ciphertext? Should it cover the IV as well?

  1. No Initialisation Vector. CBC mode should make use of an initialisation vector, or IV. In the Ruby OpenSSL bindings if you don’t specify one it just uses zero bytes (which is why we needed to create the @zero_iv in the code above. Each message should have its own IV. This can just be a random series of bytes, and doesn’t need to be kept secret (it can just be sent prepended to the ciphertext).

  2. Weak key generation. I could be wrong with this one, but since you are calculating the SHA1 hash of the provided key argument to use as the encryption/decryption key it suggest that this argument is actually a password. If this is the case then you should be using a better key derivation function (and if not then what’s the purpose of the hashing?). If you are using an easy for a human to remember password (or a single hash of one) you could be vulnerable to brute force attacks where an attacker tries lots of dictionary words as the key.

    You should be using a proper key derivation function, such as PBKDF2. Even then you will still have complications since you might need two keys (encryption and authentication), so you need to work out how to generate them both.


What to use instead

If possible you should look for a higher level library that takes into account these factors and provides a simpler API. I would recommend Libsodium, which has bindings for many languages including Ruby, Elixir, Erlang, and Java/Android.

like image 128
matt Avatar answered Oct 02 '22 06:10

matt


I'd recommend not using CBC mode directly but use GCM mode as this will provide authentication as well.

In Elixir (for a 256bit AES key)

# Gen once (see also https://hexdocs.pm/plug/Plug.Crypto.KeyGenerator.html#content)
k = :crypto.strong_rand_bytes(32)

# Gen every time you encrypt a message
iv = :crypto.strong_rand_bytes(32)
{ct, tag} = :crypto.block_encrypt(:aes_gcm, k, iv, {"AES128GCM", msg})
payload = Base.encode16(iv <> tag <> ct)

To decrypt:

<<iv::binary-32, tag::binary-16, ct::binary>> = Base.decode16!(payload)
:crypto.block_decrypt(:aes_gcm, k, iv, {"AES128GCM", ct, tag})
like image 35
Dan Draper Avatar answered Oct 02 '22 06:10

Dan Draper


Here is what I use for ECB, CBC should be the same with the added need to pass the previous block cipher in the accumulator. Don't forget that you also need to write a function to pad the term to 16 byte blocks(the ruby seems to do that automatically).

Key = "12345678"
AES_ECB_Encrypt = 
    fun Crypt(<<Block:16/binary, Rest/binary>>, Acc) ->
            NewAcc = erlang:iolist_to_binary( [Acc, crypto:block_encrypt(aes_ecb, Key, Block)] ),
            Crypt(Rest, NewAcc);
        Crypt(_, Acc) ->
            Acc
end,
AES_ECB_Encrypt(<<"hello00000000000">>, <<>>)
like image 40
Vans S Avatar answered Oct 02 '22 06:10

Vans S


JOSE.JWA component from JOSE package has block_decrypt/4 and block_encrypt/4 functions.

iex> JOSE.JWA.crypto_supports()
[ciphers: [aes_cbc: 128, aes_cbc: 192, aes_cbc: 256, aes_ecb: 128, aes_ecb: 192,
  aes_ecb: 256, aes_gcm: 128, aes_gcm: 192, aes_gcm: 256,
  chacha20_poly1305: 256],
 hashs: [:md5, :poly1305, :sha, :sha256, :sha384, :sha512, :shake256],
 public_keys: [:ec_gf2m, :ecdh, :ecdsa, :ed25519, :ed25519ph, :ed448, :ed448ph,
  :rsa, :x25519, :x448], rsa_crypt: [:rsa1_5, :rsa_oaep, :rsa_oaep_256],
 rsa_sign: [:rsa_pkcs1_padding, :rsa_pkcs1_pss_padding]]
like image 22
vstavskyi Avatar answered Oct 02 '22 05:10

vstavskyi