Client Side Encryption

Episode #63 by Teacher's Avatar David Kimura

Summary

Add an extra level of security to your application with client side encryption. Even over an SSL connection, there are attacks which could expose your users' sensitive information. Using JSEncrypt, learn how to encrypt on the client side and decrypt on the server side.
rails security javascript encryption 10:31

Resources

Summary

Copy https://github.com/travist/jsencrypt/blob/master/bin/jsencrypt.js to your vendor/assets/javascripts folder

# Terminal
openssl genrsa -out rsa_1024_priv.pem 1024
openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem

You will want to avoid storing the public and private keys in application.rb. Instead, you should have them in your secrets.yml, environment variable, parameter store or elsewhere secure.

# application.rb
PUBLIC_KEY = <PUBLIC KEY DATA>
PRIVATE_KEY = <PUBLIC KEY DATA>

# application.js
//= require jsencrypt

# application.js
$(document).on('turbolinks:load', function(){
  $('form').submit(function( event ) {
    var encrypt = new JSEncrypt();
    $('[data-encrypt]').each(function(){
      unencrypted = $(this);
      encrypt.setKey($('#public_key').val());
      encrypted = encrypt.encrypt(unencrypted.val());
      if (encrypted != false) {
        unencrypted.val(encrypted);
      }  
    })
    // event.preventDefault();
  });
});

# users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
# before_action :configure_sign_in_params, only: [:create]

  # POST /resource/sign_in
  def create
    # self.resource = warden.authenticate!(auth_options)
    resource = User.find_by(email: params[:user][:email])
    if resource&.valid_password?(authenticate_encryptor)
      set_flash_message!(:notice, :signed_in)
      sign_in(resource_name, resource)
      yield resource if block_given?
      respond_with resource, location: after_sign_in_path_for(resource)
    else
      redirect_to new_user_session_path, alert: 'Bad email or password.'
    end
  end

  private

  def authenticate_encryptor
    private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
    password = params[:user][:password]
    private_key.private_decrypt(Base64.decode64(password))
  end
end

# views/devise/sessions/new.html.erb
<%= f.password_field :password, autocomplete: "off", placeholder: 'Password', class: 'form-control', data: { encrypt: true } %>