The Crystal Programming Language Forum

How to encrypt/decrypt data?

Hi,

I am having trouble figuring out how to use OpenSSL::Cipher to decrypt data.
I keep getting this error:

Unhandled exception: EVP_CipherFinal_ex: Unknown or no error (OpenSSL::Cipher::Error)
  from ../../../../../snap/crystal/397/share/crystal/src/openssl/cipher.cr:70:7 in 'final'
  from src/crystal_encryption.cr:39:12 in 'decrypt'
  from src/crystal_encryption.cr:57:18 in '__crystal_main'
  from ../../../../../snap/crystal/397/share/crystal/src/crystal/main.cr:105:5 in 'main_user_code'
  from ../../../../../snap/crystal/397/share/crystal/src/crystal/main.cr:91:7 in 'main'
  from ../../../../../snap/crystal/397/share/crystal/src/crystal/main.cr:114:3 in 'main'
  from __libc_start_main
  from _start
  from ???

I would really appreciate it if someone could spare some time to look at the code and let me know what I am doing wrong.

Similar example from here is working fine with Bytes size 16 and “aes-128-gcm” encryption.

But in your example, it is failing here

Got it working by using single instance of cipher and passing it as parameter.

password = "password"
salt = Random::Secure.random_bytes(32)
data = "Secret data that needs to be encrypted"

key = key_derivation password, salt


cipher = OpenSSL::Cipher.new("aes-256-gcm")
encrypted_data = encrypt cipher, data, key
puts "Encrypted data: #{encrypted_data.to_s}"
puts "--------------------"
decrypted_data = decrypt cipher, encrypted_data, key
puts "Decrypted data: #{decrypted_data.to_s}"

I recently had to do the same. Here’s a shard I made to help me out https://github.com/jwoertink/psst/blob/master/crystal/src/psst.cr Feel free to pull some ideas from that.

Also note that the Crystal OpenSSL::Cipher may be missing some methods https://github.com/crystal-lang/crystal/issues/9681 just in case you run in to that.

Thanks,

@aravindavk,
A single instance of Cipher works because some state is being kept between encryption/decryption. Notably, it breaks if you do the encryption/decryption independently.

To demonstrate this I have separated the encryption/decryption into separate files that writes/reads the encrypted data to a file.

$ crystal src/encrypt.cr
$ cat encrypted_data
<gibberish>
$ crystal src/decrypt.cr
<same_error>

@jwoertink,
I noticed that you are using “aes-256-cbc” in your code, and that does indeed work whereas the “aes-256-gcm” that I was using does not. And yeah, looks like those missing methods are required for gcm.

The main difference between gcm and cbc as far as I understand is that gcm does data integrity checks and cbc does not.

Switching my example to use cbc the effect of data corruption can be seen like this:

encrypted_data[33] = 1 # data corruption
decrypted_data = decrypt encrypted_data, key
puts "Decrypted data: #{decrypted_data.to_s}"

Could you explain why you picked the numbers that you did for the salt/iv (8/16 bytes)?
I thought that the iv needed to match the key length.

1 Like

A single instance of Cipher works because some state is being kept between encryption/decryption. Notably, it breaks if you do the encryption/decryption independently.

Totally makes sense. They should not depend on each other.

I was trying to port this lib https://github.com/mdp/gibberish/blob/master/lib/gibberish/aes.rb#L137 which uses the gcm, and those missing methods. It also uses 12 bytes for the iv, but when I used 12 with cbc, Crystal throws an error and said it had to be 16.

I don’t know anything about cryptography, or encryption. So I can’t say that my was is a good way of doing, but it works for what I needed (for now). It seems like the Crystal Crypto stuff needs a lot of love. I’m finding a few instances where there’s some missing areas like the Crystal version of ecdsa, elliptic and rsa. So if you find a better way to handle this, then I’m all ears!

Yeah, the crypto stuff is a bit rough compared to the rest of Crystal.

I do wish there were a simple to use API for encryption built in, something like this.

text = "secret stuff..."

key, salt = Crypto::PBKDF2.key_derivation "password" #(optional) provide salt : Bytes

encrypted_bytes = text.bytes.encrypt key #(optional), algorithm: Crypto::AES256CBC
# encrypted_text, iv = ...  (maybe return the iv, but I think I  would prefer it to just be baked in to the result)

decrypted_bytes = encrypted_bytes.decrypt key #(optional), provide the algorithm
decrypted_text = decrypted_bytes.to_s

I don’t know, just would be nice if crypto was easy to use and hard to get wrong by default instead of the other way around. Also good examples in the documentation would be great.

Guess I will use cbc for now so I can get on with finishing this application.