I'm new to ruby on rails, and I'm developing an application that will have very sensitive data (api keys from other websites) and I need to store it encrypted in a db but without knowing them at any time.
Let me explain myself:
The form asks the user for his api keys
Encrypt them
Store it in the db
The main question is, how do I encrypt them in such a way that I can use them later (still without knowing them)?
Sorry if the question is silly, but I can't find a way to do it, and thanks.
I've used attr_encrypted for this. Works great.
class User
attr_encrypted :ssn, key: 'This is a key that is 256 bits!!'
end
You then work with ssn as if it were a plain field
user = User.find(1)
puts user.ssn
but it's encrypted at rest (in the database) and can't be retrieved without the key.
def encrypt text
text = text.to_s unless text.is_a? String
len = ActiveSupport::MessageEncryptor.key_len
salt = SecureRandom.hex len
key = ActiveSupport::KeyGenerator.new(Rails.application.secrets.secret_key_base).generate_key salt, len
crypt = ActiveSupport::MessageEncryptor.new key
encrypted_data = crypt.encrypt_and_sign text
"#{salt}$$#{encrypted_data}"
end
def decrypt text
salt, data = text.split "$$"
len = ActiveSupport::MessageEncryptor.key_len
key = ActiveSupport::KeyGenerator.new(Rails.application.secrets.secret_key_base).generate_key salt, len
crypt = ActiveSupport::MessageEncryptor.new key
crypt.decrypt_and_verify data
end
Pass the key to encrypt method and store the returned encrypted value in DB.
Then to decrypt pass the encrypted key to the decrypt method.
This is assuming your Secret Key Base is in Rails.application.secrets.secret_key_base
The original source for the answer is here
Related
I want to store a keyset, and would like the file to be encrypted with key produced from a user-provided "master password". And of course, at a later point I'd like to, given the same master password, be able to load that keyset by decrypting the file.
It seems that I need an Aead, which I can generate from a KeysetHandle with AeadFactory.getPrimitive(keysetHandle). But how can I produce a KeysetHandle from a "master password"?
(And for the context of this question, getting that key from a Key Management Systems, instead of producing it "out of thin air" from a master password, isn't an option.)
An Aead can be created as follows (here done from Scala):
val password: String = "..."
val aead = {
val messageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(password.getBytes(CharsetNames.Utf8))
val key256Bit = messageDigest.digest()
val key128Bit = key256Bit.take(16)
new AesGcmJce(key128Bit)
}
A few comments:
I'd prefer the key to be based on a 32-bit digest, but the cipher picked by Tink in this case throws an exception when provided with a 32-bit key, hence the shortening to a 16-bit key.
It seems shortening the key this way is ok from a hash weakness perspective.
I'm using the attr_encrypted gem in a Rails project and encrypting bank account numbers in a PostgreSQL database. In the database, I have two columns (encrypted_bank_account and encrypted_bank_account_iv) that are used for the encrypted data. I have this line in my payment_method model file:
attr_encrypted :account_number, key: ENV['ACCOUNT_KEY'], encode: true, encode_iv: true, algorithm: 'aes-256-cbc'
I want to be able to send this encrypted data to SalesForce, decrypt it, and store it in a SalesForce object. I'm not exactly sure how to decrypt it when it gets there and could use some advice. Thanks!
Have an endpoint on salesforce that takes the data and decrypts it in
// Normally this key should be stored in a protected custom setting
// or an encrypted field on a custom object
Blob cryptoKey = Crypto.generateAesKey(256);
// Generate the data to be encrypted.
Blob data = Blob.valueOf('Test data to encrypted');
// Encrypt the data and have Salesforce.com generate the initialization vector
Blob encryptedData = Crypto.encryptWithManagedIV('AES256', cryptoKey, data);
// Decrypt the data - the first 16 bytes contain the initialization vector
Blob decryptedData = Crypto.decryptWithManagedIV('AES256', cryptoKey, encryptedData);
// Decode the decrypted data for subsequent use
String decryptedDataString = decryptedData.toString();
https://developer.salesforce.com/page/Apex_Crypto_Class
Say I have copied the string for a cookie from a browser request.
_some_session=RXF6SVF5RHdV...
I want to open the rails console and paste something like
> session[RXF6SVF5RHdV...]
To retrieve the decrypted data from the session. If this is possible, how do I do it?
Yes it is possible, here is more detailed way, open your rails console to try this:
content = 'BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJTgwZGFiNzhiYWZmYTc3NjU1ZmVmMGUxM2EzYmEyMDhhBjsAVEkiFGdpdGh1Yl91c2VybmFtZQY7AEZJIhJuZWVyYWpkb3RuYW1lBjsARkkiEF9jc3JmX3Rva2VuBjsARkkiMU1KTCs2dXVnRFo2R2NTdG5Kb3E2dm5BclZYRGJGbjJ1TXZEU0swamxyWU09BjsARg%3D%3D--b5bcce534ceab56616d4a215246e9eb1fc9984a4'
assuming content is your session cookie,
When the content is written to cookie then it is escaped. So first we need to unescape it.
> unescaped_content = URI.unescape(content)
=> "BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJTgwZGFiNzhiYWZmYTc3NjU1ZmVmMGUxM2EzYmEyMDhhBjsAVEkiFGdpdGh1Yl91c2VybmFtZQY7AEZJIhJuZWVyYWpkb3RuYW1lBjsARkkiEF9jc3JmX3Rva2VuBjsARkkiMU1KTCs2dXVnRFo2R2NTdG5Kb3E2dm5BclZYRGJGbjJ1TXZEU0swamxyWU09BjsARg==--b5bcce534ceab56616d4a215246e9eb1fc9984a4"
Notice that towards the end unescaped_content has -- . That is a separation marker. The value before -- is the real payload. The value after -- is digest of data.
> data, digest = unescaped_content.split('--')
=> ["BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJTgwZGFiNzhiYWZmYTc3NjU1ZmVmMGUxM2EzYmEyMDhhBj VEkiFGdpdGh1Yl91c2VybmFtZQY7AEZJIhJuZWVyYWpkb3RuYW1lBjsARkkiEF9jc3JmX3Rva2VuBjsARkkiMU1KTCs2dXVnRFo2R
2NTdG5Kb3E2dm5BclZYRGJ
GbjJ1TXZEU0swamxyWU09BjsARg==", "b5bcce534ceab56616d4a215246e9eb1fc9984a4"]
The data is Base64 encoded. So let’s unecode it.
> Marshal.load(::Base64.decode64(data))
=> {"session_id"=>"80dab78baffa77655fef0e13a3ba208a",
"github_username"=>"manoj910",
"_csrf_token"=>"MJL+6uugDZ6GcStnJoq6vnArVXDbFn2uMvDSK0jlrYM="}
So we are able to get the data that is stored in cookie.
Here is an answer that works for non-cookie-based sessions too. I.e. normally all that you would save in the cookie is the session ID. Now given the session ID you can lookup the session data independently from the used session store (file, cache, ActiveRecord) like this:
def lookup_session_data(session_id)
session_store = Rails.application.config.session_store.new nil, {}
_id, data = session_store.find_session({}, Rack::Session::SessionId.new(session_id))
data if data.present?
end
session_id = "..."
lookup_session_data session_id
The organization that I work for uses PPolicy (an OpenLDAP module) to automatically salt and hash passwords. Unfortunately, I don't have access to the machine running the OpenLDAP server, so i can't look at the config file. From what I've seen though, pretty much everything appears to be setup using the default settings.
I'd like to be able to retrieve the salt for a specific user. If I look at the user's attributes, userPassword is the SSHA password. I don't see anything about a salt for that specific user. I ended up looking at the LDAP schema and I see nothing about salts there either.
If you were to guess where the salt were being stored for each user, where would it be? I understand this is vague and probably not a lot of information, but I can't find anywhere in the OpenLDAP docs that explain where exactly the unique salts are stored. Perhaps someone who has configured an OpenLDAP server before would know where the default location is.
Thank you.
With SSHA, normally the salt is appended to the SHA1 hash and then the whole thing is Base64 encoded (I've never seen an LDAP that didn't do SSHA this way). You should be able to tell this by looking at the userPassword attribute. If it's 28 character long with a = at the end, it's only the hash.
If the Base64 value is 32 character long or greater, it contains both the hash and the salt. Base64 decode the value and strip off the first 20 bytes, this is the SHA1 hash. The remaining bytes are the salt.
Example:
Base64 encoded hash with salt
userPassword: {SSHA}MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0
Base64 decoded value
SHA1 Hash Salt
--------------------++++
123456789012345678901234
Edit: After double checking, it seems that variable length salts are sometimes supported. Corrected the encoding description to account for this.
The post of Syon did help me a lot, thanks for that! I thought a working test would be a nice extra for someone else struggling with this topic ;).
public class SshaPasswordVerifyTest {
private final static int SIZE_SHA1_HASH = 20;
#Test
public void itShouldVerifyPassword() throws Exception{
String password = "YouNeverGuess!";
String encodedPasswordWithSSHA = "{SSHA}M6HeeJAbwUCzuLwXbq00Fc3n3XcxFI8KjQkqeg==";
Assert.assertEquals(encodedPasswordWithSSHA, getSshaDigestFor(password, getSalt(encodedPasswordWithSSHA)));
}
// The salt is the remaining part after the SHA1_hash
private byte[] getSalt(String encodedPasswordWithSSHA){
byte[] data = Base64.getMimeDecoder().decode(encodedPasswordWithSSHA.substring(6));
return Arrays.copyOfRange(data, SIZE_SHA1_HASH, data.length);
}
private String getSshaDigestFor(String password, byte[] salt) throws Exception{
// create a SHA1 digest of the password + salt
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(password.getBytes(Charset.forName("UTF-8")));
crypt.update(salt);
byte[] hash = crypt.digest();
// concatenate the hash with the salt
byte[] hashPlusSalt = new byte[hash.length + salt.length];
System.arraycopy(hash, 0, hashPlusSalt, 0, hash.length);
System.arraycopy(salt, 0, hashPlusSalt, hash.length, salt.length);
// prepend the SSHA tag + base64 encode the result
return "{SSHA}" + Base64.getEncoder().encodeToString(hashPlusSalt);
}
}
In PHP, this compares a plain text password (usually entered by a user) to a given ssha hash (usually stored in your db):
private function checkSshaPassword($encrypted_password, $password)
{
// get hash and salt from encrypted_password
$base_64_hash_with_salt = substr($encrypted_password, 6);
$hash_with_salt = base64_decode($base_64_hash_with_salt);
$hash = substr($hash_with_salt, 0, 20);
$salt = substr($hash_with_salt, 20);
// hash given password
$hash_given = sha1($password . $salt, true);
return ($hash == $hash_given);
}
I am having to implement a payment gateway in Rails that I've not worked with or seen before (Westpac's Payway in Australia if anyone is interested).
Their documentation isn't bad and the system is fairly logical, so much so that it's been quite painless so far (a miracle for payment integration).
Where there is an issue is that after the payment is POSTed directly to Westpac and the payment processed they redirect back to our site with a large encrypted parameter. This is then meant to be decrypted by us to get access to the actual parameters.
Here is Westpac's guidance:
The parameters are encrypted using AES with Cipher Block Chaining, using PCKS-5
Padding. The decryption algorithm should be initialised with a 16 byte, zero-filled
initialization vector, and should use your encryption key (which can be found on the Security page of PayWay Net Shopping Cart setup).
Before decryption, the parameters passed with the redirect will appear as follows:
EncryptedParameters=QzFtdn0%2B66KJV5L8ihbr6ofdmrkEQwqMXI3ayF7UpVlRheR7r5fA6
IqBszeKFoGSyR7c7J4YsXgaOergu5SWD%2FvL%2FzPSrZER9BS7mZGckriBrhYt%2FKMAbTSS8F
XR72gWJZsul9aGyGbFripp7XxE9NQHVMWCko0NlpWe7oZ0RBIgNpIZ3JojAfX7b1j%2F5ACJ79S
VeOIK80layBwCmIPOpB%2B%2BNI6krE0wekvkkLKF7CXilj5qITvmv%2FpMqwVDchv%2FUNMfCi
4uUA4igHGhaZDQcV8U%2BcYRO8dv%2FnqVbAjkNwBqxqN3UPNFz0Tt76%2BP7H48PDpU23c61eM
7mx%2FZh%2Few5Pd0WkiCwZVkSZoov97BWdnMIw5tOAiqHvAR3%2BnfmGsx
Westpac has no Rails demos but they do have PHP. Here is the PHP demo:
function decrypt_parameters( $base64Key, $encryptedParametersText, $signatureText )
{
$key = base64_decode( $base64Key );
$iv = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
$td = mcrypt_module_open('rijndael-128', '', 'cbc', '');
// Decrypt the parameter text
mcrypt_generic_init($td, $key, $iv);
$parametersText = mdecrypt_generic($td, base64_decode( $encryptedParametersText ) );
$parametersText = pkcs5_unpad( $parametersText );
mcrypt_generic_deinit($td);
}
Here is what I've tried in Rails:
def Crypto.decrypt(encrypted_data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.decrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(encrypted_data) + aes.final
end
iv = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
key = Base64.decode64("mysecretkey")
data = Base64.decode64("QzFtdn0%2B66KJV5L8ihbr6ofdmrkEQwqMXI3ayF7UpVlRheR7r5fA6
IqBszeKFoGSyR7c7J4YsXgaOergu5SWD%2FvL%2FzPSrZER9BS7mZGckriBrhYt%2FKMAbTSS8F
XR72gWJZsul9aGyGbFripp7XxE9NQHVMWCko0NlpWe7oZ0RBIgNpIZ3JojAfX7b1j%2F5ACJ79S
VeOIK80layBwCmIPOpB%2B%2BNI6krE0wekvkkLKF7CXilj5qITvmv%2FpMqwVDchv%2FUNMfCi
4uUA4igHGhaZDQcV8U%2BcYRO8dv%2FnqVbAjkNwBqxqN3UPNFz0Tt76%2BP7H48PDpU23c61eM
7mx%2FZh%2Few5Pd0WkiCwZVkSZoov97BWdnMIw5tOAiqHvAR3%2BnfmGsx")
cleartext = Crypto.decrypt(data, key, iv, "AES-128-CBC")
And I simply pass in the same initialization vector as noted in the PHP, though I'm not sure this is correct for Rails.
In any event, the key is provided and easy to Base64 decode, as are the Encrypted Parameters. At the end of the day, I'm getting this error:
cipher.rb:21:in `final': wrong final block length (OpenSSL::Cipher::CipherError)
from cipher.rb:21:in `decrypt'
from cipher.rb:29:in `<main>'
Admittedly, I'm out of my depth on this Crypto stuff but am up against a wall and do not have the time (despite the interest) to learn more.
The problem was, that the input data was additionally "URI-escaped" and ruby's base64-decoder did not "care" about the invalid base64-input (% is no base64-digit), so no error was raised.
The solution was to "unescape" the URI-encoding with URI.unescape:
require 'uri'
data = Base64.decode64(
URI.unescape("QzFtdn0%2B66 ... Iw5tOAiqHvAR3%2BnfmGsx"))
Of course, if the input data is received from a GET/POST parameter, the input data is most probably already "unescaped" by your web-stack - just as a note of caution (double unescape may cause problems if a percent-sign % appears in the input data).