how to encrypt data with AES 256 ECB PKCS5Padding in ruby - ruby-on-rails

I want encrypt data with AES 256bit ECB mode using PKCS5padding
My ruby method is as follows, how to use PKCS5Padding here
def encrypt(raw_data,key)
cipher = OpenSSL::Cipher::AES.new(256, :ECB)
cipher.encrypt
cipher.key = key
encrypted_data = cipher.update(raw_data) + cipher.final
end
here key is OpenSSL::PKey::RSA type, throwing no implicit conversion of OpenSSL::PKey::RSA into String exception

I think your key is in the wrong format. You're trying to pass an RSA key, when the key should just be a hash string ... something like:
key = SecureRandom.hex(32)
=> "b67f7a5bf031aaa730473e5a9612a94b157c43aed5f52a2e70c9573f2d5a4ecd"

You should use
key = cipher.random_key
instead of RSA key
I have used it in following way for my purpose
Generate cypher random keys
Do AES encryption of data with these keys
Before supply the keys encrypt it with RSA public key
At receiver end
Decrypt the cypher keys with RSA private key
Decrypt the data with resultant cypher keys
Note: We can not encrypt large data with RSA private/public key based technique
Super secured Example
# At sender side
public_key_file = 'public.pem'
message = 'Hey vishh you are awesome!!'
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
cipher.encrypt
aes_key = cipher.random_key
encrypted_data = cipher.update(message) + cipher.final
# encrypted_data is ready to travel
rsa = OpenSSL::PKey::RSA.new(File.read(public_key_file))
rsa_cypher_key = rsa.public_encrypt(aes_key)
# rsa_cypher_key is ready to travel
# sending these data in encoded format is good idea
encrypted_data = Base64.encode64(encrypted_data)
rsa_cypher_key = Base64.encode64(rsa_cypher_key)
====> encrypted_data + rsa_cypher_key =====> Travelling
encrypted_data = Base64.decode64(encrypted_data)
rsa_cypher_key = Base64.decode64(rsa_cypher_key) # decode the data
# At recevier side
private_key_file = 'private.pem'
# Decrypt the cypher key with private key
rsp = OpenSSL::PKey::RSA.new(File.read('./config/private.pem'))
aes_key = private_key.private_decrypt(rsa_cypher_key)
decipher = OpenSSL::Cipher::AES.new(128, :CBC)
decipher.decrypt
decipher.key = aes_key
message = decipher.update(encrypted_data) + decipher.final
p message
'Hey vishh you are awesome!!'

Related

How to verify a webhook with Ruby?

I'm using Ruby 2.5.5 and Rails 5.2. I'm trying to verify a Paddle webhook, but can't get the verification to work. I tried various ways, here is one of them. I'll go through it on my console:
Here is the data:
data = {
"alert_id"=>"1",
"alert_name"=>"alert_created",
"cancel_url"=>"https://...",
"checkout_id"=>"1",
"user_id"=>"1",
"p_signature"=>"fwWXqR9C..."
}
public_key = "-----BEGIN PUBLIC KEY-----
FAKEdfsdfsdfsdfsdfsdfsdf23423423423423KCAgEAnyBxU0cUW9NGQU1Ur0MD
/M+VxdMsv7PMXsZFAKEKEYlskdfjlskdjflsjlskdj8i7+D9xZT57VhIfv8hInGy
IQFmJRrWxgmHjSW4cKbjg6Qg0NgOEuPdEIixsQZ/AfIar4KNObDul0EXL92B6ru/
...
-----END PUBLIC KEY-----"
Then I do the following:
signature = Base64.decode64(data['p_signature'])
data.delete('p_signature')
data.each {|key, value|data[key] = String(value)}
data_sorted = data.sort_by{|key, value| key}
data_serialized = data_sorted.to_json
digest = OpenSSL::Digest::SHA1.new
pub_key = OpenSSL::PKey::RSA.new(public_key)
verified = pub_key.verify(digest, signature, data_serialized)
In the end verified is always false. What am I doing wrong? I asked a related question here, but couldn't get it yet to work.
Example in paddle documentation that you've linked to suggests serializing data to PHP format before verification using php-serialize gem, that differs from regular json:
# ... data = ...
data_sorted.to_json
=> "[[\"alert_id\",\"1\"],[\"alert_name\",\"alert_created\"],[\"cancel_url\",\"https://...\"],[\"checkout_id\",\"1\"],[\"user_id\",\"1\"]]"
PHP.serialize(data_sorted, true)
=> "a:5:{s:8:\"alert_id\";s:1:\"1\";s:10:\"alert_name\";s:13:\"alert_created\";s:10:\"cancel_url\";s:11:\"https://...\";s:11:\"checkout_id\";s:1:\"1\";s:7:\"user_id\";s:1:\"1\";}"
as you can see - it gives different result, so obviously signature will not match.
Actually, you can take their whole ruby example:
require 'base64'
require 'php_serialize' # https://github.com/jqr/php-serialize
require 'openssl'
public_key = '-----BEGIN PUBLIC KEY-----
MIICIjANBgkqh...'
# 'data' represents all of the POST fields sent with the request.
signature = Base64.decode64(data['p_signature'])
data.delete('p_signature')
data.each {|key, value|data[key] = String(value)}
data_sorted = data.sort_by{|key, value| key}
data_serialized = PHP.serialize(data_sorted, true)
digest = OpenSSL::Digest::SHA1.new
pub_key = OpenSSL::PKey::RSA.new(public_key).public_key
verified = pub_key.verify(digest, signature, data_serialized)
I can't explain it completely but I think the false is related to your signature or data_serialized parameter, I use the same digest. Another difference is that sign is called on the key in the example below:
verify(digest, signature, data)
from: https://ruby-doc.org/stdlib-2.6.3/libdoc/openssl/rdoc/OpenSSL/PKey/PKey.html
To verify the String signature, digest, an instance of OpenSSL::Digest, must be provided to re-compute the message digest of the original data, also a String. The return value is true if the signature is valid, false otherwise. A PKeyError is raised should errors occur. Any previous state of the Digest instance is irrelevant to the validation outcome, the digest instance is reset to its initial state during the operation.
Example:
data = 'Sign me!'
digest = OpenSSL::Digest::SHA256.new
pkey = OpenSSL::PKey::RSA.new(2048)
signature = pkey.sign(digest, data)
pub_key = pkey.public_key
puts pub_key.verify(digest, signature, data) # => true`

Getting a bad decrypt error in ruby

I am using this snippet to extend the String class in Rails:
require 'openssl'
class String
def encrypt(key)
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
key = cipher.random_key
cipher.key = key
s = cipher.update(self) + cipher.final
s.unpack('H*')[0].upcase
end
def decrypt(key)
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
key = cipher.random_key
cipher.key = key
s = [self].pack("H*").unpack("C*").pack("c*")
cipher.update(s) + cipher.final
end
end
However when de-crypting the string I get the "Bad decrypt error":
puts plain = 'confidential' # confidential
puts key = 'secret' # secret
puts cipher = plain.encrypt(key) # 5C6D4C5FAFFCF09F271E01C5A132BE89
puts cipher.decrypt(key) # BAD DECRYPT
I tried adding padding like this to the decrypt action (Similar SO question here):
cipher.padding = 0
The error goes away but I am getting gibberish instead.
Even if you are passing key (secret) to the encrypt & decrypt functions, you are redefining key again with the below mentioned code.
key = cipher.random_key
You should be using same key for both encrypt & decrypt.
Try the below code snippet:
require 'openssl'
class String
def encrypt(key)
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
cipher.key = (Digest::SHA1.hexdigest key)[0..23]
s = cipher.update(self) + cipher.final
s.unpack('H*')[0].upcase
end
def decrypt(key)
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
cipher.key = (Digest::SHA1.hexdigest key)[0..23]
s = [self].pack("H*").unpack("C*").pack("c*")
cipher.update(s) + cipher.final
end
end
puts plain = 'confidential' # confidential
puts key = 'secret' # secret
puts cipher = plain.encrypt(key) # 5C6D4C5FAFFCF09F271E01C5A132BE89
puts cipher.decrypt(key) # confidential

How to decrypt a Rails 5 session cookie manually?

I have access to
config.action_dispatch.encrypted_cookie_salt
config.action_dispatch.encrypted_signed_cookie_salt
secrets.secret_key_base
the full cookie string (including --)
I see ways to do this in Rails 4 (Rails 4: How to decrypt rails 4 session cookie (Given the session key and secret)), but these don't seem to work in Rails 5.
I have had the same problem the other day and figured out that the generated secret was 64 bytes long (on my mac), but Rails ensures that the key is 32 bytes long (source).
This has worked for me:
require 'cgi'
require 'json'
require 'active_support'
def verify_and_decrypt_session_cookie(cookie, secret_key_base)
cookie = CGI::unescape(cookie)
salt = 'encrypted cookie'
signed_salt = 'signed encrypted cookie'
key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
secret = key_generator.generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len]
sign_secret = key_generator.generate_key(signed_salt)
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
encryptor.decrypt_and_verify(cookie)
end
Or without ActiveSupport:
require 'openssl'
require 'base64'
require 'cgi'
require 'json'
def verify_and_decrypt_session_cookie(cookie, secret_key_base)
cookie = CGI.unescape(cookie)
#################
# generate keys #
#################
encrypted_cookie_salt = 'encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_cookie_salt
encrypted_signed_cookie_salt = 'signed encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_signed_cookie_salt
iterations = 1000
key_size = 64
secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_cookie_salt, iterations, key_size)[0, OpenSSL::Cipher.new('aes-256-cbc').key_len]
sign_secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_signed_cookie_salt, iterations, key_size)
##########
# Verify #
##########
data, digest = cookie.split('--')
raise 'invalid message' unless digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, sign_secret, data)
# you better use secure compare instead of `==` to prevent time based attact,
# ref: ActiveSupport::SecurityUtils.secure_compare
###########
# Decrypt #
###########
encrypted_message = Base64.strict_decode64(data)
encrypted_data, iv = encrypted_message.split('--').map{|v| Base64.strict_decode64(v) }
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = secret
cipher.iv = iv
decrypted_data = cipher.update(encrypted_data)
decrypted_data << cipher.final
JSON.load(decrypted_data)
end
Feel free to comment on the gist: https://gist.github.com/mbyczkowski/34fb691b4d7a100c32148705f244d028
Here's a Rails 5.2 variant of #matb's answer, which handles the revised configuration, encryption and serialization:
require 'cgi'
require 'active_support'
def verify_and_decrypt_session_cookie(cookie, secret_key_base = Rails.application.secret_key_base)
cookie = CGI::unescape(cookie)
salt = 'authenticated encrypted cookie'
encrypted_cookie_cipher = 'aes-256-gcm'
serializer = ActiveSupport::MessageEncryptor::NullSerializer
key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
secret = key_generator.generate_key(salt, key_len)
encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: serializer)
encryptor.decrypt_and_verify(cookie)
end
Also up at https://gist.github.com/inopinatus/e523f36b468f94cf6d34410b73fef15e.

Using HMAC SHA256 in Ruby

I'm trying to apply HMAC-SHA256 for generate a key for an Rest API.
I'm doing something like this:
def generateTransactionHash(stringToHash)
key = '123'
data = 'stringToHash'
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.digest(digest, key, data)
puts hmac
end
The output of this is always this: (if I put '12345' as parameter or 'HUSYED815X', I do get the same)
ۯw/{o���p�T����:��a�h��E|q
The API is not working because of this... Can some one help me with that?
According to the documentation OpenSSL::HMAC.digest
Returns the authentication code an instance represents as a binary string.
If you have a problem using that maybe you need a hex encoded form provided by OpenSSL::HMAC.hexdigest
Example
key = 'key'
data = 'The quick brown fox jumps over the lazy dog'
digest = OpenSSL::Digest.new('sha256')
OpenSSL::HMAC.digest(digest, key, data)
#=> "\xF7\xBC\x83\xF40S\x84$\xB12\x98\xE6\xAAo\xB1C\xEFMY\xA1IF\x17Y\x97G\x9D\xBC-\x1A<\xD8"
OpenSSL::HMAC.hexdigest(digest, key, data)
#=> "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"
Try This:
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, data)
def make_payment(user)
#key= SecureRandom.hex(10)
#puts #key
#secret_key = #key
puts " this is the public key #{#secret_key}"
#access_key= generate_key
puts " this is the access key #{#access_key}"
#name= #user.name
puts "#{#name}"
#time= Time.now.in_time_zone("Nairobi")
puts "This is the time request sent #{#time}"
#server_key = SecureRandom.base64
puts "This is the server key #{#server_key}"
#data = 'This request is being made from Learnida for users to make a payment'
#digest = OpenSSL::Digest.new('sha256')
uri = URI.parse("https://learnida.com")
#hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), #secret_key, #access_key)
puts "This is the HMAC #{#hmac}"
req = Net::HTTP::Get.new(uri)
req['Authorization'] = "TM-HMAC-SHA256 key=#{#access_key} ts=#{#time} sign=#{#hmac}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
#hmacdigest= OpenSSL::HMAC.digest(#digest, #server_key, #data)
puts" This is the HMAC:SHA-256: #{#hmacdigest}"
#puts res.body
#=> "\xF7\xBC\x83\xF40S\x84$\xB12\x98\xE6\xAAo\xB1C\xEFMY\xA1IF\x17Y\x97G\x9D\xBC-\x1A<\xD8"
#sslkey= OpenSSL::HMAC.hexdigest(#digest, #server_key, #data)
puts #sslkey
In my case (Ticketmatic) I had to create the HMAC like above and add an Authorization header to the request with the HMAC in it.
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret_key, access_key + name + time)
req = Net::HTTP::Get.new(uri)
req['Authorization'] = "TM-HMAC-SHA256 key=#{access_key} ts=#{time} sign=#{hmac}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
You can find a full gist here
And a blogpost with more explantion here

How to encrypt data in a UTF-8 string using OpenSSL::Cipher?

In a Rails 3.0 (Ruby 1.9.2) app I'm trying to encrypt some data using something like this:
cipher = OpenSSL::Cipher.new 'aes-256-cbc'
cipher.encrypt
cipher.key = cipher.random_key
cipher.iv = cipher.random_iv
encrypted = cipher.update 'most secret data in the world'
encrypted << cipher.final
That will go into a UTF-8 database. My problem is that
> encrypted.encoding
=> #<Encoding:ASCII-8BIT>
> encrypted.encode 'utf-8'
Encoding::UndefinedConversionError: "\xF7" from ASCII-8BIT to UTF-8
How can I get an UTF-8 encrypted string?
The solution is to convert the ASCII-8BIT string to Base64 and then encode to UTF-8.
cipher = OpenSSL::Cipher.new 'aes-256-cbc'
cipher.encrypt
cipher.key = cipher.random_key
cipher.iv = cipher.random_iv
encrypted = cipher.update 'most secret data in the world'
encrypted << cipher.final
encoded = Base64.encode64(encrypted).encode('utf-8')
Once persisted and retrieved from the database,
decoded = Base64.decode64 encoded.encode('ascii-8bit')
and finally decrypt it.
PS: If you're curious:
cipher = OpenSSL::Cipher.new 'aes-256-cbc'
cipher.decrypt
cipher.key = random_key
cipher.iv = random_iv
decrypted = cipher.update encoded
decrypted << cipher.final
> decrypted
=> 'most secret data in the world'
I believe your best bet is to use force_encoding found here.
> encrypted.encoding
=> #<Encoding:ASCII-8BIT>
> encrypted.force_encoding "utf-8"
> encrypted.encoding
=> #<Encoding:UTF-8>

Resources