strongbox 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Joseph A. Ilacqua, Jr
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,178 @@
1
+ h1. Strongbox
2
+
3
+ Strongbox provides Public Key Encryption for ActiveRecord. By using a public key
4
+ sensitive information can be encrypted and stored automatically. Once stored a
5
+ password is required to access the information.
6
+
7
+ Because the largest amount of data that can practically be encrypted with a public
8
+ key is 245 byte, by default Strongbox uses a two layer approach. First it encrypts
9
+ the attribute using symmetric encryption with a randomly generated key and
10
+ initialization vector (IV) (which can just be through to as a second key), then it
11
+ encrypts those with the public key.
12
+
13
+ Strongbox stores the encrypted attribute in a database column by the same name, i.e.
14
+ if you tell Strongbox to encrypt "secret" then it will be store in "secret" in the
15
+ database, just as the unencrypted attribute would be. If symmetric encryption is used
16
+ (the default) two additional columns "secret_key" and "secret_iv" are needed as well.
17
+
18
+ The attribute is automatically encrypted simply by setting it:
19
+
20
+ user.secret = "Shhhhhhh..."
21
+
22
+ and decrypted by calling the "decrypt" method with the private key password.
23
+
24
+ plain_text = user.secret.decrypt 'letmein'
25
+
26
+ h2. Quick Start
27
+
28
+ In your model:
29
+
30
+ class User < ActiveRecord::Base
31
+ encrypt_with_public_key :secret,
32
+ :key_pair => File.join(RAILS_ROOT,'config','keypair.pem'),
33
+ end
34
+
35
+ In your migrations:
36
+
37
+ class AddSecretColumnsToUser < ActiveRecord::Migration
38
+ def self.up
39
+ add_column :users, :secret, binary
40
+ add_column :users, :secret_key, binary
41
+ add_column :users, :secret_iv, binary
42
+ end
43
+ def self.down
44
+ remove_column :users, :secret
45
+ remove_column :users, :secret_key
46
+ remove_column :users, :secret_iv
47
+ end
48
+ end
49
+
50
+ Generate a key pair:
51
+
52
+ (Choose a strong password.)
53
+
54
+ openssl genrsa -des3 -out config/private.pem 2048
55
+ openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
56
+ cat config/private.pem config/public.pem >> config/keypair.pem
57
+
58
+ In your views and forms you don't need to do anything special to encrypt data. To
59
+ decrypt call:
60
+
61
+ user.secret.decrypt 'password'
62
+
63
+ h2. Gem installation (Rails 2.1+)
64
+
65
+ In config/environment.rb:
66
+
67
+ config.gem "spikex-strongbox",
68
+ :lib => 'strongbox',
69
+ :source => 'http://gems.github.com',
70
+
71
+ h2. Usage
72
+
73
+ _encrypt_with_public_key_ sets up the attribute it's called on for automatic
74
+ encryption. It's simplest form is:
75
+
76
+ class User < ActiveRecord::Base
77
+ encrypt_with_public_key :secret,
78
+ :key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
79
+ end
80
+ end
81
+
82
+ Which will encrypt the attribute "secret". The attribute will be encrypted using
83
+ symmetric encryption with an automatically generated key and IV encrypted using the
84
+ public key. This requires three columns in the database "secret", "secret_key", and
85
+ "secret_iv" (see below).
86
+
87
+ Options to encrypt_with_public_key are:
88
+
89
+ :public_key - Path to the public key file. Overrides :keypair.
90
+
91
+ :private_key - Path to the private key file. Overrides :keypair.
92
+
93
+ :keypair - Path to a file containing both the public and private keys.
94
+
95
+ :symmetric :always/:never - Encrypt the date using symmetric encryption. The public
96
+ key is used to encrypt an automatically generated key and IV. This allows for large
97
+ amounts of data to be encrypted. The size of data that can be encrypted directly with
98
+ the public is limit to key size (in bytes) - 11. So a 2048 key can encrypt *245 bytes*. Defaults to :always
99
+
100
+ :symmetric_cipher - Cipher to use for symmetric encryption. Defaults to *'aes-256-cbc'*. Other ciphers support by OpenSSL may be used.
101
+
102
+ :base64 true/false - Use Base64 encoding to convert encrypted data to text. Use when
103
+ binary save data storage is not available. Defaults to *false*
104
+
105
+ :padding - Method used to pad data encrypted with the public key. Defaults to
106
+ RSA_PKCS1_PADDING. The default should be fine unless you are dealing with legacy
107
+ data.
108
+
109
+ For example, encrypting a small attribute, providing only the public key for extra
110
+ security, and Base64 encoding the encrypted data:
111
+
112
+ class User < ActiveRecord::Base
113
+ validates_length_of :pin_code, :is => 4
114
+ encrypt_with_public_key :pin_code,
115
+ :symmetric => :never
116
+ :base64 => true
117
+ :public_key => File.join(RAILS_ROOT,'config','public.pem'),
118
+ end
119
+ end
120
+
121
+ h2. Key Generation
122
+
123
+ Generate a key pair:
124
+
125
+ openssl genrsa -des3 -out config/private.pem 2048
126
+ Generating RSA private key, 2048 bit long modulus
127
+ ......+++
128
+ .+++
129
+ e is 65537 (0x10001)
130
+ Enter pass phrase for config/private.pem:
131
+ Verifying - Enter pass phrase for config/private.pem:
132
+
133
+ and extract the the public key:
134
+
135
+ openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
136
+ Enter pass phrase for config/private.pem:
137
+ writing RSA key
138
+
139
+ If you are going to leave the private key installed it's easiest to create a single
140
+ key pair file:
141
+
142
+ cat config/private.pem config/public.pem >> config/keypair.pem
143
+
144
+ Or, for added security, store the private key file else where, leaving only the public key.
145
+
146
+ h2. Table Creation
147
+
148
+ In it's default configuration Strongbox requires three columns, one the encrypted
149
+ data, one for the encrypted symmetric key, and one for the encrypted symmetric IV. If
150
+ symmetric encryption is disabled then only the columns for the data being encrypted
151
+ is needed.
152
+
153
+ If your underlying database allows, use the *binary* column type. If you must store
154
+ your data in text format be sure to enable Base64 encoding and to use the *text*
155
+ column type. The _string_ column type is likely to be too small to hold the encrypted
156
+ string.
157
+
158
+ h2. Security Caveats
159
+
160
+ If you don't encrypt your data, then an attacker only needs to steal that data to get
161
+ your secrets.
162
+
163
+ If encrypt your data using symmetric encrypts and a stored key, then the attacker
164
+ needs the data and the key stored on the server.
165
+
166
+ If you use public key encryption, the attacker needs the data, the private key, and
167
+ the password. This means the attacker has to sniff the password somehow, so that's
168
+ what you need to protect against.
169
+
170
+ h2. Authors
171
+
172
+ Spike Ilacqua
173
+
174
+ h2. Thanks
175
+
176
+ Strongbox's implementation drew inspiration from Thoughtbot's Paperclip gem
177
+ http://www.thoughtbot.com/projects/paperclip
178
+
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
6
+ require 'strongbox'
7
+
8
+ desc 'Default: run tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the strongbox gem.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib' << 'profile'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the strongbox gem.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'doc'
21
+ rdoc.title = 'Strongbox'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README*')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ spec = Gem::Specification.new do |s|
28
+ s.name = "strongbox"
29
+ s.version = Strongbox::VERSION
30
+ s.summary = "Secures ActiveRecord fields with public key encryption."
31
+ s.authors = ["Spike Ilacqua"]
32
+ s.email = "spike@stuff-things.net"
33
+ s.homepage = "http://stuff-things.net/strongbox"
34
+ s.files = FileList["[A-Z]*", "init.rb", "{lib,rails}/**/*"]
35
+ s.add_development_dependency 'thoughtbot-shoulda'
36
+ end
37
+
38
+ desc "Generate a gemspec file for GitHub"
39
+ task :gemspec do
40
+ File.open("#{spec.name}.gemspec", 'w') do |f|
41
+ f.write spec.to_yaml
42
+ end
43
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'strongbox'
data/lib/strongbox.rb ADDED
@@ -0,0 +1,75 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ require 'strongbox/lock'
5
+
6
+ module Strongbox
7
+
8
+ VERSION = "0.2.0"
9
+
10
+ RSA_PKCS1_PADDING = OpenSSL::PKey::RSA::PKCS1_PADDING
11
+ RSA_SSLV23_PADDING = OpenSSL::PKey::RSA::SSLV23_PADDING
12
+ RSA_NO_PADDING = OpenSSL::PKey::RSA::NO_PADDING
13
+ RSA_PKCS1_OAEP_PADDING = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
14
+
15
+ class << self
16
+ # Provides for setting the default options for Strongbox
17
+ def options
18
+ @options ||= {
19
+ :base64 => false,
20
+ :symmetric => :always,
21
+ :padding => RSA_PKCS1_PADDING,
22
+ :symmetric_cipher => 'aes-256-cbc'
23
+ }
24
+ end
25
+
26
+ def included base #:nodoc:
27
+ base.extend ClassMethods
28
+ end
29
+ end
30
+
31
+ class StrongboxError < StandardError #:nodoc:
32
+ end
33
+
34
+ module ClassMethods
35
+ # +encrypt_with_public_key+ gives the class it is called on an attribute that
36
+ # when assigned is automatically encrypted using a public key. This allows the
37
+ # unattended encryption of data, without exposing the information need to decrypt
38
+ # it (as would be the case when using symmetric key encryption alone). Small
39
+ # amounts of data may be encrypted directly with the public key. Larger data is
40
+ # encrypted using symmetric encryption. The encrypted data is stored in the
41
+ # database column of the same name as the attibute. If symmetric encryption is
42
+ # used (the default) additional column are need to store the generated password
43
+ # and IV.
44
+ def encrypt_with_public_key(name, options = {})
45
+ include InstanceMethods
46
+
47
+ class_inheritable_reader :lock_options
48
+ write_inheritable_attribute(:lock_options, {}) if lock_options.nil?
49
+
50
+
51
+ lock_options[name] = options.symbolize_keys.reverse_merge Strongbox.options
52
+
53
+ define_method name do
54
+ lock_for(name)
55
+ end
56
+
57
+ define_method "#{name}=" do | plaintext |
58
+ lock_for(name).encrypt plaintext
59
+ end
60
+
61
+ end
62
+ end
63
+
64
+ module InstanceMethods
65
+ def lock_for name
66
+ @_locks ||= {}
67
+ @_locks[name] ||= Lock.new(name, self, self.class.lock_options[name])
68
+ end
69
+ end
70
+ end
71
+
72
+ if Object.const_defined?("ActiveRecord")
73
+ ActiveRecord::Base.send(:include, Strongbox)
74
+ end
75
+
@@ -0,0 +1,115 @@
1
+ module Strongbox
2
+ # The Lock class encrypts and decrypts the protected attribute. It
3
+ # automatically encrypts the data when set and decrypts it when the private
4
+ # key password is provided.
5
+ class Lock
6
+
7
+ def initialize name, instance, options = {}
8
+ @name = name
9
+ @instance = instance
10
+
11
+ @size = nil
12
+
13
+ options = Strongbox.options.merge(options)
14
+
15
+ @base64 = options[:base64]
16
+ @public_key = options[:public_key] || options[:key_pair]
17
+ @private_key = options[:private_key] || options[:key_pair]
18
+ @padding = options[:padding]
19
+ @symmetric = options[:symmetric]
20
+ @symmetric_cipher = options[:symmetric_cipher]
21
+ @symmetric_key = options[:symmetric_key] || "#{name}_key"
22
+ @symmetric_iv = options[:symmetric_iv] || "#{name}_iv"
23
+ end
24
+
25
+ def encrypt plaintext
26
+ unless @public_key
27
+ raise StrongboxError.new("#{@instance.class} model does not have public key_file")
28
+ end
29
+ if !plaintext.blank?
30
+ @size = plaintext.size # For validations
31
+ # Using a blank password in OpenSSL::PKey::RSA.new prevents reading
32
+ # the private key if the file is a key pair
33
+ public_key = OpenSSL::PKey::RSA.new(File.read(@public_key),"")
34
+ if @symmetric == :always
35
+ cipher = OpenSSL::Cipher::Cipher.new(@symmetric_cipher)
36
+ cipher.encrypt
37
+ cipher.key = random_key = cipher.random_key
38
+ cipher.iv = random_iv = cipher.random_iv
39
+
40
+ ciphertext = cipher.update(plaintext)
41
+ ciphertext << cipher.final
42
+ encrypted_key = public_key.public_encrypt(random_key,@padding)
43
+ encrypted_iv = public_key.public_encrypt(random_iv,@padding)
44
+ if @base64
45
+ encrypted_key = Base64.encode64(encrypted_key)
46
+ encrypted_iv = Base64.encode64(encrypted_iv)
47
+ end
48
+ @instance.write_attribute(@symmetric_key,encrypted_key)
49
+ @instance.write_attribute(@symmetric_iv,encrypted_iv)
50
+ else
51
+ ciphertext = public_key.public_encrypt(plaintext,@padding)
52
+ end
53
+ ciphertext = Base64.encode64(ciphertext) if @base64
54
+ @instance.write_attribute(@name,ciphertext)
55
+ end
56
+ end
57
+
58
+ # Given the private key password decrypts the attribute. Will raise
59
+ # OpenSSL::PKey::RSAError if the password is wrong.
60
+
61
+ def decrypt password = ""
62
+ # Given a private key and a nil password OpenSSL::PKey::RSA.new() will
63
+ # *prompt* for a password, we default to an empty string to avoid that.
64
+ ciphertext = @instance.read_attribute(@name)
65
+ return nil if ciphertext.nil?
66
+ return "" if ciphertext.empty?
67
+
68
+ return "*encrypted*" if password.blank?
69
+
70
+ unless @private_key
71
+ raise StrongboxError.new("#{@instance.class} model does not have private key_file")
72
+ end
73
+
74
+ if ciphertext
75
+ ciphertext = Base64.decode64(ciphertext) if @base64
76
+ private_key = OpenSSL::PKey::RSA.new(File.read(@private_key),password)
77
+ if @symmetric == :always
78
+ random_key = @instance.read_attribute(@symmetric_key)
79
+ random_iv = @instance.read_attribute(@symmetric_iv)
80
+ if @base64
81
+ random_key = Base64.decode64(random_key)
82
+ random_iv = Base64.decode64(random_iv)
83
+ end
84
+ cipher = OpenSSL::Cipher::Cipher.new(@symmetric_cipher)
85
+ cipher.decrypt
86
+ cipher.key = private_key.private_decrypt(random_key,@padding)
87
+ cipher.iv = private_key.private_decrypt(random_iv,@padding)
88
+ plaintext = cipher.update(ciphertext)
89
+ plaintext << cipher.final
90
+ else
91
+ plaintext = private_key.private_decrypt(ciphertext,@padding)
92
+ end
93
+ else
94
+ nil
95
+ end
96
+ end
97
+
98
+ def to_s
99
+ decrypt
100
+ end
101
+
102
+ # Needed for validations
103
+ def blank?
104
+ @instance.read_attribute(@name).blank?
105
+ end
106
+
107
+ def nil?
108
+ @instance.read_attribute(@name).nil?
109
+ end
110
+
111
+ def size
112
+ @size
113
+ end
114
+ end
115
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__),'../init.rb')
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: strongbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Spike Ilacqua
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-28 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description:
26
+ email: spike@stuff-things.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - LICENSE
35
+ - Rakefile
36
+ - README.textile
37
+ - init.rb
38
+ - lib/strongbox/lock.rb
39
+ - lib/strongbox.rb
40
+ - rails/init.rb
41
+ has_rdoc: true
42
+ homepage: http://stuff-things.net/strongbox
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.3.4
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Secures ActiveRecord fields with public key encryption.
69
+ test_files: []
70
+