column_cryptor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +51 -0
  3. data/Rakefile +30 -0
  4. data/lib/column_cryptor/core_ext.rb +1 -0
  5. data/lib/column_cryptor/decryptor.rb +48 -0
  6. data/lib/column_cryptor/encryptor.rb +43 -0
  7. data/lib/column_cryptor/version.rb +4 -0
  8. data/lib/column_cryptor.rb +87 -0
  9. data/lib/generators/column_cryptor/install/install_generator.rb +18 -0
  10. data/lib/generators/column_cryptor/install/templates/initializer.rb.erb +1 -0
  11. data/test/column_cryptor/decryptor_test.rb +31 -0
  12. data/test/column_cryptor/encryptor_test.rb +35 -0
  13. data/test/column_cryptor_test.rb +57 -0
  14. data/test/dummy/README.rdoc +261 -0
  15. data/test/dummy/Rakefile +7 -0
  16. data/test/dummy/app/assets/javascripts/application.js +15 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  18. data/test/dummy/app/controllers/application_controller.rb +3 -0
  19. data/test/dummy/app/helpers/application_helper.rb +2 -0
  20. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/test/dummy/config/application.rb +59 -0
  22. data/test/dummy/config/boot.rb +10 -0
  23. data/test/dummy/config/database.yml +25 -0
  24. data/test/dummy/config/environment.rb +5 -0
  25. data/test/dummy/config/environments/development.rb +37 -0
  26. data/test/dummy/config/environments/production.rb +67 -0
  27. data/test/dummy/config/environments/test.rb +37 -0
  28. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  29. data/test/dummy/config/initializers/inflections.rb +15 -0
  30. data/test/dummy/config/initializers/mime_types.rb +5 -0
  31. data/test/dummy/config/initializers/secret_token.rb +7 -0
  32. data/test/dummy/config/initializers/session_store.rb +8 -0
  33. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  34. data/test/dummy/config/locales/en.yml +5 -0
  35. data/test/dummy/config/routes.rb +58 -0
  36. data/test/dummy/config.ru +4 -0
  37. data/test/dummy/log/test.log +0 -0
  38. data/test/dummy/public/404.html +26 -0
  39. data/test/dummy/public/422.html +26 -0
  40. data/test/dummy/public/500.html +25 -0
  41. data/test/dummy/public/favicon.ico +0 -0
  42. data/test/dummy/script/rails +6 -0
  43. data/test/shoulda_macros/encrypts.rb +37 -0
  44. data/test/test_helper.rb +20 -0
  45. metadata +221 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 SCVNGR, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # column_cryptor
2
+
3
+ `column_cryptor` is a gem that makes it easy to encrypt/decrypt ActiveRecord columns using a private key. If you need to store some sensitive information in your database, then this makes it easy to keep it encrypted.
4
+
5
+ Note that `column_cryptor` is NOT a good solution for something like credit card information. Do everyone a favor and use a vault-like service. Instead, use `column_cryptor` to encrypt a user's phone number or other one-off bits of information you'd like to keep secret.
6
+
7
+ ## Getting Started
8
+
9
+ Add to your Gemfile:
10
+
11
+ gem 'column_cryptor'
12
+
13
+ then run `bundle install`.
14
+
15
+ Next, use the generator to create an initializer with a random private key:
16
+
17
+ rails generate column_cryptor:install
18
+
19
+ This will create a file called `config/initializers/column_cryptor.rb` that looks something like:
20
+
21
+ ColumnCryptor.private_key = "ssWII/MrFX8EmHMjG/5+un0mnYF5UeG2k7ajSjaKayU=\n"
22
+
23
+ Those random characters represent a Base64-encoded private key suitable for encrypting and decrypting data using `column_cryptor`. It's recommended that you move that private key somewhere outside your code, such as to a yaml file or as an environment variable. Just be sure to set `ColumnCryptor.private_key` to your key.
24
+
25
+ ## Encrypting some data
26
+
27
+ Once installed, you can then encrypt an ActiveRecord column like so:
28
+
29
+ class User < ActiveRecord::Base
30
+ encrypts :phone_number
31
+ end
32
+
33
+ Getters and setters will be created for each column, automatically encrypting/decrypting `phone_number`.
34
+
35
+ ## Generating a new private key
36
+
37
+ You can create a new private key using the `new_key` method:
38
+
39
+ ColumnCryptor.new_key
40
+
41
+ This will return a Base64-encoded string representing a random private key. Be sure to leave it as-is (with the new-line at the end!) or ColumnCryptor won't know what do with it.
42
+
43
+ Note that once you've started using a private key, if you ever lose it, all of your encrypted data will be lost with it. You also can't change your private key on the fly: you would need to first decrypt all of your data and then re-encrypt with your new private key.
44
+
45
+ ## Requirements
46
+
47
+ `column_cryptor` requires Ruby 1.9+, and Rails 3.0 or later. The tests are written with Test::Unit and shoulda.
48
+
49
+ ## License
50
+
51
+ `column_cryptor` is written by Ryan Twomey and Costa Walcott, and is Copyright 2012 SCVNGR, Inc. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ begin
7
+ require 'rdoc/task'
8
+ rescue LoadError
9
+ require 'rdoc/rdoc'
10
+ require 'rake/rdoctask'
11
+ RDoc::Task = Rake::RDocTask
12
+ end
13
+
14
+ RDoc::Task.new(:rdoc) do |rdoc|
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = 'ColumnCryptor'
17
+ rdoc.options << '--line-numbers'
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
20
+
21
+ require 'rake/testtask'
22
+
23
+ desc 'Test the paperclip plugin.'
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib' << 'profile'
26
+ t.pattern = 'test/**/*_test.rb'
27
+ t.verbose = true
28
+ end
29
+
30
+ task default: :test
@@ -0,0 +1 @@
1
+ ActiveRecord::Base.send :include, ColumnCryptor
@@ -0,0 +1,48 @@
1
+ module ColumnCryptor
2
+ # Given some ciphertext, attempts to decrypt it. Requires that ColumnCryptor.private_key be
3
+ # previously set to the same private key used to create the ciphertext.
4
+
5
+ class Decryptor
6
+ # Returns a new ColumnCryptor::Decryptor object initialized with the given ciphertext.
7
+ def initialize(encrypted_data)
8
+ @encrypted_data = encrypted_data
9
+ end
10
+
11
+ # Returns decrypted version of the Base64-encoded ciphertext.
12
+ def decrypt
13
+ unless @encrypted_data.blank?
14
+ plaintext_data
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def decipher
21
+ @decipher ||= OpenSSL::Cipher::AES.new(256, :CBC).tap do |decipher|
22
+ decipher.decrypt
23
+ decipher.key = private_key
24
+ end
25
+ end
26
+
27
+ def deciphertext
28
+ decipher.update(decoded_data) + decipher.final
29
+ end
30
+
31
+ def decoded_data
32
+ @decoded_data ||= Base64.decode64(@encrypted_data)
33
+ end
34
+
35
+ def initialization_vector
36
+ decoded_data.slice! 0, decipher.block_size
37
+ end
38
+
39
+ def plaintext_data
40
+ decipher.iv = initialization_vector
41
+ deciphertext
42
+ end
43
+
44
+ def private_key
45
+ Base64.decode64 ColumnCryptor.private_key
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ module ColumnCryptor
2
+ # Given some plaintext, attempts to encrypt it. Requires that ColumnCryptor.private_key be set.
3
+
4
+ class Encryptor
5
+ # Returns a new ColumnCryptor::Encryptor object initialized with the given plaintext.
6
+ def initialize(plaintext)
7
+ @plaintext = plaintext
8
+ end
9
+
10
+ # Returns a Base64-encoded string of the encrypted (ciphertext) version of the plaintext.
11
+ def encrypt
12
+ unless @plaintext.blank?
13
+ Base64.encode64 encrypted_data
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def cipher
20
+ @cipher ||= OpenSSL::Cipher::AES.new(256, :CBC).tap do |cipher|
21
+ cipher.encrypt
22
+ cipher.key = private_key
23
+ end
24
+ end
25
+
26
+ def ciphertext
27
+ cipher.update(@plaintext) + cipher.final
28
+ end
29
+
30
+ def encrypted_data
31
+ cipher.iv = initialization_vector
32
+ initialization_vector + ciphertext
33
+ end
34
+
35
+ def initialization_vector
36
+ @initial_vector ||= cipher.random_iv
37
+ end
38
+
39
+ def private_key
40
+ Base64.decode64 ColumnCryptor.private_key
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ module ColumnCryptor
2
+ #:nodoc:
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,87 @@
1
+ # This gem makes it easy to encrypt and decrypt sensitive application data using ActiveRecord. It
2
+ # adds a getter and setter for each encryptable column, transparently encrypting before writing to
3
+ # the database, and decrypting when reading.
4
+ #
5
+ # Author:: Ryan Twomey (mailto:ryant@scvngr.com)
6
+ # Copyright:: Copyright (c) 2012 SCVNGR, Inc.
7
+ # License:: MIT
8
+
9
+ # Sets up ColumnCryptor and includes it on all ActiveRecord::Base objects.
10
+
11
+ require 'active_support/core_ext/string'
12
+ require 'base64'
13
+ require 'openssl'
14
+
15
+ require 'column_cryptor/decryptor'
16
+ require 'column_cryptor/encryptor'
17
+ require 'column_cryptor/version'
18
+
19
+ module ColumnCryptor
20
+ @@private_key = nil
21
+
22
+ #:nodoc:
23
+ def self.included(base)
24
+ base.extend ClassMethods
25
+ end
26
+
27
+ # Returns a randomly generated key, Base64 encoded, suitable for sending to
28
+ # ColumnCryptor.private_key=
29
+ def self.new_key
30
+ Base64.encode64 cipher.random_key
31
+ end
32
+
33
+ # Sets the globally-used private key for all encryption/decryption operations.
34
+ def self.private_key=(private_key)
35
+ @@private_key = private_key
36
+ end
37
+
38
+ # Returns the currently set private_key, or nil.
39
+ def self.private_key
40
+ @@private_key
41
+ end
42
+
43
+ module ClassMethods
44
+ # Define which columns to mark as being encrypted. For example, if your ActiveRecord model has
45
+ # two columns you'd like to encrypt named "address" and "phone_number", you would call this as:
46
+ #
47
+ # encrypts :address, :phone_number
48
+ #
49
+ # This will define two methods for each attribute: a getter (i.e. "address") and a setter
50
+ # (i.e. "address="). The getter will automatically attempt to decrypt the data, while the
51
+ # setter will attempt to encrypt the data. When ActiveRecord writes your record to the database,
52
+ # the values of these columns will be the ciphertext.
53
+ #
54
+ # Note that you must set ColumnCryptor.private_key prior to using these setters/getters.
55
+ def encrypts(*args)
56
+ if args.respond_to?(:each)
57
+ args.each do |attribute|
58
+ define_encryption_methods attribute
59
+ end
60
+ else
61
+ define_encryption_methods args
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def define_encryption_methods(attribute)
68
+ define_method attribute do
69
+ encrypted_data = send("#{attribute}_encrypted")
70
+ ColumnCryptor::Decryptor.new(encrypted_data).decrypt
71
+ end
72
+
73
+ define_method "#{attribute}=" do |new_value|
74
+ encrypted_data = ColumnCryptor::Encryptor.new(new_value).encrypt
75
+ send "#{attribute}_encrypted=", encrypted_data
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def self.cipher
83
+ OpenSSL::Cipher::AES.new(256, :CBC)
84
+ end
85
+ end
86
+
87
+ require 'column_cryptor/core_ext'
@@ -0,0 +1,18 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ColumnCryptor
4
+ # Creates an initializer with a random private key
5
+ class InstallGenerator < Rails::Generators::Base
6
+ desc 'Add the column_cryptor initializer'
7
+
8
+ #:nodoc:
9
+ def self.source_root
10
+ @source_root ||= File.expand_path('../templates', __FILE__)
11
+ end
12
+
13
+ #:nodoc:
14
+ def install_initializer
15
+ template 'initializer.rb.erb', 'config/initializers/column_cryptor.rb'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ ColumnCryptor.private_key = <%= ColumnCryptor.new_key.inspect %>
@@ -0,0 +1,31 @@
1
+ require './test/test_helper'
2
+ require 'base64'
3
+
4
+ class DecryptorTest < ActiveSupport::TestCase
5
+ context '#decrypt' do
6
+ setup do
7
+ ColumnCryptor.private_key = "dKY56fUpxMF2gmOxDzWoEm1z7bUJwe1jj9a2962T0GA=\n"
8
+ end
9
+
10
+ context 'with no data' do
11
+ setup do
12
+ @decryptor = ColumnCryptor::Decryptor.new(nil)
13
+ end
14
+
15
+ should 'return nil' do
16
+ assert_nil @decryptor.decrypt
17
+ end
18
+ end
19
+
20
+ context 'with encrypted data' do
21
+ setup do
22
+ decryptor = ColumnCryptor::Decryptor.new("xxl4Ny2fGdOKYaEXTDqdOWoNRNd4hT1xe9GlPQ7qWxE=\n")
23
+ @result = decryptor.decrypt
24
+ end
25
+
26
+ should 'decrypt to plaintext' do
27
+ assert_equal 'abc123', @result
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ require './test/test_helper'
2
+ require 'base64'
3
+
4
+ class EncryptorTest < ActiveSupport::TestCase
5
+ context '#encrypt' do
6
+ setup do
7
+ ColumnCryptor.private_key = "dKY56fUpxMF2gmOxDzWoEm1z7bUJwe1jj9a2962T0GA=\n"
8
+ end
9
+
10
+ context 'with no data' do
11
+ setup do
12
+ @encryptor = ColumnCryptor::Encryptor.new(nil)
13
+ end
14
+
15
+ should 'return nil' do
16
+ assert_nil @encryptor.encrypt
17
+ end
18
+ end
19
+
20
+ context 'with plaintext data' do
21
+ setup do
22
+ cipher = OpenSSL::Cipher::AES.new(256, :CBC)
23
+ cipher.stubs random_iv: Base64.decode64('xxl4Ny2fGdOKYaEXTDqdOQ==\n')
24
+ OpenSSL::Cipher::AES.stubs new: cipher
25
+
26
+ @encryptor = ColumnCryptor::Encryptor.new('abc123')
27
+ @expected = "xxl4Ny2fGdOKYaEXTDqdOWoNRNd4hT1xe9GlPQ7qWxE=\n"
28
+ end
29
+
30
+ should 'encrypt data' do
31
+ assert_equal @expected, @encryptor.encrypt
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ require './test/test_helper'
2
+
3
+ class ColumnCryptorTest < ActiveSupport::TestCase
4
+ context '.included' do
5
+ context 'on the class' do
6
+ setup do
7
+ @test_class = Class.new(ActiveRecord::Base)
8
+ end
9
+
10
+ should 'define encrypts method' do
11
+ assert @test_class.respond_to?(:encrypts)
12
+ end
13
+ end
14
+
15
+ context 'on the instance' do
16
+ setup do
17
+ test_class = Class.new(ActiveRecord::Base) do
18
+ encrypts :secret_data
19
+ end
20
+
21
+ klass = Object.const_set('Dummy', test_class)
22
+ create_table 'dummies'
23
+
24
+ @test_object = klass.new
25
+ end
26
+
27
+ should 'define getter and setter methods' do
28
+ assert @test_object.respond_to?(:secret_data)
29
+ assert @test_object.respond_to?(:secret_data=)
30
+ end
31
+ end
32
+ end
33
+
34
+ context '.new_key' do
35
+ setup do
36
+ key_str = 'abc123'
37
+ cipher = stub(random_key: key_str)
38
+ OpenSSL::Cipher::AES.stubs new: cipher
39
+
40
+ @expected = Base64.encode64(key_str)
41
+ end
42
+
43
+ should 'return expected key' do
44
+ assert_equal @expected, ColumnCryptor.new_key
45
+ end
46
+ end
47
+
48
+ context '.private_key' do
49
+ setup do
50
+ ColumnCryptor.private_key = 'abc123'
51
+ end
52
+
53
+ should 'return abc123' do
54
+ assert_equal 'abc123', ColumnCryptor.private_key
55
+ end
56
+ end
57
+ end