Encrypt string with my key

Hello!
I want to encrypt some string with my own key, how I can do it? I prefer to do it with some encryption technology that I can use to decrypt this in my js frontend.

1 Like

Maybe

message = "i'm almost secure" <> :binary.copy(<<15>>, 15)
password = "your-password"
encrypted = :crypto.block_encrypt(:aes_ecb, :crypto.hash(:sha256, password), message)
1 Like

Nice, but how I can get encrypted as a string, because I need to send it to my frontend?

This approach has several weaknesses:

  • ECB mode can very easily lead to data exposure if multiple messages are encrypted with the same key, or if a single message spans multiple blocks; use CBC or GCM mode, and a unique random IV for each message (to be transmitted along with the ciphertext)
  • Using SHA256 as a key derivation function (KDF) provides very little protection against brute-force attacks to recover the password; use a proven KDF, or a secure random key

Instead of building an encryption scheme from primitives, consider using high-level API such as JWE or Plug.Crypto (https://hex.pm/packages/plug_crypto)

4 Likes

And what about if I want to run a bash command inside my app and use stdout, for example I have a command echo 'foo' | openssl aes-256-cbc -a -salt -k one and I got a stdout result what I need, but how I can run this command using System.cmd? I have troubles with that.

Either do it correct and use a Port and send messages to it, or do it one-shot: System.cmd("sh", ["echo 'foo' | openssl aes-256-cbc -a -salt -k one"]) or something…

This improves on the earlier proposal in that it uses CBC, but it still relies on a weak KDF. If you need to use a password (rather than a binary key) you are going to have to bring in a package that implements a strong KDF. You can do the rest with :crypto APIs, no need to shell out to OpenSSL.

Another package that could help is https://hex.pm/packages/pbcs, but its documentation is very minimal.

1 Like

I’m currently struggling to get some similar setup up and running and just don’t get, howto do things.
First and foremost, we don’t do this for server security, but to fulfill GDPR regulations, just to be sure.

We are authenticating PPPoE users by freeradius via CHAP. Basically this means, the customer sends a hash value created from password and a challenge. The radius server has to keep a cleartext password for this to work. For GDPR, we would rather persist the user password aes encrypted. So we now deliver the cleartext password by the SQL query: AES_DECRYPT(UNHEX(value), ‘ThisIsMySecretAesKey’)

So what I try to do is the opposite HEX(AES_ECNRYPT(‘Password’, ‘ThisIsMySecretAesKey’)).

Looking at the (scarce) examples, :aes_ecb boils down to a simple: :crypto.block_encrypt(:aes_ecb, “Key”, “Pass”), but this didn’t work out:

   :crypto.block_encrypt(
     :aes_ecb,
     Application.fetch_env!(:myapp, :aes_key),
     plaintext
   )

Leads to:

** (ArgumentError) argument error
:crypto.block_crypt_nif(:aes_ecb, "Testkey", "Teststring", true)

As this seems to work for several people, maybe someone has some insights, why not in my case.
The above example works, but I assume, padding the strings would not yield the same result with the sql decrypt query?

1 Like

An AES key must be 16 bytes long. Payload must be a in blocks of even 16 bytes but if using aes_ecb you should not encrypt more than 1 block (16 bytes). It is not secure to do so.

If using another crypto mode, such as aes_cbc you must pad it so that the payload is evenly divided by the block size but if you pad you must authenticate the crypto otherwise it is not secure. (See padding oracle)

And if you authenticate the crypto please make sure that you use a constant time compare when checking the authentication otherwise this can be utilized to crack the crypto.

Alternatively use a crypto with authentication such as AES-GCM.

EDIT:

I’ve had a look at the AES_ENCRYPT in MySQl (which I assume you are using?). They do pad the string but they don’t mention which padding algorithm, iv or what type of aes mode they are using but from some stackoverflow post it seems like they are using AES-ECB, padded with PKCS#5.

2 Likes

Ah thanks, I think, MySQL then does this implicitly, while using :crypto, I have to care for this myself.

The real problem is, that (normally), a radius server just awaits a cleartext password from database and in most cases it is realized this way (so, this is more snakeoil to calm our CEOs, security wise :wink: ). As far as I know, MySQL does not support ctr based modes, like gcm , so we really seem to be stuck with ecb here.

We could use PAP, with stronger encryption, but unfortunately, PPPoE users would then be forced to send cleartext password while logging in as UDP :smiley:

Personally, taking into account the possibility, that after an outage, around >20k Users want to log in again as fast as possible, I’d be more comfortable by cleartext password and avoid the encryption overhead for a weak encryption.

EDIT:
That was just my experience. I also di not find a lot of infos, how MySQL does this. ECB was the best, I could get from searching. How the padding is implicitly done is not documented too well.

Yes, you’d have to reverse engineer whatever MySQL is doing. Seems like later versions of MySQL do support different crypto modes (such as cbc).

I just tried and MySQL uses AES-ECB-128 by default. I can decrypt like this:

SELECT HEX(AES_ENCRYPT('testing', '1234567890123456'));
0930A6442C8B22D69A35A30EC032BB73

and then decrypt it in elixir

:crypto.block_decrypt(:aes_ecb, "1234567890123456", Base.decode16!("0930A6442C8B22D69A35A30EC032BB73"))
"testing\t\t\t\t\t\t\t\t\t"

But I don’t know what padding they use if the key is not 16 bytes. Somewhere it is claimed to be 0 padded but I couldn’t get it to work.

All in all, from a security point of view this feels very dodgy. From storing the passwords in the database (instead of a hash, even if encrypted) to the algorithms used.

1 Like

Normally, you can assume, that the Database is isolated efficiently. So the focus in ISP settings is set on protecting the communication between BRAS, Radius and customer modem. So they do a challenge handshake and the customer sends some calculated hash from this one time challenge and his dialin password.

The radius now takes this challenge and has to calculate with the same plaintext password. Of course, you can deploy a kind of “middleware” that can handle stronger encryption, but you must be prepared for a lot of queries, when several thousand FritzBoxes want do dialin between 4 and 5 a.m :smiley:

I found some interesting reference on stackoverflow. Though from 2013, this seems still valid:

MySQL’s implementation of AES gives headaches to a lot of people. It’a mainly because of how MySQL processes the encryption key. The encryption key gets broken into 16-byte blocks and MySQL will XOR the bytes from one block with the bytes in the preceding block. If the user provided key happens to be less than 16 bytes in length then the key is essentially padded with null bytes to get up to 16 bytes. That’s how the key is processed by MySQL’s aes_encrypt().

So it seems, one will have to implement two methods:

  • PKCS7 padding for the data to be encrypted though some say this might also be PKCS5
  • The key padding algorithm as stated in the text

But you brought up another thing for me to check, before trying to implement this. There is also an asymmetric encryption method with 1024 bit public/private rsa key available in MySQL 5.7, that is used in Percona Cluster.

If this works out (regarding server load and authentication speed), I think this would be a way better method, regarding application security. The elixir microservice configuring the radius would yield the private key, the radius in plaintext configuration would use only the public key in plaintext files.

This approach does not really offer any benefits over symmetrical crypto, and will have significant performance impact.

The fact that the key stored in the Radius configuration files is notionally the “public” half of the key pair does not change the fact that anyone who has access to the plaintext config file will be able to decrypt all passwords. The fact that the “private” half is kept more secure just means someone with only the public key can’t encrypt new passwords, which presumably isn’t what you are concerned about.

2 Likes

My main concern, in respect to GDPR is, that the database could be leaked due to someone here deploying an insecure script somewhere. In law terms, any encryption would do to avoid potentially high fines in this case. If I could implement something, that works and is not too easy to reverse engineer, I’d feel a lot more comfortable.

That is, why i thought about rsa. But I fear, you are completely right with the performance impact.

The radius itself is placed in a private management network with the BRAS and if this system is compromised, our problems are far worse than some DB leakage, I assume. So I would deem this a secondary or tertiary risk and would require a directed attack at our internal management network.

Would a customer have direct access to the radius for authorization, I would completely agree, that it makes no real difference, as the risk of exploting the service is clearly there.

I have been tinkering with a requirement to encrypt some data before storing in a database. Now I am extremely new to a lot of this and in all fairness my need is very small, but I found a lot of good references with the following that certainly helped: https://github.com/dwyl/phoenix-ecto-encryption-example.

Most likely it doesn’t fit your requirements, but just in case :smile:

3 Likes

Thanks for all the input :smile:

Though we decided against aes just now, I came up with the following solution to the mysql implementation of aes_ecb as a personal challenge, which works well in my test cases:

#
# Encrypt password with 128-bit aes and insert into database as hex value
#
@aes_blocksize 16

def encrypt(data, key) do
  :crypto.block_encrypt(
    :aes_ecb,
    mysql_key_pad(key),
    pkcs7_pad(data, @aes_block_size)
  )
  |> Base.encode16()
end

# PKCS7 Padding
defp pkcs7_pad(data, blocksize) do
  pad = blocksize - rem(byte_size(data), blocksize)
  data <> to_string(List.duplicate(pad, pad))
end

defp mysql_key_pad(key) when byte_size(key) < @aes_block_size do
  bits = 8 * (@aes_block_size - byte_size(key))
  key <> <<0 :: size(bits)>>
end

defp mysql_key_pad(key) when byte_size(key) == @aes_block_size do
  key |> :binary.bin_to_list()
end

defp mysql_key_pad (key) do
  bytelist = :binary.bin_to_list(key)
  bytes_to_pad = (@aes_block_size - rem(length(bytelist), @aes_block_size))

  bytelist ++ List.duplicate(<<0::8>>, bytes_to_pad)
  |> Enum.chunk_every(@aes_block_size)
  |> Enum.reduce(
      List.duplicate(<<0::8>>, @aes_block_size),
        fn chunk, acc ->
          :crypto.exor(acc, chunk)
        end
    )
end

You can also implement the functionality of :crypto.exor(acc, chunk) in Elixir with a recursive function like
do_xor_calculation(chunk1, chunk2, accumulator), as I did first, but with :crypto.exor/2 it is way more comfortable.

2 Likes

Did you try encrypted |> :base64.encode ?