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

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>

Related

Origami & OpenSSL error while validating digital signature of a pdf in rails

I need to verify the digital signature of a PDF that i receive, i searched for a couple of gems and i found Origami pdf reader and openssl to parse the certificate. I have two certificate files one of type .cer and subfilter adbe.x509.rsa_sha1 and other of extension .p7c and of subfilter PKCS7 . I user openssl gem to read the certificate of type .cer and when i try to verify it with pdf it gives me error NotImplementedError: Unsupported signature method "adbe.x509.rsa_sha1" and when i try to read .p7c file OpenSSL gives me error OpenSSL::X509::CertificateError: nested asn1 error
how to overcome these errors if there are any other gems for the same purpose??.
I have referenced to this stack over flow question but it dint help be my code is similar to the code in that question.
cert = OpenSSL::X509::Certificate.new(File::read('2.p7c'))
it throws the second error
cert = OpenSSL::X509::Certificate.new(File::read('a.cer'))
pdf.verify(trusted_certs: [cert])
It gives me the first error
Thanks
# REFERENCE
# https://www.rubydoc.info/gems/origami/Origami/PDF
# https://github.com/gdelugre/origami/blob/master/test/test_pdf_sign.rb
# https://ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/X509/Certificate.html
# Usage
# ruby pdf_signer.rb some_file.pdf
require 'openssl'
require 'stringio'
require "time"
# require 'byebug' Not necessary but useful
begin
require 'origami'
rescue LoadError
$: << File.join(__dir__, "../../lib")
require 'origami'
end
include Origami
input_file = ARGV[0]
OUTPUT_FILE = "#{File.basename(__FILE__, ".rb")}.pdf"
puts "Generating a RSA key pair."
key = OpenSSL::PKey::RSA.new 2048
puts "Generating a self-signed certificate."
name = OpenSSL::X509::Name.parse 'CN=origami/DC=example'
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 0
cert.not_before = Time.now
cert.not_after = Time.now + 3600
cert.public_key = key.public_key
cert.subject = name
cert.issuer = name
cert.sign key, OpenSSL::Digest::SHA256.new
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.issuer_certificate = cert
extension_factory.subject_certificate = cert
cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
cert.add_extension extension_factory.create_extension('keyUsage', 'digitalSignature,keyCertSign')
cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
#################################################
# Read input PDF
#################################################
pdf = PDF.read(input_file)
page = pdf.get_page(1)
#################################################
# prepare annotation data ( visable time_stamp )
#################################################
width = 200.0
height = 50.0
x = page.MediaBox[2].to_f - width - height
y = height
size = 8
now = Time.now
text_annotation = Annotation::AppearanceStream.new
text_annotation.Type = Origami::Name.new("XObject")
text_annotation.Resources = Resources.new
text_annotation.Resources.ProcSet = [Origami::Name.new("Text")]
text_annotation.set_indirect(true)
text_annotation.Matrix = [ 1, 0, 0, 1, 0, 0 ]
text_annotation.BBox = [ 0, 0, width, height ]
text_annotation.write("Signed at #{now.iso8601}", x: size, y: (height/2)-(size/2), size: size)
# Add signature annotation (so it becomes visibles in PDF document)
signature_annotation = Annotation::Widget::Signature.new
signature_annotation.Rect = Rectangle[llx: x, lly: y+height, urx: x+width, ury: y]
signature_annotation.F = Annotation::Flags::PRINT
signature_annotation.set_normal_appearance(text_annotation)
page.add_annotation(signature_annotation)
############################
# Sign the PDF with the specified keys
pdf.sign( cert,
key,
method: 'adbe.pkcs7.detached',
annotation: signature_annotation,
location: "Canada",
contact: "someone#localhost.com",
reason: "Proof of concept")
# Save the resulting file
pdf.save(OUTPUT_FILE)
puts "PDF file saved as #{OUTPUT_FILE}."
# Now that we have signed and saved, lets re-open it and prove the concept
document = PDF.read(OUTPUT_FILE)
document.signature.inspect
begin
puts "******"
puts document.verify(trusted_certs: [cert])
puts "******"
rescue StandardError => e
puts e.message
end
#puts document.author
open 'cert_123.crt', 'w' do |io|
io.write cert
end
cert_2 = OpenSSL::X509::Certificate.new(File.read('cert_123.crt'))
puts "$" * 40
puts document.verify(trusted_certs: [cert_2])
puts "$" * 40
# To verify that all of this has worked use adobe acrobat reader or similar
# product in which you can inspect the signatures. Not all PDF readers will
# allow you to read these signatures```

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.

how to encrypt data with AES 256 ECB PKCS5Padding in ruby

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!!'

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

Resources