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.
Related
I'm new to rails and trying to encrypt and decrypt a cookie manually. Can anyone tell me where I'm going wrong?
The below code raises this error ActiveSupport::MessageVerifier::InvalidSignature
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
cookie = 'example123'
secret = 'Rails.application.secret_key_base'
p verify_and_decrypt_session_cookie(cookie, secret)
Thanks
Has to be done in ruby
my code is:
require "openssl"
require "time"
require "uri"
require "pathname"
def getSignatureKey (key, dateStamp, regionName, serviceName)
digest= OpenSSL::Digest.new('sha256')
kDate = OpenSSL::HMAC.hexdigest(digest, ("AWS4" + key), dateStamp)
puts("kdate: #{kDate}")
kRegion = OpenSSL::HMAC.hexdigest(digest, kDate, regionName)
puts("kRegion: #{kRegion}")
kService = OpenSSL::HMAC.hexdigest(digest, kRegion, serviceName)
puts("kService: #{kService}")
kSigning = OpenSSL::HMAC.hexdigest(digest, kService, "aws4_request")
puts("kSigning: #{kSigning}")
kSigning
end
key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'
puts("===========================================OUTPUT IS: ==============================================")
getSignatureKey(key,dateStamp,regionName,serviceName)
which produces the output:
kdate: 969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d
kRegion:a59e30f9d899c47b3dd68ea1c0ab3bb529e03a8f4ed2f54cb64af547330a22a0
kService:e33cbcda009243ed24c04af249a477d33b33c97ba7717327636afab2c6e6ef78
kSigning:9d8a8749deaee911a4ed0d04fb34af903f690d0423c146023bd282ba8ebf955b
however the output should be, according to aws documentation: https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html):
kdate: 969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d
kRegion:69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c
kService:f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa
kSigning:f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d
so the kdate matches but the others do not, I haven't miss-spelled "us-east-1" so I'm really not sure why they don't match. If anyone could offer advice I'd be very grateful.
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```
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
It seems like we have always initialize and use the same private key when encoding and decoding a token in RSA256 algorithm:
payload = {:data => 'test'}
rsa_private = OpenSSL::PKey::RSA.generate 2048
rsa_public = rsa_private.public_key
token = JWT.encode payload, rsa_private, 'RS256'
# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0ZXN0IjoiZGF0YSJ9.c2FynXNyi6_PeKxrDGxfS3OLwQ8lTDbWBWdq7oMviCy2ZfFpzvW2E_odCWJrbLof-eplHCsKzW7MGAntHMALXgclm_Cs9i2Exi6BZHzpr9suYkrhIjwqV1tCgMBCQpdeMwIq6SyKVjgH3L51ivIt0-GDDPDH1Rcut3jRQzp3Q35bg3tcI2iVg7t3Msvl9QrxXAdYNFiS5KXH22aJZ8X_O2HgqVYBXfSB1ygTYUmKTIIyLbntPQ7R22rFko1knGWOgQCoYXwbtpuKRZVFrxX958L2gUWgb4jEQNf3fhOtkBm1mJpj-7BGst00o8g_3P2zHy-3aKgpPo1XlKQGjRrrxA
puts token
decoded_token = JWT.decode token, rsa_public, true, { :algorithm => 'RS256' }
# Array
# [
# {"data"=>"test"}, # payload
# {"alg"=>"RS256"} # header
# ]
puts decoded_token
But what is the best way t do that in a Rails 5.1 app ?
I figured out how to fix that.
So I have a class JsonWebTokenwith 2 class methods: encodeand decodedefined as follows:
class JsonWebToken
ALGO = 'RS256'
class << self
def encode(payload, exp = 2.hours.from_now)
# set expiry to 2 hours from creation time
payload[:exp] = exp.to_i
JWT.encode(payload, private_key, ALGO)
end
def decode(token)
body = JWT.decode(token, private_key.public_key, true, algorithm: ALGO)[0]
HashWithIndifferentAccess.new body
# rescue from expiry exception
rescue JWT::ExpiredSignature, JWT::VerificationError => e
# raise custom error to be handled by custom handler
raise ExceptionHandler::ExpiredSignature, e.message
end
private
def private_key
#rsa_private ||= OpenSSL::PKey::RSA.generate 2048
end
end
end
I just use another private static method to generate an rsa private key if needed.