6

Is it possible to specify which salt is used when encrypting strings with ruby-bcrypt?

I know it's not very safe, but I only use it for not-high security data: I have a plattform, and when a user deletes his account, i still want to know if this user was registered with this email before (due to free credits on registration).

So I thought I will encrypt the email with Bcrypt (before deletion) and later I can query afterwards if this hash exists when the user wants to register again with this email address?

But now i realized that bcrypt always procudes a new salt... Can I specify the salt somehow?

Thanks,

DISCLAIMER/ATTENTION:

IN GENERAL YOU SHOULD NEVER SPECIFY SALTS DIRECTLY - ITS INSECURE!!!

BvuRVKyUVlViVIc7
  • 11,641
  • 9
  • 59
  • 111
  • Can't you store the unique salt somewhere along with the password hash? (If you only have one field to work with you could just stick a known separator in there between the hashed email address and the salt) – struthersneil Oct 24 '13 at 23:40
  • 1
    Wait, I'm being an idiot. Bcrypt handles the storage of hash + salt already. – struthersneil Oct 24 '13 at 23:50

3 Answers3

11

Yes you can:

BCrypt::Engine.hash_secret( 'foo@example.com', "$2a$10$ThisIsTheSalt22CharsX." )

The first 7 chars are not technically the salt, they identify the bcrypt algorithm, and set the number of iterations to 2**10 == 1024. For simplicity though, Ruby's bcrypt module treats the first characters as part of the salt, so you need to as well. You should probably use

BCrypt::Engine.generate_salt

To create your shared salt, as that doesn't rely on you to come up with something "random".

To improve security a little, you could keep the salt separate from the searchable hashes, and treat it much like any other secret data within the app. For example, make it a config item, and only store the hash part of the bcrypt for searching (the first part is redundant data for you anyhow and will reduce performance of the search, although that effect is probably very small):

email = 'foo@example.com'
full_hash = BCrypt::Engine.hash_secret( email, settings.email_search_salt )
searchable_hash = full_hash[29,31]
# Either store or search . . .
Neil Slater
  • 26,512
  • 6
  • 76
  • 94
8

You could use BCrypt::Password.create, passing it the email, to generate those hashes along with a unique salt.

2.0.0-p195 :003 > hashed_email = BCrypt::Password.create 'joe@test.com'
 => "$2a$10$vX2tl3omW9h4k66XC7/BwOFH0n7EqtH4PJATPa7YVSeJh7TEpt/bK" 
2.0.0-p195 :004 > hashed_email = BCrypt::Password.create 'joe@test.com'
 => "$2a$10$RdQIHtz.L5To1F1XRK//..h6nHYdQ3uJ2PTgB58e3xufoqgZGqbO6" 
2.0.0-p195 :005 > hashed_email = BCrypt::Password.create 'joe@test.com'
 => "$2a$10$bTFVXO/d0/sf6SxzCcRMU.zBPcR5yjI6ID6O9J2eXKbqim/jPM3PC" 
2.0.0-p195 :006 > hashed_email = BCrypt::Password.create 'joe@test.com'
 => "$2a$10$gbXU4UEiHTC0HCnD672Dm.TeBhZeCa6sBiX8Pk50KSXcprDJnEYA." 

Now you don't have to worry about using a fixed salt as BCrypt has already stored it with the hash for you.

But what I guess you've identified is that it means that there will be a processing cost associated with the comparison later though as you can't just do a 'SELECT user WHERE email_hash = hash'...

If you absolutely want to use a fixed salt, you can.

salt = BCrypt::Engine.generate_salt
hash = BCrypt::Engine.hash_secret 'hello', salt

(Just store that salt string somewhere and you can use it later.)

struthersneil
  • 2,700
  • 10
  • 11
  • 1
    OP needs a fixed salt so that a DB search for the email will work, without needing to store email plaintext. I have done similar work for black-listed credit card numbers and AFAIK, the only solution to keep the result searchable is to use a fixed salt. – Neil Slater Oct 25 '13 at 07:49
  • 1
    This is what I describe after the second paragraph. – struthersneil Oct 25 '13 at 08:41
  • Or another way to put this: "It does mean that there will be a processing cost associated with the comparison later", assuming the site is popular enough that there are a thousand emails to check, you are talking *minutes* of CPU time for the system to figure out whether a signup has been seen before. On each signup. – Neil Slater Oct 25 '13 at 09:32
  • 1
    I am figuring out OP's reasoning as I go...then providing the answer, right there. Perhaps the words 'I would' are too suggestive. Ok. Reworded. – struthersneil Oct 25 '13 at 09:46
  • You should not directly store `hash` to the DB as this will include the salt (which is really a pepper here) in the database, making it worthless if the purpose is email pseudonymization. Instead, remove the salt as suggested in Neil's answer. – idmean Nov 10 '21 at 08:26
1

This is a very old thread but still... here are other options to bcrypt with a fixed salt:

If security is not an issue (mere obfuscation): use md5

Digest::MD5.hexdigest 'some value'

pros: Its computationally inexpensive (faster generation)

cons: its computationally inexpensive (faster hacking)

If security is an issue, you may encrypt it using a shared secret (no hash). See https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL.html#module-OpenSSL-label-Encryption

estani
  • 24,254
  • 2
  • 93
  • 76