Reading/Writing password protected and encrypted file in ruby - 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.

Related

ruby: create MD5 checksum with salt?

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,

openSSL encrypt with CLI, decrypt with ruby not working

I'm using the openSSL CLI tool to encrypt data, and I want to decrypt that data on another machine in my ruby application. I'm unable to decrypt the data and I'm not sure what isn't lining up between the two. I'd really appreciate any help, below is what I'm running:
CLI Tool
$ export PASS=EncryptDecryptSuperSecretKeyNoOne123456789123456789
$ echo 'someTextIWantToEncrypt' | openssl enc -aes-256-ecb -base64 -e -k $PASS
:> U2FsdGVkX18Gboawkim6n6Ps/yssGaOZkdb1e6I4VyOZDUcqEh2uYdT8jxUydplX
Ruby Application
require 'openssl'
require 'base64'
module Aes
KEY = "EncryptDecryptSuperSecretKeyNoOne123456789123456789"
ALGORITHM = 'AES-256-ECB'
def self.decrypt(msg)
begin
cipher = OpenSSL::Cipher.new(ALGORITHM)
cipher.decrypt
cipher.key = KEY
tempkey = Base64.decode64(msg)
crypt = cipher.update(tempkey)
crypt << cipher.final
return crypt
rescue Exception => exc
puts ("Message for the decryption log file for message #{msg} = #{exc.message}")
end
end
end
When I call Aes.decrypt("U2FsdGVkX18Gboawkim6n6Ps/yssGaOZkdb1e6I4VyOZDUcqEh2uYdT8jxUydplX") it raises an error bad decrypt
What am I missing or not including? ECB does not require an IV. Is there something else?

How to Generate a Bitcoin Address in Ruby

I'm trying to generate Bitcoin addresses in ruby by using this guide:
https://bhelx.simst.im/articles/generating-bitcoin-keys-from-scratch-with-ruby/
But something isn't quite right, because the addresses being generated aren't coming out quite right.
Here's the class I'm using:
require 'openssl'
require 'ecdsa'
require 'securerandom'
require 'base58'
class BitcoinAddressGenerator
ADDRESS_VERSION = '00'
def self.generate_address
# Bitcoin uses the secp256k1 curve
curve = OpenSSL::PKey::EC.new('secp256k1')
# Now we generate the public and private key together
curve.generate_key
private_key_hex = curve.private_key.to_s(16)
puts "private_key_hex: #{private_key_hex}"
public_key_hex = curve.public_key.to_bn.to_s(16)
puts "public_key_hex: #{public_key_hex}"
pub_key_hash = public_key_hash(public_key_hex)
puts "pub_key_hash: #{pub_key_hash}"
address = generate_address_from_public_key_hash(public_key_hash(public_key_hex))
puts "address: #{address}"
end
def self.generate_address_from_public_key_hash(pub_key_hash)
pk = ADDRESS_VERSION + pub_key_hash
encode_base58(pub_key_hash + checksum(pub_key_hash))
end
def self.int_to_base58(int_val, leading_zero_bytes=0)
alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
base58_val, base = '', alpha.size
while int_val > 0
int_val, remainder = int_val.divmod(base)
base58_val = alpha[remainder] + base58_val
end
base58_val
end
def self.encode_base58(hex)
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
("1"*leading_zero_bytes) + int_to_base58( hex.to_i(16) )
end
def self.checksum(hex)
sha256(sha256(hex))[0...8]
end
# RIPEMD-160 (160 bit) hash
def self.rmd160(hex)
Digest::RMD160.hexdigest([hex].pack("H*"))
end
def self.sha256(hex)
Digest::SHA256.hexdigest([hex].pack("H*"))
end
# Turns public key into the 160 bit public key hash
def self.public_key_hash(hex)
rmd160(sha256(hex))
end
end
It outputs something like:
private_key_hex: C96DE079BAE4877E086288DEDD6F9F70B671862B7E6E4FC0EC401CADB81EDF45
public_key_hex: 0422435DF80F62E643D3CFBA66194052EC9ED0DFB47A1B26A4731079A5FF84FBF98FF0A540B6981D75BA789E6192F3B38BABEF6B0286CAEB4CAFCB51BB96D97B46
public_key_hash: db34927cc5ec0066411f366d9a95f9c6369c6e1d
address: Lz3xnxx6Uh79PEzPpWSMMZJVWR36hJgVL
If I plug this address into blockchain.info and similar tools it says that it's an invalid address.
Any help would be greatly appreciated.
In your generate_address_from_public_key_hash method, the checksum should be over the hash including the address prefix. You’re not actually using the pk variable at all at the moment after you assign it. The code should look something like:
def self.generate_address_from_public_key_hash(pub_key_hash)
pk = ADDRESS_VERSION + pub_key_hash
encode_base58(pk + checksum(pk)) # Using pk here, not pub_key_hash
end
The mistake seems to also be on the page you link to, I guess the author must have made a copy/paste error.
As an aside, keeping everything in hex strings and decoding back and forth seems an odd way of doing this. I would have thought it would be easier to use raw binary strings, and only encode to hex when printing out values.

Having trouble saving to file in Ruby

Hi I have a simple form that allows a user to input a name, their gender and a password. I use Digest::MD5.hexdigest to encrypt the input. Once I have the encrypted input eg, d1c261ede46c1c66b7e873564291ebdc, I want to be able to append this to a file I have already created. However every thing I have tried just isn't working. Can anyone please help and thank you in advance. Here is what I have:
input = STDIN.read( ENV["CONTENT_LENGHT"] )
puts "Content-type: text/html \n\n"
require 'digest/md5'
digest = Digest::MD5.hexdigest(input)
f = File.open("register.txt", "a")
f.write(digest)
f.close
I have also tried this with no luck:
File.open("register.txt", "a") do |f|
f.puts(digest)
end
If the code is verbatim then I think you have a typo in the first line: did you mean CONTENT_LENGHT or is it a typo? ENV[] will return a string if the variable is set, which will upset STDIN#read. I get TypeError: can't convert String into Integer. Assuming the typo, then ENV[] returns nil, which tells STDIN#read to read until EOF, which from the console means, I think, Control-Z. That might be causing a problem.
I suggest you investigate by modifying your script thus:
read_length = ENV["CONTENT_LENGTH"].to_i # assumed typo fixed, convert to integer
puts "read length = #{read_length}"
input = STDIN.read( read_length )
puts "input = #{input}"
puts "Content-type: text/html \n\n" # this seems to serve no purpose
require 'digest/md5'
digest = Digest::MD5.hexdigest(input)
puts "digest = #{digest}"
# prefer this version: it's more idiomatically "Rubyish"
File.open("register.txt", "a") do |f|
puts "file opened"
f.puts(digest)
end
file_content = File.read("register.txt")
puts "done, file content = #{file_content}"
This works on my machine, with the following output (when CONTENT_LENGTH set to 12):
read length = 12
abcdefghijkl
input = abcdefghijkl
Content-type: text/html
digest = 9fc9d606912030dca86582ed62595cf7
file opened
done, file content = 6cfbc6ae37c91b4faf7310fbc2b7d5e8
e271dc47fa80ddc9e6590042ad9ed2b7
b0fb8772912c4ac0f13525409c2b224e
9fc9d606912030dca86582ed62595cf7

Moving a file referenced in an array in ruby

I'm a bit confused. I'm writing a program to check the md5 checksum of a series of files. That part works great. I thought it'd be cool though to move those files to a duplicate folder for easy reference/removal. The issue is it keeps failing, it says no such file or directory, and I'm not sure if I'm even trying to move this file correctly. if someone wouldn't mind taking a look I'd be appreciative. Thanks in advance.
!/usr/local/bin/ruby
require 'find'
require 'digest/md5'
require 'fileutils'
testArray = Dir["**/**/*"] #create an array based off the contents of the current directory
def checksum(file) #method for calculating the checksum
sumArray = Array.new
dupArray = Array.new
file.each do |file| #Iterate over each entry in the array
digest = Digest::MD5.hexdigest(File.read(file)) # create a MD5 checksum for the file and storeit in the variable digest
dupArray << file if sumArray.include?(digest) # check to see if the item exists in the sumarray already, if not ad to duparray
sumArray << digest unless sumArray.include?(digest) # if it's not already in sumarray, add it in there
end
dupArray
end
this is where my problems start ;)
def duplicateDirectory(file)
file.each do |file|
FileUtils.mv('file', '/duplicate') if Dir.exists?('duplicate')
Dir.mkdir('duplicate')
FileUtils.mv('file', '/duplicate')
end
end
sumTest = checksum(testArray) #pass the test array along to the method written
puts sumTest
duplicateDirectory(sumTest)
You should remove '' around file and remove / in front of duplicate. And it's better to rewrite like this:
dir = 'duplicate'
Dir.mkdir(dir) if !Dir.exists?(dir)
FileUtils.mv(file, dir)

Resources