ssoper-acts_as_encryptable 1.0.4

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.
data/Changelog ADDED
@@ -0,0 +1,12 @@
1
+ == 3-26-2009
2
+
3
+ * Changed how encrypted data is saved, now put on separate column in the same table as encrypted model
4
+ * Added tests
5
+ * Can configure name of encrypted column
6
+ * Added documentation
7
+ * Log warning if default sample keys are being used while in production mode
8
+
9
+
10
+ == 3-25-2009
11
+
12
+ * Initial build
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Sean Soper
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.rdoc ADDED
@@ -0,0 +1,61 @@
1
+ = Acts As Encryptable
2
+
3
+ Encrypt and decrypt your data using asymmetric keys
4
+
5
+
6
+ == Installation
7
+
8
+ Run this if the gem isn't installed already
9
+ gem install ssoper-acts_as_encryptable --source=http://gems.github.com
10
+
11
+ Or place in your environment.rb
12
+ config.gem 'ssoper-acts_as_encryptable', :source => 'http://gems.github.com'
13
+
14
+
15
+ == Configuration
16
+
17
+ After installing be sure to add a column to any tables that need to be encrypted
18
+ class AddEncryptionFieldsToCreditCards < ActiveRecord::Migration
19
+ def self.up
20
+ add_column :credit_cards, :encrypted, :text
21
+ end
22
+
23
+ def self.down
24
+ remove_column :credit_cards, :encrypted
25
+ end
26
+ end
27
+
28
+ Or you can use the migration helper to generate the migration for the model
29
+ ./script/generate acts_as_encryptable_migration credit_cards
30
+
31
+ And in your model
32
+ class CreditCard < ActiveRecord::Base
33
+ attr_accessor :first_name, :last_name, :number
34
+ acts_as_encryptable :first_name, :last_name, :number
35
+ end
36
+
37
+
38
+ == Usage
39
+
40
+ Encrypt your data
41
+ card = CreditCard.new
42
+ card.first_name = 'Test'
43
+ card.last_name = 'User'
44
+ card.number = '1234567890'
45
+ card.save
46
+
47
+ Decrypt your data
48
+ card = CreditCard.last
49
+ card.decrypt!
50
+ => { :first_name => 'Test', :last_name => 'User', :number => '1234567890' }
51
+
52
+
53
+ == Tests
54
+
55
+ > rake test
56
+
57
+
58
+ == Acknowledgements
59
+
60
+ * Tobias Lütke for his blog post on asymmetric encryption using Ruby's native SSL libraries
61
+ * Paul Barry for helping simplify the functionality
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ namespace :gem do
5
+
6
+ task :default => :build
7
+
8
+ desc 'Build the acts_as_encryptable gem'
9
+ task :build do
10
+ Dir['*.gem'].each do |gem_filename|
11
+ sh "rm -rf #{gem_filename}"
12
+ end
13
+ sh "gem build acts_as_encryptable.gemspec"
14
+ end
15
+
16
+ desc 'Install the acts_as_encryptable gem'
17
+ task :install do
18
+ gem_filename = Dir['*.gem'].first
19
+ sh "sudo gem install --local #{gem_filename}"
20
+ end
21
+
22
+ end
23
+
24
+ task :default => ['gem:build', 'gem:install']
25
+
26
+ namespace :test do
27
+ Rake::TestTask.new(:unit) do |t|
28
+ t.libs << 'test'
29
+ t.pattern = 'test/unit/*_test.rb'
30
+ t.verbose = true
31
+ end
32
+ end
33
+
34
+ task :test do
35
+ Rake::Task['test:unit'].invoke
36
+ end
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "acts_as_encryptable"
3
+ s.version = "1.0.4"
4
+ s.date = "2009-03-27"
5
+ s.author = "Sean Soper"
6
+ s.email = "sean.soper@gmail.com"
7
+ s.summary = "Encrypt and decrypt your data using asymmetric keys"
8
+ s.description = "Allow your models to encrypt an arbitrary amount of data using asymmetric keys"
9
+ s.homepage = "http://github.com/ssoper/acts_as_encryptable"
10
+ s.require_path = "lib"
11
+ s.files = %w{ acts_as_encryptable.gemspec
12
+ lib/acts_as_encryptable.rb
13
+ lib/acts_as_encryptable/base.rb
14
+ lib/acts_as_encryptable/crypto.rb
15
+ generators/acts_as_encryptable_migration/acts_as_encryptable_migration_generator.rb
16
+ sample_keys/rsa_key.pub
17
+ sample_keys/rsa_key
18
+ test/test_helper.rb
19
+ test/unit/base_test.rb
20
+ test/unit/crypto_test.rb
21
+ MIT-LICENSE
22
+ Rakefile
23
+ README.rdoc
24
+ Changelog }
25
+ s.has_rdoc = true
26
+ s.extra_rdoc_files = %w{ MIT-LICENSE
27
+ README.rdoc }
28
+ s.rdoc_options = ["--line-numbers",
29
+ "--inline-source",
30
+ "--title",
31
+ "acts_as_encryptable",
32
+ "--main",
33
+ "README.rdoc"]
34
+ s.rubygems_version = "1.3.1"
35
+ s.add_dependency "capistrano", ">= 2.5.0"
36
+ end
@@ -0,0 +1,25 @@
1
+ class ActsAsEncryptableMigrationGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration:migration.rb', 'db/migrate', {
5
+ :assigns => migration_local_assigns,
6
+ :migration_file_name => "add_encryption_field_to_#{model_name}"
7
+ }
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def model_name
14
+ return ARGV.first
15
+ end
16
+
17
+ def migration_local_assigns
18
+ returning(assigns = {}) do
19
+ assigns[:migration_action] = "add"
20
+ assigns[:class_name] = "add_encryption_field_to_#{model_name}"
21
+ assigns[:table_name] = model_name
22
+ assigns[:attributes] = [Rails::Generator::GeneratedAttribute.new("encrypted", "text")]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ require 'acts_as_encryptable/base'
2
+ require 'acts_as_encryptable/crypto'
@@ -0,0 +1,90 @@
1
+ module ActsAsEncryptable
2
+ module Base
3
+ WARNING_MSG = "\n \e[0m\e[1;36m[\e[37mActsAsEncryptable\e[36m] \e[1;31mUsing the provided sample keys in production mode is highly discouraged. Generate your own RSA keys using the provided crypto libraries.\e[0m\n\n"
4
+ MAX_CHUNK_SIZE = 118
5
+ ENCRYPTED_CHUNK_SIZE = 175
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ def acts_as_encryptable(*args)
13
+ self.class_eval do
14
+ has_many :encrypted_chunks, :as => :encryptable
15
+ before_save :encrypt!
16
+
17
+ sample_key_public = File.join(File.dirname(__FILE__), '/../../sample_keys/rsa_key.pub')
18
+ sample_key_private = File.join(File.dirname(__FILE__), '/../../sample_keys/rsa_key')
19
+
20
+ options = {
21
+ :public_key => sample_key_public,
22
+ :private_key => sample_key_private,
23
+ :column => :encrypted
24
+ }
25
+ options.merge!(args.pop) if args.last.is_a? Hash
26
+
27
+ if (sample_key_public == options[:public_key]) and
28
+ (ENV['RAILS_ENV'] && ENV['RAILS_ENV'] == 'production')
29
+ RAILS_DEFAULT_LOGGER.warn WARNING_MSG
30
+ end
31
+
32
+ write_inheritable_attribute(:encrypted_fields, args.uniq)
33
+ class_inheritable_reader :encrypted_fields
34
+
35
+ write_inheritable_attribute(:public_key, ActsAsEncryptable::Crypto::Key.from_file(options[:public_key]))
36
+ class_inheritable_reader :public_key
37
+
38
+ write_inheritable_attribute(:private_key, ActsAsEncryptable::Crypto::Key.from_file(options[:private_key]))
39
+ class_inheritable_reader :private_key
40
+
41
+ write_inheritable_attribute(:encrypted_column, options[:column])
42
+ class_inheritable_reader :encrypted_column
43
+
44
+ include ActsAsEncryptable::Base::InstanceMethods
45
+ extend ActsAsEncryptable::Base::SingletonMethods
46
+ end
47
+ end
48
+ end
49
+
50
+ module SingletonMethods
51
+ def chunkize(str, chunk_size)
52
+ (0..((str.length/chunk_size.to_f).ceil - 1)).each do |x|
53
+ start, stop = x * chunk_size, (x + 1) * chunk_size
54
+ start += 1 if start > 0
55
+ yield str[start..stop]
56
+ end
57
+ end
58
+ end
59
+
60
+ module InstanceMethods
61
+ def encrypt!
62
+ yaml_data = self.class.encrypted_fields.inject({}) do |result, field|
63
+ result.merge!(field => instance_variable_get("@#{field}".to_sym))
64
+ end.to_yaml
65
+
66
+ chunks = ''
67
+ self.class.chunkize(yaml_data, MAX_CHUNK_SIZE) do |chunk|
68
+ chunks << self.class.public_key.encrypt(chunk)
69
+ end
70
+
71
+ self.send("#{self.class.encrypted_column}=", chunks)
72
+ end
73
+
74
+ def decrypt!
75
+ encrypted_data = self.send("#{self.class.encrypted_column}")
76
+
77
+ chunks = ''
78
+ self.class.chunkize(encrypted_data, ENCRYPTED_CHUNK_SIZE) do |chunk|
79
+ chunks << self.class.private_key.decrypt(chunk)
80
+ end
81
+
82
+ YAML::load(chunks).each do |k, v|
83
+ instance_variable_set("@#{k}".to_sym, v)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ActiveRecord::Base.send(:include, ActsAsEncryptable::Base)
@@ -0,0 +1,46 @@
1
+ require 'openssl'
2
+
3
+ module ActsAsEncryptable
4
+
5
+ # Originally posted by Tobias Lütke
6
+ # http://blog.leetsoft.com/2006/03/14/simple-encryption
7
+ module Crypto
8
+ def self.create_keys(priv = "rsa_key", pub = "#{priv}.pub", bits = 1024)
9
+ private_key = OpenSSL::PKey::RSA.new(bits)
10
+ File.open(priv, "w+") { |fp| fp << private_key.to_s }
11
+ File.open(pub, "w+") { |fp| fp << private_key.public_key.to_s }
12
+ private_key
13
+ end
14
+
15
+ class Key
16
+ def initialize(data)
17
+ @public = (data =~ /^-----BEGIN (RSA|DSA) PRIVATE KEY-----$/).nil?
18
+ @key = OpenSSL::PKey::RSA.new(data)
19
+ end
20
+
21
+ def self.from_file(filename)
22
+ self.new File.read( filename )
23
+ end
24
+
25
+ def encrypt(text)
26
+ Base64.encode64(@key.send("#{key_type}_encrypt", text))
27
+ end
28
+
29
+ def decrypt(text)
30
+ @key.send("#{key_type}_decrypt", Base64.decode64(text))
31
+ end
32
+
33
+ def private?
34
+ !@public
35
+ end
36
+
37
+ def public?
38
+ @public
39
+ end
40
+
41
+ def key_type
42
+ @public ? :public : :private
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICWwIBAAKBgQDN2qBMNriTzfnGTcPkkPWR/Ep6jAcOBGjmFj15hPzshjgk++dr
3
+ Z74AVlE/3EsCZ1mY16T1UDz8c8izVr8AhDaixhZBrRkx4xzipjHZ98JJwQhVQQIA
4
+ 85j2jH+/sYHLetr4VbSNTfQBXcOmWltW7l8ABePXt0uEUbE2NYuPQNOu0QIDAQAB
5
+ AoGAE4MZlp/JNxlbB5TvcIbdAA1t8de8A3QfjU+mXBJi9vhx8e9+rAuVUurboLX8
6
+ 1il9sKMgG7CTV0qSR419ZUsi8ndJW5Lg4V3jLDJ4iSnNH4L7DWs7ZyoYq4Z0Rkav
7
+ lZJbvFo0dsl9ntWAgioemEhul7d+OYI2/Rzrn1Kulqb/EQECQQDxWB5iphmyhIiq
8
+ rSF344TXoFKHEKL6+EQbknPh2WkhFxzkx53/oKm3IszjbMkixOrmIYbxRyhXWL7c
9
+ WJ1fBOkpAkEA2lrI8pHo7k5qkFc9RBpjUiPqEhl9ZO+W8tS0XS45uCpxM+Z3Di9k
10
+ tafzkPIU11oeGrvYa6m5SEkgN34O1DhFaQJAYzkXRPeFGR/kEEeduuyPcRc41s7A
11
+ Mu5fEfbkLbZ0wmX+OxDWpIIpRGHKWrYe+2x6JqMiF5BpxX92+KB2EtqyAQJAG4U9
12
+ tnT1arOvcqnMKv04b23fXpCf4UzhNZHhea0N0UxoICZ38u2+P7b/V9FrFwlgqfXq
13
+ /QbTN20gBl549/5voQJAQPC7y93eEL73CY6FTNqETMrIT8dVmaAnQkvbk55Ratqv
14
+ XwxN+/Ec8Nvu12fTO+dYrf7IW6kbZ0zZ3pCQUIYsQQ==
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,5 @@
1
+ -----BEGIN RSA PUBLIC KEY-----
2
+ MIGJAoGBAM3aoEw2uJPN+cZNw+SQ9ZH8SnqMBw4EaOYWPXmE/OyGOCT752tnvgBW
3
+ UT/cSwJnWZjXpPVQPPxzyLNWvwCENqLGFkGtGTHjHOKmMdn3wknBCFVBAgDzmPaM
4
+ f7+xgct62vhVtI1N9AFdw6ZaW1buXwAF49e3S4RRsTY1i49A067RAgMBAAE=
5
+ -----END RSA PUBLIC KEY-----
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'activerecord'
4
+ require 'fileutils'
5
+
6
+ ENV['RAILS_ENV'] = 'test'
7
+ require File.dirname(__FILE__) + '/../lib/acts_as_encryptable'
8
+
9
+ class Test::Unit::TestCase
10
+
11
+ def establish_connection(db_file = nil)
12
+ db_file = File.join('/tmp/acts_as_encryptable_tests.sqlite') unless db_file
13
+ ActiveRecord::Base.configurations = { 'ActiveRecord::Base' => { :adapter => 'sqlite3', :database => db_file, :timeout => 5000 } }
14
+ ActiveRecord::Base.establish_connection('ActiveRecord::Base')
15
+ ActiveRecord::Base.connection.execute('drop table if exists credit_cards')
16
+ ActiveRecord::Base.connection.execute('create table credit_cards (id integer, encrypted text)')
17
+ ActiveRecord::Base.connection.execute('drop table if exists people')
18
+ ActiveRecord::Base.connection.execute('create table people (id integer, important_data text)')
19
+ ActiveRecord::Base.connection
20
+ end
21
+
22
+ end
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+
5
+ class CreditCard < ActiveRecord::Base
6
+ attr_accessor :name_on_card, :number, :expiration
7
+ acts_as_encryptable :name_on_card, :number, :expiration
8
+ end
9
+
10
+ class Person < ActiveRecord::Base
11
+ attr_accessor :first_name, :last_name, :ssn
12
+ acts_as_encryptable :first_name, :last_name, :ssn, :column => 'important_data'
13
+ end
14
+
15
+ def setup
16
+ @connection = establish_connection
17
+ end
18
+
19
+ def test_a_credit_card
20
+ card = CreditCard.new(valid_credit_card)
21
+ assert card.save
22
+ end
23
+
24
+ def test_data_is_encrypted
25
+ test_a_credit_card
26
+ card = CreditCard.last
27
+ assert !card.name_on_card
28
+ assert !card.number
29
+ assert !card.expiration
30
+ end
31
+
32
+ def test_data_is_decrypted
33
+ test_a_credit_card
34
+ card = CreditCard.last
35
+ card.decrypt!
36
+ assert card.name_on_card == valid_credit_card[:name_on_card]
37
+ assert card.number == valid_credit_card[:number]
38
+ assert card.expiration == valid_credit_card[:expiration]
39
+ end
40
+
41
+ def test_set_encrypted_column_name
42
+ person = Person.new(valid_person)
43
+ assert person.save!
44
+ person = Person.last
45
+ person.decrypt!
46
+ assert person.first_name = valid_person[:first_name]
47
+ assert person.last_name = valid_person[:last_name]
48
+ assert person.ssn = valid_person[:ssn]
49
+ end
50
+
51
+ private
52
+
53
+ def valid_credit_card
54
+ {
55
+ :name_on_card => 'Test User',
56
+ :number => '1234567890123456',
57
+ :expiration => (Date.today + 1.year).strftime("%i/%y")
58
+ }
59
+ end
60
+
61
+ def valid_person
62
+ {
63
+ :first_name => 'Test',
64
+ :last_name => 'User',
65
+ :ssn => '111223333'
66
+ }
67
+ end
68
+
69
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class CryptoTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @text = "I am a secret"
7
+ @public_key = File.join(File.dirname(__FILE__), '/../../sample_keys/rsa_key.pub')
8
+ @private_key = File.join(File.dirname(__FILE__), '/../../sample_keys/rsa_key')
9
+ end
10
+
11
+ def test_encryption
12
+ public_key = ActsAsEncryptable::Crypto::Key.from_file(@public_key)
13
+ encrypted = public_key.encrypt(@text)
14
+ assert encrypted != @text
15
+ end
16
+
17
+ def test_decryption
18
+ public_key = ActsAsEncryptable::Crypto::Key.from_file(@public_key)
19
+ private_key = ActsAsEncryptable::Crypto::Key.from_file(@private_key)
20
+ encrypted = public_key.encrypt(@text)
21
+ assert @text == private_key.decrypt(encrypted)
22
+ end
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssoper-acts_as_encryptable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Sean Soper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-27 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: capistrano
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.5.0
24
+ version:
25
+ description: Allow your models to encrypt an arbitrary amount of data using asymmetric keys
26
+ email: sean.soper@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - MIT-LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - acts_as_encryptable.gemspec
36
+ - lib/acts_as_encryptable.rb
37
+ - lib/acts_as_encryptable/base.rb
38
+ - lib/acts_as_encryptable/crypto.rb
39
+ - generators/acts_as_encryptable_migration/acts_as_encryptable_migration_generator.rb
40
+ - sample_keys/rsa_key.pub
41
+ - sample_keys/rsa_key
42
+ - test/test_helper.rb
43
+ - test/unit/base_test.rb
44
+ - test/unit/crypto_test.rb
45
+ - MIT-LICENSE
46
+ - Rakefile
47
+ - README.rdoc
48
+ - Changelog
49
+ has_rdoc: true
50
+ homepage: http://github.com/ssoper/acts_as_encryptable
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --line-numbers
54
+ - --inline-source
55
+ - --title
56
+ - acts_as_encryptable
57
+ - --main
58
+ - README.rdoc
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.2.0
77
+ signing_key:
78
+ specification_version: 2
79
+ summary: Encrypt and decrypt your data using asymmetric keys
80
+ test_files: []
81
+