Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 55 additions & 18 deletions lib/aes/aes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ class << self
def encrypt(plain_text, key, opts={})
::AES::AES.new(key, opts).encrypt(plain_text)
end

# Encrypts an file
def encrypt_file(filepath, enc_filepath, key, opts={})
::AES::AES.new(key, opts).encrypt_file(filepath, enc_filepath)
end

# Decrypts the cipher_text with the provided key
def decrypt(cipher_text, key, opts={})
::AES::AES.new(key, opts).decrypt(cipher_text)
end

# Decrypts an file
def decrypt_file(filepath, enc_filepath, key, opts={})
::AES::AES.new(key, opts).decrypt_file(filepath, enc_filepath)
end

# Generates a random key of the specified length in bits
# Default format is :plain
def key(length=256,format=:plain)
Expand All @@ -19,7 +31,7 @@ def key(length=256,format=:plain)
key
end
end
# Generates a random iv
# Generates a random iv
# Default format is :plain
def iv(format=:plain)
iv = ::AES::AES.new("").random_iv
Expand All @@ -28,26 +40,29 @@ def iv(format=:plain)
Base64.encode64(iv).chomp
else
iv
end
end
end
end

class AES
attr :options
attr :key
attr :iv
attr :cipher
attr :cipher_text
attr :plain_text


ENCRYPT_CHUNK_SIZE = 2048
DECRYPT_CHUNK_SIZE = 2822 #it uses base64 on the encrypted chunk. this is the size after the base64 encoding.

def initialize(key, opts={})
merge_options opts
@cipher = nil
@key = key
@iv ||= random_iv
self
end

# Encrypts
def encrypt(plain_text)
@plain_text = plain_text
Expand All @@ -62,7 +77,18 @@ def encrypt(plain_text)
@cipher_text
end

# Decrypts
def encrypt_file(filepath, encrypted_filepath)
enc_file = File.open(encrypted_filepath, 'wb')
File.open(filepath, 'rb') do |f|
until f.eof
chunk = f.read(ENCRYPT_CHUNK_SIZE)
enc_file.write(encrypt(chunk))
end
end
enc_file.close
end

# Decrypts
def decrypt(cipher_text)
@cipher_text = cipher_text
_setup(:decrypt)
Expand All @@ -73,32 +99,43 @@ def decrypt(cipher_text)
ctext = @cipher_text
end
@cipher.iv = ctext[0]
@plain_text = @cipher.update(ctext[1]) + @cipher.final
@plain_text = @cipher.update(ctext[1]) + @cipher.final
end

def decrypt_file(encrypted_filepath, filepath)
file = File.open(filepath, 'wb')
File.open(encrypted_filepath, 'rb') do |f|
until f.eof
chunk = f.read(DECRYPT_CHUNK_SIZE)
file.write(decrypt(chunk))
end
end
file.close
end

# Generate a random initialization vector
def random_iv
_setup(:encrypt)
@cipher.random_iv
end

# Generate a random key
def random_key(length=256)
_random_seed.unpack('H*')[0][0..((length/8)-1)]
end

private

# Generates a random seed value
def _random_seed(size=32)
if defined? OpenSSL::Random
return OpenSSL::Random.random_bytes(size)
else
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
(1..size).collect{|a| chars[rand(chars.size)] }.join
(1..size).collect{|a| chars[rand(chars.size)] }.join
end
end

# Un-Base64's the IV and CipherText
# Returns an array containing the IV, and CipherText
def b64_d(data)
Expand All @@ -108,12 +145,12 @@ def b64_d(data)
end
iv_and_ctext
end

# Base64 Encodes a string
def b64_e(data)
Base64.encode64(data).chomp
end

# Encrypts @plain_text
def _encrypt
@cipher.update(@plain_text) + @cipher.final
Expand All @@ -128,7 +165,7 @@ def merge_options(opts)
}.merge! opts
_handle_iv
end

def _handle_iv
@iv = @options[:iv]
return if @iv.nil?
Expand All @@ -138,13 +175,13 @@ def _handle_iv
@iv = Base64.decode64(@options[:iv])
end
end

# Create a new cipher using the cipher type specified
def _setup(action)
@cipher ||= OpenSSL::Cipher::Cipher.new(@options[:cipher])
@cipher ||= OpenSSL::Cipher::Cipher.new(@options[:cipher])
# Toggles encryption mode
@cipher.send(action)
@cipher.key = @key.unpack('a2'*32).map{|x| x.hex}.pack('c'*32)
end
end
end
end
30 changes: 26 additions & 4 deletions test/test_aes.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'helper'

class TestAES < Test::Unit::TestCase

should "encrypt and decrypt a string" do
key = "01234567890123456789012345678901"
msg = "This is a message that nobody should ever see"
Expand All @@ -10,7 +10,7 @@ class TestAES < Test::Unit::TestCase
enc = AES.encrypt(msg, key, {:format => :plain})
assert_equal msg, AES.decrypt(enc, key, {:format => :plain})
end

should "produce the same encrypted string when provided an identical key and iv" do
key = "01234567890123456789012345678901"
msg = "This is a message that nobody should ever see"
Expand All @@ -19,10 +19,32 @@ class TestAES < Test::Unit::TestCase
enc2 = AES.encrypt(msg, key, {:iv => iv})
assert_equal enc1, enc2
end

should "generate a new key when AES#key" do
assert_equal 32, AES.key.length
assert_equal 44, AES.key(256, :base_64).length
end


should "encrypt and decrypt a file" do
key = "01234567890123456789012345678901"
msg = "This is a message that nobody should ever see"
File.open('message.txt', 'w') { |f| f.write(msg) }

AES.encrypt_file('message.txt', 'encrypted.txt', key)
assert_not_equal(File.read('message.txt'), File.read('encrypted.txt'))

AES.decrypt_file('encrypted.txt', 'decrypted.txt', key)
assert_equal(File.read('message.txt'), File.read('decrypted.txt'))

end

def teardown
files = ['message.txt', 'encrypted.txt', 'decrypted.txt']
files.each do |file|
if File.exists?(file)
File.delete(file)
end
end
end

end