event_shipper / lib / event_shipper / filter / encrypt.rb

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] = 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
      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
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.