I'm making a new application that will be public facing but I don't want any clear text being stored on the server for any user at anytime. This way, there will be no compromising of data. In this case, username and password credentials. This isn't an application where a user will login with their creds, if that were the case I would have used Devise. Instead, I'm taking these username and password credentials and piping them in to the application to talk to an outside service.
The first part of obscurity was making the Rails model create new entries based on UUID instead of sequential numbers. That was pretty easy by following this tutorial How to start using UUID in ActiveRecord with PostgreSQL. The second part was implementing the attr_encrypted gem. attr_encrypted allows you to store data in encrypted format to your ActiveRecord database. In this case, i'm using Postgres.
The README is a start but doesn't get you all the way there. So here is how it's done.
Start off by creating a new scaffold. You must specify encrypted_ before anything you want encrypted in the model
rails g scaffold Model encrypted_user:string encrypted_password:string host:string
Here is the migration file:
class CreateModels < ActiveRecord::Migration def change create_table :models do |t| t.string :encrypted_user t.string :encrypted_password t.string :host t.timestamps end end end
perform rake:db migrate
rake db:migrate == 20150111200010 CreateModels: migrating ===================================== -- create_table(:models) -> 0.0112s == 20150111200010 CreateModels: migrated (0.0114s) ============================
Open up the model.rb file and add the attr_encrypted attributes for anything you are wanting to encrypt. attr_encrypted_options.merge! sets paramters for all attr_encrypted lines. encode is set to true on ActiveRecord by default, but I set it anyway. In this example, I'm using the Figaro gem to keep things even more secure because the 256-bit keys I'm using are stored as Environment Variables and will not be accessible in github.
class Model < ActiveRecord::Base attr_encrypted_options.merge!(:encode => true) attr_encrypted :user, :key => ENV["USERKEY"] attr_encrypted :password, :key => ENV["PASSWORDKEY"] end
We need to change our controller next. At the very bottom of a default scaffolding is the modelname_params method. We need to remove encrypted_ from each of those
### BEFORE def model_params params.require(:model).permit(:encrypted_user, :encrypted_password, :host) end ### AFTER def model_params params.require(:model).permit(:user, :password, :host) end
Finally, Let's change our _form. The submitted text_field value now needs to match the params specified above:
<%= form_for(@model) do |f| %> <% if @model.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@model.errors.count, "error") %> prohibited this model from being saved:</h2> <ul> <% @model.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :encrypted_user %><br> <%= f.text_field :user %> </div> <div class="field"> <%= f.label :encrypted_password %><br> <%= f.text_field :password %> </div> <div class="field"> <%= f.label :host %><br> <%= f.text_field :host %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
Now we have additional methods available to us. If we do Model.username then we will see the unencrypted username. If we specify Model.encrypted_username then it will show the encrypted version. This can be verified by looking at the show page and adding in the additional methods.
<p id="notice"><%= notice %></p> <p> <strong>Encrypted user:</strong> <%= @model.user %><br> <%= @model.encrypted_user %> </p> <p> <strong>Encrypted password:</strong> <%= @model.password %><br> <%= @model.encrypted_password %> </p> <p> <strong>Host:</strong> <%= @model.host %> </p> <%= link_to 'Edit', edit_model_path(@model) %> | <%= link_to 'Back', models_path %>
And here is a resulting screenshot
This can also be verified in the rails console if you bring up a record or create a new one:
2.1.2 :003 > @user = Model.new => #<Model id: nil, encrypted_user: nil, encrypted_password: nil, host: nil, created_at: nil, updated_at: nil> 2.1.2 :004 > @user.user = "kenny" => "kenny" 2.1.2 :005 > @user.save (0.4ms) BEGIN SQL (2.1ms) INSERT INTO "models" ("created_at", "encrypted_user", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["created_at", "2015-01-11 20:11:27.987417"], ["encrypted_user", "t+vBFbEvvy9hju1Crl10KQ==\n"], ["updated_at", "2015-01-11 20:11:27.987417"]] (1.6ms) COMMIT => true 2.1.2 :006 > exit