Reputation: 2328
I'm trying to create an MD5
checksum in ruby
with salt, but I cannot find any way to do this using the standard digest/md5
package.
I know I can do this:
require 'digest/md5'
checksum = '$1$' + (Digest::MD5.new << plaintext).to_s
However, there doesn't appear to be any way to specify salt for this MD5
checksum generation using digest
, and I haven't found any other package I could use for this in ruby
.
Is this even possible in ruby
?
Upvotes: 0
Views: 1405
Reputation: 84433
If you're trying to manage *nix system passwords, you're better off just shelling out to system utilities rather than building your own. However, if you want to generate or validate salted passwords with only Ruby core or standard library capabilities, you certainly can.
A computed MD5 password with salt is generally stored in a flat-file database (e.g. /etc/shadow) like this, where $
is the field separator:
$1$salt$hashed_pw
Note that the first two fields are stored in cleartext, because they're needed to rebuild and hash the correct string when presented with only the password to validate. As a result, you need to treat the salt as a separate variable from the plaintext password, although the salt is included with the password when hashed.
If you don't have a character limitation imposed by your utilites, one way to generate a strong salt is to use SecureRandom#uuid to generate a UUIDv4 value. For example:
require 'securerandom'
salt = SecureRandom.uuid
#=> "c05280ef-151c-4ebc-83c6-f5f0906f89c2"
You can then invoke your MD5 hash on salt + pw
or pw + salt
depending on your application's password implementation. For example:
require 'digest/md5'
MD5_STR_FMT = '$1$%s$%s'.freeze
salt = 'c05280ef-151c-4ebc-83c6-f5f0906f89c2'
pw = 'plaintext password gathered securely'
pw_digest = Digest::MD5.new << salt + pw
pw_entry = MD5_STR_FMT % [salt, pw_digest]
#=> "$1$c05280ef-151c-4ebc-83c6-f5f0906f89c2$87dcc23c0008e45526e474d0364e4aa5"
You then store pw_entry in your password database file, and then parse out the salt to prepend to the offered password when you recompute the hash during authentication. For example:
require 'digest/md5'
# this is how we'll validate a password from user
# input against an entry from the password database
def valid_pw? pw, salt, hashed_pw
pw_digest = Digest::MD5.new << salt + pw
pw_digest.to_s.eql? hashed_pw.to_s
end
# extract salt and password from a database entry
def parse_pw_entry str
str.split(?$).slice -2, 2
end
# get this from your password database in whatever
# way you like
pw_entry = '$1$c05280ef-151c-4ebc-83c6-f5f0906f89c2$87dcc23c0008e45526e474d0364e4aa5'
# for demonstration purposes only; gather password
# securely from user, then perform your validation
['foo', 'plaintext password gathered securely'].map do |pw|
valid_pw? pw, *parse_pw_entry(pw_entry)
end
#=> [false, true]
Upvotes: 1
Reputation: 2328
I found the following, and it does what I want ...
https://github.com/mogest/unix-crypt
It works like this:
require 'unix_crypt'
checksum = UnixCrypt::MD5.build(plaintext, salt)
This generates the same checksum as is used in /etc/shadow, which is what I want to use it for,
Upvotes: 0
Reputation: 107097
You can add compute the digest of multiple chunks like this:
require 'digest/md5'
md5 = Digest::MD5.new
md5 << '$1$'
md5 << plaintext
checksum = md5.to_s
Or by string concatenation of the salt and the text in one method call:
salt = '$1$'
checksum = Digest::MD5.hexdigest("#{salt}#{plaintext}")
Upvotes: 0