column_cryptor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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