I'm trying to use AES encryption in order to encrypt files that are being saved on MongoDB.
This uses the GridFS gem.
The AES key is being encrypted itself by RSA and then stored alongside the document within Mongo.
I was previously using Mongoid::EncryptedFields.cipher.encrypt which worked fine, however, the client wanted RSA keys to be used.
My xsl_action:
tempFile = params[:stylesheet].tempfile
file = File.open(tempFile)
grid_fs = Mongoid::GridFS
#Encryption
pub_file = CaseCenter::Config::Reader.get('pub_key');
public_key = OpenSSL::PKey::RSA.new(File.read(pub_file))
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
key = cipher.random_key
encData = cipher.update(File.read(file))
#End Encryption
File.open(file, 'wb') do |f|
f.write(encData)
end
encrypted_aes = Base64.encode64(public_key.public_encrypt(key))
stylesheet.aes_key = encrypted_aes
grid_file = grid_fs.put(file.path)
stylesheet.stylesheet_id = grid_file.id
To decrypt the file, I use this:
grid_fs = Mongoid::GridFs
f = grid_fs.get(stylesheet_id)
#Decryption
key = CaseCenter::Config::Reader.get('priv_key')
passphrase = CaseCenter::Config::Reader.get('key_pass')
if key.include? "-----BEGIN RSA PRIVATE KEY-----"
private_key = OpenSSL::PKey::RSA.new(key,passphrase)
else
private_key = OpenSSL::PKey::RSA.new(File.read(key),passphrase)
end
decKey = private_key.private_decrypt(Base64.decode64(doc[:aes_key]))
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = decKey
decData = cipher.update(f.data)
The AES key and file are properly encrypted, however, on decryption, the file loses the last 30 characters.
Am I doing something wrong during encryption?
EDIT 1:
Thought it could have been that I hadn't included cipher.final.
I have now included
encData << cipher.final
I am still having the same issue, not all of the file is being returned, however, there are less missing characters now.
I now assume this issue is due to the initial encryption stage.
For anyone else who might experience the same issue, I had forgotten to include cipher.final on both encryption and decryption.
#Encryption
encData << cipher.final
#Decryption
decData << cipher.final
This solved my issue.
Related
I'm sending a value to the client that I want encrypted (and decrypted if/when received back).
It is not super sensitive data, I just don't want to send it in plain naked text.
I know of ActiveSupport::MessageEncryptor but it seemingly requires too many steps for my basic need. Perhaps I'm overthinking it and there's no heavy computation involved under the hood but I'm intuitively looking for something more like how setting/reading a signed cookie works.
It would be a great advantage if I wouldn't need to include anything more than what comes out of the box with Rails 4.
Why not put it in the session? Then it will be part of the encrypted cookie / in your session store.
You can set the value with session[:message] = "my message" and get it with session[:message].
Using Cipher which comes included in ruby # OpenSSL seems simple.
Try this on irb:
require "openssl"
data = "Very, very confidential data"
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
encrypted = cipher.update(data) + cipher.final
decipher = OpenSSL::Cipher::AES.new(128, :CBC)
decipher.decrypt
decipher.key = key
decipher.iv = iv
plain = decipher.update(encrypted) + decipher.final
puts data == plain
The following function works perfect in PHP. How can it be translated in Ruby on Rails.
Please note that both privateKey and iv are 32 characters long.
mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $privateKey, base64_decode($enc), MCRYPT_MODE_CBC, $iv)
I tried to use the following in Ruby but got a bad decrypt error.
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = privateKey
cipher.iv = iv
decrypted = '' << cipher.update(encrypted) << cipher.final
Here some code which works for me :
def decrypt_data(data, pwd, iv)
encrypted_data = Base64.decode64(data)
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
aes.decrypt
aes.key = Digest::MD5.hexdigest(pwd)
aes.iv = iv
result = aes.update(encrypted_data) + aes.final
end
In my example the password is encrypted with MD5.
I hope this help
You are base64 decoding it in the php example. Are you doing that in the ruby one as well?
require "base64"
Base64.decode64(encrypted)
Other than that, the code looks right to me.
I saw a while ago the possibility to decrypt and encrypt strings in rails without including any library, but I can't find the blog post.
I want to be able to encrypt and decrypt strings without including anything.
Using the same key has for the everything else in rails, signed cookies for example.
Any ideas?
You mean this one?: ActiveSupport::MessageEncryptor. Here is the way to reuse Rails 5+ on Ruby 2.4+ application's secret:
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
encrypted_data = crypt.encrypt_and_sign('my confidental data')
And encrypted data can be decrypted with:
decrypted_back = crypt.decrypt_and_verify(encrypted_data)
The above example uses first 32 characters of Rails app secret as an encryption and signing key, because the default MessageEncryptor cipher aes-256-gcm requires exactly 256 bit key. By convention, during the app creation, Rails generates a secret as a string of 128 hex digits.
Important! Ruby 2.4 upgrade note
Before Ruby 2.4 and Rails 5 there was no key size restriction and it was popular to just past full secret into the encryptor initializer:
# pre-2.4
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base)
Internally the encryption algorithm (AES256GCM provided by OpenSSL) was using only 32 characters from the key, however the signing algorithm (SHA1) was consuming all 128 characters.
Therefore, while upgrading an app from pre-2.4 Ruby, and where the app previously encrypted the data with an unrestricted key size, the MessageEncryptor must get a full secret in the second parameter to avoid ActiveSupport::MessageVerifier::InvalidSignature on the legacy data decryption:
# post-2.4 upgrade
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31], Rails.application.secrets.secret_key_base)
Rails 5 requires that the key be 32 bytes.
Edit to Rails 4 answer that works for Rails 5:
key = SecureRandom.random_bytes(32)
crypt = ActiveSupport::MessageEncryptor.new(key)
encrypted_data = crypt.encrypt_and_sign('my confidental data')
Decrypt:
decrypted_back = crypt.decrypt_and_verify(encrypted_data)
Rails 5 update:
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
encrypted_data = crypt.encrypt_and_sign('my confidental data')
Rails 5.x Needs a key of exactly 32 bytes.
To verify a previously signed message with a longer key:
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31], Rails.application.secrets.secret_key_base)
encrypted_data = crypt.encrypt_and_sign('my confidental data')
as described in the documentation and the discussion on this change
This is a C# code:
byte[] pb = System.Text.Encoding.UTF8.GetBytes(policy.ToString());
// Encode those UTF-8 bytes using Base64
string policyB = Convert.ToBase64String(pb);
// Sign the policy with your Secret Key using HMAC SHA-1.
System.Security.Cryptography.HMACSHA1 hmac = new System.Security.Cryptography.HMACSHA1();
hmac.Key = System.Text.Encoding.UTF8.GetBytes(secretKey);
byte[] signb = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(policyB));
string signature = Convert.ToBase64String(signb);
How to do the same in Ruby on rails? More specifically I need to know functions to get bytes from string and base64 encode them and calculate hmac hash.
Not sure if it exactly the same, but it works for me:
#policy = ActiveSupport::Base64.encode64s(#policy)
# Sign policy with secret key
digest = OpenSSL::Digest::Digest.new('sha1')
#signature = ActiveSupport::Base64.encode64s(OpenSSL::HMAC.digest(digest, secretKey, #policy))
I'll try again.
There are a couple of HMAC libraries for ruby/rails that might make this much simpler:
http://auth-hmac.rubyforge.org/
My RoR server receives a string, that was encrypted in C++ application using des3 with base64 encoding
The cipher object is created so:
cipher = OpenSSL::Cipher::Cipher::new("des3")
cipher.key = key_str
cipher.iv = iv_str
key_str and iv_str: are string representations of key and initialization vector for encryption algorithm. They are the same for RoR and C++ application.
The code on the RoR side is following:
result = ""
result << cipher.update( Base64.decode64(message) )
result << cipher.final
After executing the last line of code, i get an exception
OpenSSL::CipherError (bad decrypt)
What is wrong here ? Any ideas ?
The documentation for OpenSSL::Cipher states:
Make sure to call .encrypt or .decrypt before using any of the following
methods:
[key=, iv=, random_key, random_iv, pkcs5_keyivgen]
In your specific case, omitting the call to cipher.decrypt causes a bad decrypt error, as you've seen.
The following example corrects that problem and exhibits the expected behavior:
require 'openssl'
require 'Base64'
# For testing purposes only!
message = 'MyTestString'
key = 'PasswordPasswordPassword'
iv = '12345678'
# Encrypt plaintext using Triple DES
cipher = OpenSSL::Cipher::Cipher.new("des3")
cipher.encrypt # Call this before setting key or iv
cipher.key = key
cipher.iv = iv
ciphertext = cipher.update(message)
ciphertext << cipher.final
puts "Encrypted \"#{message}\" with \"#{key}\" to:\n\"#{ciphertext}\"\n"
# Base64-encode the ciphertext
encodedCipherText = Base64.encode64(ciphertext)
# Base64-decode the ciphertext and decrypt it
cipher.decrypt
plaintext = cipher.update(Base64.decode64(encodedCipherText))
plaintext << cipher.final
# Print decrypted plaintext; should match original message
puts "Decrypted \"#{ciphertext}\" with \"#{key}\" to:\n\"#{plaintext}\"\n\n"
gem install encryptor
It wraps the standard Ruby OpenSSL library and allows you to use any of its algorithms.
require 'encryptor'
Base64.decode64(message).decrypt(:algorithm => 'des', :key => key, :iv => iv)