nbfritz-encryptedattributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,169 @@
1
+ require 'openssl'
2
+ require 'digest'
3
+ require 'base64'
4
+
5
+ # This module allows for the easy encryption/decryption of attributes
6
+ #
7
+ # Attributes to be encrypted are configured with an attr_encrypted, similar to
8
+ # an attr_accessor call, at the beginning of the class. Eg:
9
+ #
10
+ # class TestClass
11
+ # attr_encrypted :ssn
12
+ # ...
13
+ #
14
+ # One caveat: at this point, only attributes containing strings can be
15
+ # encrypted. If you need to encrypt other types, you'll have to handle
16
+ # the casting to and from String type manually.
17
+ module EncryptedAttributes
18
+ module ClassMethods
19
+ # Class-level writer for the @@encrypted_attributes array
20
+ def encrypted_attributes=(attributes)
21
+ @encrypted_attributes = attributes
22
+ end
23
+
24
+ # Class-level reader for the @@encrypted_attributes array
25
+ def encrypted_attributes
26
+ @encrypted_attributes
27
+ end
28
+
29
+ # Generates a random IV for seeding a cipher
30
+ def random_iv
31
+ OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_iv
32
+ end
33
+
34
+ # Generates a random IV for seeding a cipher
35
+ def random_key
36
+ OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_key
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ attr_writer :cipher_iv
42
+ attr_accessor :cipher_key, :encrypted
43
+
44
+ # reader for @cipher_iv that returns a default value if none exists
45
+ def cipher_iv
46
+ @cipher_iv ||= "1234567812345678" # 16 bytes (128 bits) of data
47
+ end
48
+
49
+ # Encrypts all encrypted attributes with the crypt_all private method
50
+ def encrypt_all; crypt_all(:encrypt); end
51
+
52
+ # Decrypts all encrypted attributes with the crypt_all private method
53
+ def decrypt_all; crypt_all(:decrypt); end
54
+
55
+ # Takes the supplied password and optional salt and sets the @cipher_key
56
+ def key_from_password(password, salt='')
57
+ @cipher_key = crypto_hash(salt+password)
58
+ end
59
+
60
+ # Generates a random 256-bit hash
61
+ def random_key
62
+ @cipher_key = self.class.random_key
63
+ end
64
+
65
+ # Generates a random IV
66
+ def random_iv
67
+ @cipher_iv = self.class.random_iv
68
+ end
69
+
70
+ # Marshals the @cipher_key and @cipher_iv and returns it in base64
71
+ def credentials
72
+ Base64.encode64(Marshal.dump([self.cipher_key, self.cipher_iv]))
73
+ end
74
+
75
+ # Unmarshals supplied base64 data into @cipher_key and @cipher_iv
76
+ def credentials=(credentials)
77
+ (@cipher_key, @cipher_iv) = Marshal.load(Base64.decode64(credentials))
78
+ end
79
+
80
+ # Generates a 256-bit hash from the supplied data
81
+ def crypto_hash(data)
82
+ Digest::SHA256.digest(data)
83
+ end
84
+
85
+ # Returns a base64 encoded version of crypto_hash
86
+ def crypto_hash64(data)
87
+ Base64.encode64(crypto_hash(data))
88
+ end
89
+
90
+ private
91
+ # Encrypts or decrypts all encrypted attributes on this instance
92
+ #
93
+ # Mode must be either :encrypt or :decrypt
94
+ #
95
+ # Returns false if attempting a decrypt on an already decrypted instance
96
+ # or an encrypt on an already encrypted instance.
97
+ def crypt_all(mode = :decrypt)
98
+ if (mode == :decrypt && @encrypted) || (mode == :encrypt && !@encrypted)
99
+ self.class.encrypted_attributes.each do |attribute|
100
+ crypt_attr(attribute.to_s, mode)
101
+ end
102
+ @encrypted = (mode == :decrypt) ? false : true
103
+ return true
104
+ else
105
+ return false
106
+ end
107
+ end
108
+
109
+ # Handles the encryption of a single attribute
110
+ #
111
+ # If accessors are available, they will be used, otherwise, the instance
112
+ # variables will be read/written directly.
113
+ #
114
+ # Note: will skip encryption/decryption if the attribute is nil
115
+ def crypt_attr(attribute, mode = :decrypt)
116
+ method_list = self.methods
117
+ if method_list.include?(attribute.to_s)
118
+ data = send(attribute)
119
+ else
120
+ data = instance_variable_get("@"+attribute)
121
+ end
122
+
123
+ if data
124
+ if method_list.include?(writer = attribute.to_s+"=")
125
+ send(writer, crypt(data, mode))
126
+ else
127
+ instance_variable_set("@"+attribute, crypt(data, mode))
128
+ end
129
+ end
130
+ end
131
+
132
+ # Does the real encryption/decryption
133
+ def crypt(data, mode = :decrypt)
134
+ data = Base64.decode64(data) if mode == :decrypt
135
+
136
+ raise "No key found" unless @cipher_key
137
+ raise "Only Strings can be encrypted" unless data.class == String
138
+ cipher = cipher_object(mode)
139
+ output = cipher.update(data)
140
+ output << cipher.final
141
+
142
+ return (mode == :encrypt) ? Base64.encode64(output) : output
143
+ end
144
+
145
+ # Returns a fresh cipher object.
146
+ #
147
+ # (developer note: The gotcha here is that the call to cipher to set
148
+ # the mode to either encrypt or decrypt must preceed the calls to supply
149
+ # the iv and key!)
150
+ def cipher_object(mode = :decrypt)
151
+ cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
152
+ cipher.send(mode.to_s)
153
+ cipher.key = self.cipher_key
154
+ cipher.iv = self.cipher_iv
155
+ return cipher
156
+ end
157
+ end
158
+ end
159
+
160
+ class Class
161
+ # Add the attr_encrypted command to Class so new classes can use it
162
+ def attr_encrypted(*attrs)
163
+ self.extend(EncryptedAttributes::ClassMethods)
164
+ send(:encrypted_attributes=, attrs)
165
+ instance_eval do
166
+ send(:include, EncryptedAttributes::InstanceMethods)
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/encryptedattributes.rb'
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/setup.rb'
2
+
3
+ class TestClassAccessors
4
+ attr_accessor :x, :y
5
+ attr_encrypted :x, :y
6
+ end
7
+
8
+ class TestRandomEncryption < Test::Unit::TestCase
9
+ def setup
10
+ @test_a = TestClassAccessors.new
11
+ @key = @test_a.random_key
12
+ @iv = @test_a.random_iv
13
+ @test_a.x = "test text 1"
14
+ @test_a.y = "test text 2"
15
+ end
16
+
17
+ def test_random_key
18
+ assert_equal 32, @key.length
19
+ assert_instance_of String, @key
20
+ end
21
+
22
+ def test_random_iv
23
+ assert_equal 16, @iv.length
24
+ assert_instance_of String, @iv
25
+ end
26
+
27
+ def test_encryption
28
+ @before = @test_a.x
29
+ @test_a.encrypt_all
30
+ @after = @test_a.x
31
+
32
+ assert_not_equal @before, @after
33
+ end
34
+ end
35
+
36
+ class TestFixedEncryption < Test::Unit::TestCase
37
+ def setup
38
+ @test_a = TestClassAccessors.new
39
+ @key = @test_a.key_from_password('password')
40
+ @iv = @test_a.cipher_iv = 'a'*16
41
+ @test_a.x = "test text 1"
42
+ @test_a.y = "test text 2"
43
+
44
+ @credentials = "BAhbByIlXohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtgiFWFhYWFh\n"+
45
+ "YWFhYWFhYWFhYWE=\n"
46
+ @encrypted = "LJ/4bevW6+N+9YuthORrqA==\n"
47
+ end
48
+
49
+ def test_fixed_key
50
+ assert_equal 32, @key.length
51
+ assert_instance_of String, @key
52
+ end
53
+
54
+ def test_fixed_iv
55
+ assert_equal 16, @iv.length
56
+ assert_instance_of String, @iv
57
+ end
58
+
59
+ def test_encryption
60
+ @before = @test_a.x
61
+ @test_a.encrypt_all
62
+ @after = @test_a.x
63
+
64
+ assert_not_equal @before, @after
65
+ assert_equal @after, @encrypted
66
+ end
67
+
68
+ def test_credentials
69
+ assert_equal 78, @test_a.credentials.size
70
+ assert_equal @credentials, @test_a.credentials
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nbfritz-encryptedattributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Fritz
8
+ autorequire: encryptedattributes
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: This module adds the attr_encrypted call (use it like attr_accessor) to add two-way encryption capabilities to any class being defined.
17
+ email: fritzn@crown.edu
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - tests/setup.rb
26
+ - tests/test_encryption.rb
27
+ - lib/encryptedattributes.rb
28
+ has_rdoc: true
29
+ homepage: http://n.thefritzes.net
30
+ post_install_message:
31
+ rdoc_options: []
32
+
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 1.0.1
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: Basic module to add symmetric encryption of attributes/instance variables to Ruby classes.
54
+ test_files:
55
+ - tests/test_encryption.rb