Simple Encryption in Ruby without external gems
The solution is kind of from scratch but based on this: https://math.stackexchange.com/questions/9508/looking-for-a-bijective-discrete-function-that-behaves-as-chaotically-as-possib
The simplest way presented is using a * x + b (mod 2^n)
Obviously this is no real encryption and really only useful if you want to create sequential coupon codes without using much code.
So to implement this, you first need to pick a, b and n. (a must be odd) For example a=17
, b=37
and n=27
. Also we need to find "a^(-1)
" on "mod 2^n". It's possible to do this on https://www.wolframalpha.com using the ExtendedGcd function:
So the inverse of a
is therefore 15790321
. Putting all this together:
A=17
B=37
A_INV=15790321
def encrypt(x)
(A*x+B)%(2**27)
end
def decrypt(y)
((y-B)*A_INV)%(2**27)
end
And now you can do:
irb(main):038:0> encrypt(4)
=> 105
irb(main):039:0> decrypt(105)
=> 4
Obviously we want the coupon codes to look cool. So 2 extra things are needed: start the sequence at 4000 or so, so the codes are longer. Also convert them into something alpha-numeric, that's also an easy one with Ruby:
irb(main):050:0> decrypt("1ghx".to_i(36))
=> 4000
irb(main):051:0> encrypt(4000).to_s(36)
=> "1ghx"
One nice additional property is that consecutive numbers are different enough that guessing is practically impossible. Of course we assume that the users are not crypto analysts and if someone indeed guesses a valid number, it's not the end of the world: :-)
irb(main):053:0> encrypt(4001).to_s(36)
=> "1gie"
irb(main):054:0> decrypt("1gie".to_i(36))
=> 4001
Let's try to naively "hack" it by counting from 1gie
to 1gif
:
irb(main):059:0* decrypt("1gif".to_i(36))
=> 15794322
That's completely out of range, there are just 2000 or so coupons anyways - not a million. :-) Also if I remember correctly one can experiment a bit with the parameters, so subsequent numbers look more chaotic.
(Pick a larger n
for longer codes and vice-versa. Base 36
means 6
bits are needed for each character ("Math.log(36, 2)
"). So n=27
allows for up to 5 characters.)
native ruby methods for compressing/encrypt strings?
From http://ruby-doc.org/stdlib/libdoc/zlib/rdoc/classes/Zlib.html
# aka compress
def deflate(string, level)
z = Zlib::Deflate.new(level)
dst = z.deflate(string, Zlib::FINISH)
z.close
dst
end
# aka decompress
def inflate(string)
zstream = Zlib::Inflate.new
buf = zstream.inflate(string)
zstream.finish
zstream.close
buf
end
Encryption from http://snippets.dzone.com/posts/show/991
require 'openssl'
require 'digest/sha1'
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
c.encrypt
# your pass is what is used to encrypt/decrypt
c.key = key = Digest::SHA1.hexdigest("yourpass")
c.iv = iv = c.random_iv
e = c.update("crypt this")
e << c.final
puts "encrypted: #{e}\n"
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
c.decrypt
c.key = key
c.iv = iv
d = c.update(e)
d << c.final
puts "decrypted: #{d}\n"
Ruby - encrypted_strings
With a symmetric encryption scheme, you only need the same password for encryption and decryption. And from the looks of it, the password is stored in an instance variable on the encrypted string:
>> secret = '123'
=> "123"
>> crypted = secret.encrypt(:symmetric, :password => "password")
=> "R5RVA511Nzw=\n"
>> crypted.instance_variables
=> ["@cipher"]
>> crypted.instance_variable_get("@cipher")
=> #<EncryptedStrings::SymmetricCipher:0x101192768 @password="password", @algorithm="DES-EDE3-CBC">
So yes, if you store the crypted
object as above, you'll be storing the password as well. The goal then is to store only the string content of crypted
without its instance variables. I thought that crypted.to_s
or String(crypted)
would accomplish this, but neither do. As a workaround, you can string interpolate it, explicitly pass it to String#new
, or explicitly remove the instance variable:
>> "#{crypted}".instance_variables
=> []
>> String.new(crypted).instance_variables
=> []
>> crypted.send :remove_instance_variable, :@cipher
=> #<EncryptedStrings::SymmetricCipher:0x101192768 @password="password", @algorithm="DES-EDE3-CBC">
>> crypted.instance_variables
=> []
Once you have only the string content, you can decrypt it with the password at a later point:
>> "R5RVA511Nzw=\n".decrypt(:symmetric, :password => "password")
=> "123"
How to encrypt files with Ruby?
Ruby's OpenSSL is a thin wrapper around OpenSSL itself and provides almost all the functionality that OpenSSL itself does, so yes, there's a one-to-one mapping for all your examples:
openssl rand -base64 2048 > secret_key
That's actually exaggerated, you are using AES-256, so you only need a 256 bit key, you are not using RSA here. Ruby OpenSSL takes this decision off your shoulders, it will automatically determine the correct key size given the algorithm you want to use.
You are also making the mistake of using a deterministic IV during your encryption. Why? Because you don't specify an IV at all, OpenSSL itself will default to an IV of all zero bytes. That is not a good thing, so I'll show you the correct way to do it, for more information have a look at the Cipher documentation.
require 'openssl'
# encryption
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
buf = ""
File.open("file.enc", "wb") do |outf|
File.open("file", "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
# decryption
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = key
cipher.iv = iv # key and iv are the ones from above
buf = ""
File.open("file.dec", "wb") do |outf|
File.open("file.enc", "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
As you can see, encryption and decryption are fairly similar, so you can probably combine the streaming reading/writing into one shared method and just pass it a properly configured Cipher
plus the corresponding file names, I just stated them explicitly for the sake of clarity.
If you'd like to Base64-encode the key (and probably the IV, too), you can use the Base64 module:
base64_key = Base64.encode64(key)
Devise: manually encrypt password and store directly
Good news and bad news.
Good news:
The following works to create your user's password manually.
pepper = nil
cost = 10
encrypted_password = ::BCrypt::Password.create("#{password}#{pepper}", :cost => cost).to_s
You can find your pepper and cost in your devise initializer. This method was confirmed using Devise's "valid_password?" method.
Bad news:
The entire reason I was trying to avoid "User.new(password: password).encrypted_password" was because of speed. It's terribly slow. With all my other pieces of my import task, I've intentionally avoided this.
But as it turns out, the major cost here is not instantiating a User object -- but BCrypt itself. There is very little noticeable speed boost when using BCrypt directly because it's intentionally designed to be slow.
My final answer: suck it up, run the rake script, go find a beverage.
Related Topics
How to Have Multiple Versions of Ruby and Rails, and Their Combinations on Windows
Differencebetween Class and Klass in Ruby
Ruby on Rails Plural (Controller) and Singular (Model) Convention - Explanation
Ruby on Rails: How to Run Things in the Background
Removing All Empty Elements from a Hash/Yaml
How to Get Haml to Work with Rails
In Ruby's Test::Unit::Testcase, How to Override the Initialize Method
How to Pass a Parameter for Gem Installation When I Run Bundle Install
How to Get Readline Support in Irb Using Rvm on Ubuntu 11.10
Ruby on Rails: Alias_Method_Chain, What Exactly Does It Do
Rails Activesupport Time Parsing
Difference Between Each.With_Index and Each_With_Index in Ruby
Active Record - Find Records Which Were Created_At Before Today
Parsing String to Add to Url-Encoded Url
Rails 4 Unpermitted Parameters for Array