Source

event_shipper / lib / event_shipper / filter / encrypt.rb

Full commit

require 'openssl'

require 'event_shipper/protocol'

module EventShipper::Filter
  class AES256
    def initialize password
      @password = password
      @salt = generate_salt

      @key = generate_key(password, @salt)
    end

    def generate_salt
      OpenSSL::Random.random_bytes(8)
    end

    def generate_key password, salt
      if [password, salt] == @last_key_seed
        return @last_key 
      end

      iterations = 10000
      key_length = 32

      @last_key_seed = [password, salt]
      @last_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
        password, salt, iterations, key_length)
    end

    def enc str
      cipher = OpenSSL::Cipher::AES256.new(:CBC)
      cipher.encrypt
      iv = cipher.random_iv
      cipher.key = @key

      ciphertext = cipher.update(str) + cipher.final

      [@salt, iv, ciphertext]
    end
    def dec salt, iv, str
      cipher = OpenSSL::Cipher::AES256.new(:CBC)
      cipher.decrypt
      cipher.iv = iv
      cipher.key = generate_key(@password, salt)

      cipher.update(str) + cipher.final
    end
  end

  # Takes a Protocol::Transmission object, encrypts it and turns it into 
  # something Decrypt can read from the wire. (a string)
  #
  class Encrypt
    include EventShipper::Protocol

    def initialize user, password
      @user = user
      @algo = AES256.new(password)
    end

    def en transmission
      salt, iv, ciphertext = @algo.enc(transmission)

      encrypted(
        iv: iv, 
        salt: salt, 
        user: @user, 
        ciphertext: ciphertext).serialize_to_string
    end
  end

  class Decrypt
    include EventShipper::Protocol
    
    def initialize &user_lookup
      @user_cache = Hash.new { |h, u|
        password = user_lookup.call(u)
        h[u] = password && AES256.new(password) }
    end

    def de encrypted_string, attributes={}
      message = parse_encrypted(encrypted_string)

      if algo = @user_cache[message.user]
        transmission = algo.dec(
          message.salt, 
          message.iv, 
          message.ciphertext)

        attributes[:encrypting_user] = message.user

        return transmission, attributes
      else
        warn "No such user '#{message.user}' in database; cannot decrypt."
        return nil, attributes
      end
    end
  end

end

if $0 == __FILE__
  enc = EventShipper::Filter::Encryption.new 'password'
  str = enc.enc 'A very secret text'
  puts str

  dec = EventShipper::Filter::Encryption.new 'password'
  puts dec.dec(str)
  exit

  require 'benchmark'
  puts "Doing it a 1000 times"
  msg = "foobar"*20
  puts Benchmark.measure { 
    e = EventShipper::Filter::Encrypt.new('test', 'password')
    d = EventShipper::Filter::Decrypt.new('test' => 'password')
    1000.times do d.call(e.call(msg)) end
  }
  
end