When making a C# program that talks to an existing Ruby on Rails program, the following is helpful for working with signed cookies.
using Newtonsoft.Json.Linq;
using System.Security.Cryptography;
using System.Text;
String getValue(string cookie, string secret)
{
var values = cookie.Split("--");
var returnValue = "";
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(Encoding.ASCII.GetBytes(secret),
Encoding.ASCII.GetBytes("signed cookie"), 1000, HashAlgorithmName.SHA256);
var key = pbkdf2.GetBytes(64);
HMACSHA256 digest = new HMACSHA256(key);
var data = digest.ComputeHash(Encoding.ASCII.GetBytes(values[0]));
if (values[1] == ToHexString(data))
{
JObject jsonValue = JObject.Parse(Encoding.ASCII.GetString(Convert.FromBase64String(values[0])));
var _rails = jsonValue["_rails"];
if (_rails != null)
{
var message = _rails["message"].ToString();
if (message != null)
{
returnValue = Encoding.ASCII.GetString(Convert.FromBase64String(message));
}
}
}
return returnValue;
}
Signed cookies are used when the data stored is harmless for the end user to view but not modify. These can be user id’s that would be seen anyway or other values that aren’t sensitive.
The basic way this works is a hash is created of the message using a chosen cipher with a private key on the server with salt. This is then applied to the cookie after the “–”. Once that happens, you can ensure the message hasn’t been tampered with otherwise the hash would change.
The data is then Base64 encoded for easier transport.
:signed_cookie_salt=>"signed cookie",
:encrypted_cookie_salt=>"encrypted cookie",
:encrypted_signed_cookie_salt=>"signed encrypted cookie",
:authenticated_encrypted_cookie_salt=>"authenticated encrypted cookie",
irb(main):001:0> Rails.application.config.action_dispatch
Normally stored as _app_session_data
def verify_and_decrypt_session_cookie(cookie, secret_key_base)
data, iv, auth_tag = cookie.split("--").map do |v|
Base64.strict_decode64(v)
end
# encrypted_cookie_cipher
cipher = OpenSSL::Cipher.new("aes-256-gcm")
# secret_key_base, authenticated_encrypted_cookie, rounds, key_size, signed_cookie_digest
secret = OpenSSL::PKCS5.pbkdf2_hmac(secret_key_base, "authenticated encrypted cookie", 1000, 32, OpenSSL::Digest::SHA256.new)
cipher.decrypt
cipher.key = secret
cipher.iv = iv
cipher.auth_tag = auth_tag
cipher.auth_data = ""
cookie_payload = cipher.update(data)
cookie_payload << cipher.final
cookie_payload = JSON.parse cookie_payload
decoded_stored_value = Base64.decode64 cookie_payload["_rails"]["message"]
stored_value = JSON.parse decoded_stored_value
end
cookies.signed[:appdata] = 'Joseph'
def verify_cookie(cookie, secret)
values = cookie.split("--").first
# secret_key_base, signed_cookie_salt, rounds, key_length, hash
key = OpenSSL::PKCS5.pbkdf2_hmac(secret, "signed cookie", 1000, 64, OpenSSL::Digest::SHA256.new)
digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, key, values)
digest == cookie.split("--")[1]
end
def cookie_data
data = JSON.parse Base64.decode64(cookie.split('--')[0])
Base64.decode64(data['_rails']['message'])
end