Simple Encryption in Ruby Without External Gems

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:

Sample Image

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



Leave a reply



Submit