ruby: create MD5 checksum with salt? - ruby

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?

Creating/Validating *nix Style MD5 Entries with Salt
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]

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}")

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,

Related

Create a program that asks for a password without showing it in the code

I want to write a program in Ruby that can ask for a password and verify if the password entered correspond to a valid password.
The thing is, I could write a function in ruby that could check if the password entered is the good one like :
def is_valid?(password)
password == "my_password"
end
But then if someone is looking at the file, the password is going to be revealed.
So how do I do this?
Hash the password and store the hash as a string.
When the user types the password, hash it and compare it to the hashed string. If it matches, it's correct otherwise it's not.
This is secure since you can't get the original password from the hashed string.
This example uses SHA-512, which is secure, since it can't be brute forced (yet).
def is_valid?(password)
hash = Digest::SHA512.hexdigest(password)
mypassword == #the hash of your password
if hash == mypassword
return true
else
return false
end
Edit:
As #Jörg W Mittag suggested, using Argon2 is a better option in terms of security, since it is actually for password hashing.
More info on Argon2:
https://github.com/technion/ruby-argon2
--
What is hashing?
https://en.wikipedia.org/wiki/Hash_function
--
Hashing in ruby:
http://www.informit.com/articles/article.aspx?p=2314083&seqNum=35
https://richonrails.com/articles/hashing-data-in-ruby
You can use the bcrypt gem.
Extracted from their docs:
require 'bcrypt'
my_password = BCrypt::Password.create("my password")
#=> "$2a$12$K0ByB.6YI2/OYrB4fQOYLe6Tv0datUVf6VZ/2Jzwm879BW5K1cHey"
my_password == "my password" #=> true
my_password == "not my password" #=> false
my_password = BCrypt::Password.new("$2a$12$K0ByB.6YI2/OYrB4fQOYLe6Tv0datUVf6VZ/2Jzwm879BW5K1cHey")
my_password == "my password" #=> true
my_password == "not my password" #=> false

Ruby update all same object data in hash array for JSON.parse

So a = first is
=> <Ng::EntityConfiguration id: 15903, entity_id: 1, entity_type: "Ng::Company", key: "wpa2.psk", value: "[{"ssid":"Main-Hall-Staff","password":"abc123","dhcp":"Enabled"},{"ssid":"Main-Hall-Guest","password":"def456","dhcp":"Disabled"}]", created_at: "2016-11-08 11:03:51", updated_at: "2016-11-08 11:03:51", name: "WIFI/Main Hall">
I have a.value which is will return:
"[
{\"ssid\":\"Main-Hall-Staff\",\"password\":\"abc123\"},
{\"ssid\":\"Main-Hall-Guest\",\"password\":\"def456\"}
]"
My question is, how to update both password value and save it?
new_pass1 = 'xyz123'
new_pass2 = 'xyz321'
I have tried code (below) but this will only update first password if i only have one hash_array.
Here is my full code
def encrypt_pass
# get the actual password
parse = JSON.parse(self.value)
get_pass = parse.last['password']
# encrypt the password
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_KEY_BASE"])
encrypted = crypt.encrypt_and_sign(get_pass)
# save the new encrypted password
parse.first['password'] = encrypted
encrypt_pass = parse.to_json
self.value = encrypt_pass
end
Just to be clear, you're trying to update both the Main-Hall-Staff password and the Main-Hall-Guest password (all passwords) from your record to be the encrypted version of themselves? I'm assuming this method is called in a before_save callback of some sort? If you show more code related to the model I can give you more details.
def encrypt_pass
# Changed the name to devises, always use easy to understand naming
# Also rescuing from a JSON parse error, this isnt always recommended
# as it hides other errors that might be unrelated to parsing
devices = JSON.parse(self.value) rescue []
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_KEY_BASE"])
devices.each do |device|
# get the actual password
password = device['password']
# encrypt the password
encrypted_pass = crypt.encrypt_and_sign(password)
# Save the encrypted password
device['password'] = encrypted_pass
end
self.value = devices.to_json
end
Hopefully you have some logic surrounding when this method is called as you dont want to encrypt an already encrypted password.

Encrypt empty string

I am using Ruby's Open SSL bindings to do AES-256 encryption. I can encrypt a non-empty string. However, when attempting to encrypt an empty string, Ruby raises an exception complaining that the data must not be empty. How can I encrypt an empty string using Ruby's OpenSSL bindings?
Code to reproduce the problem
require "openssl"
KEY = OpenSSL::Cipher::Cipher.new("aes-256-cbc").random_key
def encrypt(plaintext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.encrypt
iv = cipher.random_iv
cipher.iv = iv
cipher.key = KEY
ciphertext = cipher.update(plaintext) # <- ArgumentError here
ciphertext << cipher.final
[iv, ciphertext]
end
def decrypt(iv, ciphertext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.iv = iv
cipher.key = KEY
plaintext = cipher.update(ciphertext)
plaintext << cipher.final
plaintext
end
p decrypt(*encrypt("foo")) # "foo"
p decrypt(*encrypt(""))
# /tmp/foo.rb:11:in `update': data must not be empty (ArgumentError)
# from /tmp/foo.rb:11:in `encrypt'
# from /tmp/foo.rb:27:in `<main>'
Versions
ruby-2.2.2p95
OpenSSL::VERSION is "1.1.0"
Microsoft SQL Server 2014 (12.0.2000.8)
Why do I want to encrypt empty strings?
I am writing an ETL program to migrate data from one database to a SqlServer database. Certain columns from the source database must be encrypted before writing them to the destination database. The source columns may contain any data, including empty strings. The destination columns are usually non-nullable. The destination columns will be decrypted by .net code.
Goal #1: No information about the encrypted field, including whether or not it even exists, should be recoverable without properly decrypting it. An encrypted empty string should be indistinguishable from any other encrypted data.
Goal #2: The .net code that will decrypt these values should not need to handle empty strings specially.
If I can get openssl to encrypt empty strings, I will achieve both of these goals.
Workaround - Don't encrypt empty strings
I could just not encrypt empty strings, passing them through.
def encrypt(plaintext)
return plaintext if plaintext.empty?
...
end
def decrypt(iv, ciphertext)
return ciphertext if ciphertext.empty?
...
end
This has the disadvantages of exposing information, and also of requiring cooperating code to be written on the .net side.
Workaround - Add some constant to the plaintext
I could add some constant string to the plaintext before encryption, and remove it after decryption:
PLAINTEXT_SUFFIX = " "
def encrypt(plaintext)
plaintext += PLAINTEXT_SUFFIX
...
end
def decrypt(iv, ciphertext)
...
plaintext.chomp(PLAINTEXT_SUFFIX)
end
This hides whether the data exists or not, but still requires cooperating .net code.
As suggested by #ArtjomB, it's as simple as not calling Cipher#update with the empty string. The value returned by Cipher#final then properly encrypts an empty string.
require "openssl"
KEY = OpenSSL::Cipher::Cipher.new("aes-256-cbc").random_key
def encrypt(plaintext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.encrypt
iv = cipher.random_iv
cipher.iv = iv
cipher.key = KEY
ciphertext = ""
ciphertext << cipher.update(plaintext) unless plaintext.empty?
ciphertext << cipher.final
[iv, ciphertext]
end
def decrypt(iv, ciphertext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.iv = iv
cipher.key = KEY
plaintext = cipher.update(ciphertext)
plaintext << cipher.final
end
p decrypt(*encrypt("foo")) # "foo"
p decrypt(*encrypt("")) # ""
If you can use DBMS provided encryption functions, then, MySQL AES_ENCRYPT, seems to be able to encrypt blank string.
For example:
UPDATE some_table
SET some_column = AES_ENCRYPT('',UNHEX('F3229A0B371ED2D9441B830D21A390C3'));
It's AES-128 by default, I am guessing that will be a problem as you need AES-256. Also, not sure which DBMS you are using and whether that DBMS has encryption functions.

How should I check passwords in Ruby?

I am using Ruby to manage users in a database.
I am using pass = Digest::MD5.hexdigest() to encrypt passwords before they are being added to the database.
I need to create a function to check that a given password matches that stored in the database but I'm not sure how I should do it.
Do I use pass = Digest::MD5.hexdigest() on the user provided password, and then check that against what is returned from the database?
This is correctpassword?:
def correctpassword?(nick, pass)
user = nick
pass = Digest::MD5.hexdigest(pass)
db = SQLite3::Database.new "database.db"
db.execute("SELECT * FROM t1 WHERE user = ? and pass = ?", user, pass)
!results.empty?
end
This is attempting to use correctpassword?:
if clt.registered?(#nick)
if clt.correctpassword?(#nick, #pass)
sv_send 'NOTICE', #nick, ":Correct password."
else
sv_send 'NOTICE', #nick, ":Incorrect password."
end
end
I don't see either notices. Using correctpassword? seems to break things.
This works though:
if clt.registered?(#nick)
sv_send 'NOTICE', #nick, ":This account is registered."
end
There is no assignment for results var. Do results = db.execute("SELECT * FROM t1 WHERE user = ? and pass = ?", user, pass)
MD5 is a one-way hash encryption algorithm. There is no way to directly decrypt a MD5 hash. The algorithm itself uses modular arithmetic to package the serialized string and there is no way to go backwards from that.
So I think you should convert the password to MD5 which user try to enter and compare that hash with stored encrypted password in db.
Eg:
> stored_password_in_db = Digest::MD5.hexdigest('gagan')
#=> "cc18a19beff0bdf874861a4dae6124b6"
> user_enter_password_for_login = Digest::MD5.hexdigest('gagan')
#=> "cc18a19beff0bdf874861a4dae6124b6"
> stored_password_in_db == user_enter_password_for_login
#=> true
> user_enter_password_for_login = Digest::MD5.hexdigest('Gagan')
#=> "f52bb23033354697e8f55abdaed9d94f"
> stored_password_in_db == user_enter_password_for_login
#=> false

Reading/Writing password protected and encrypted file in ruby

I want to encrypt a file that a ruby program will be loading data from.
In addition, I need the program to prompt for a password on startup that will be used to decrypt the file.
In other words, the file needs to reside encrypted on the machine and only users with passwords will be able to run the app.
I have started to look at openpgp but as far as I understand, this does still not solve the password problem.
There's two easy ways to go about doing this. One is to shell out to openssl to do your encryption / decryption there. The arguably better way would be to use the Ruby Crypto gem.
Program to encrypt:
require 'rubygems'
require 'crypt/blowfish';
puts "Password? "
pw = gets
puts "Secret data? "
data = gets
blowfish = Crypt::Blowfish.new(pw)
r = StringIO.new(data);
File.open('data', 'w') do |f|
while l = r.read(8) do
while l.size < 8 do l += "\0" end
f.print blowfish.encrypt_block(l)
end
end
Program to decrypt:
require 'rubygems'
require 'crypt/blowfish';
puts "Password? "
pw = gets
blowfish = Crypt::Blowfish.new(pw)
r = StringIO.new();
File.open('data', 'r') do |f|
while l = f.read(8) do
r << blowfish.decrypt_block(l)
end
end
puts "Secret data:"
puts r.string
This example uses the Blowfish symmetric block cypher. Other cyphers could be used. Also, you would probably want to concatenate a fixed string to the password, to make the key longer and to help tie the encryption/decryption to your application.
Try the encrypted strings gem. Works like a charm.

Resources