encrypted-field 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7f4f73714ca33f397ac0ea28ca518ae864a813f559d111f96a074f573d493604
4
+ data.tar.gz: d080c07db99b099651022386aaa7c11ee573ce542cb418b63500e1bc39d6f1b7
5
+ SHA512:
6
+ metadata.gz: 6f70eec7a623e4959fe8e20dbb08169ac82871f2b1875fae247b7de4c111698bd740ca8e752b105141faab1fa39a49a6155116cad94649574ac2d02949670244
7
+ data.tar.gz: 950af5feb6aa9e5ecff184ecd62f0ff5c9c712f899b70f96d31ed01b496b80e7e55511c723b478d5deff6530d455917d72cbf3195f345c89c1e51e4049eaf164
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'encrypted-field/config'
4
+ require 'encrypted-field/encoder'
5
+ require 'encrypted-field/field'
6
+ require 'encrypted-field/base_policy'
7
+ require 'encrypted-field/policy_with_iv'
8
+ require 'encrypted-field/policy_without_iv'
9
+
10
+ # EncryptedField is a library for obfuscating and simplifying the logic around encrypting/decrypting values from a DB
11
+ module EncryptedField
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ # no-doc
17
+ module ClassMethods
18
+ def encrypted_field(field_name, policy_name, encrypted_field_name = nil, fallback_policy_name = nil)
19
+ encrypted_field_name ||= "#{field_name}_encrypted"
20
+ Field.new(field_name, encrypted_field_name, policy_name, fallback_policy_name).add_methods(self)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module EncryptedField
7
+ # EncryptedField::BasePolicy abstract class for creating encryption policies
8
+ class BasePolicy
9
+ attr_reader :algorithm,
10
+ :options
11
+
12
+ def initialize(algorithm, secret_key, options = {})
13
+ @algorithm = algorithm
14
+ @secret_key = secret_key
15
+ @options = options
16
+ end
17
+
18
+ def prefix_with_policy_name?
19
+ options.fetch(:prefix_with_policy_name, true)
20
+ end
21
+
22
+ private
23
+
24
+ def encode_payload(str)
25
+ if options.key?(:encode_payload)
26
+ options[:encode_payload].call(str)
27
+ else
28
+ Base64.strict_encode64(str)
29
+ end
30
+ end
31
+
32
+ def decode_payload(str)
33
+ if options.key?(:decode_payload)
34
+ options[:decode_payload].call(str)
35
+ else
36
+ Base64.strict_decode64(str)
37
+ end
38
+ end
39
+
40
+ def encode_iv(str)
41
+ if options.key?(:encode_iv)
42
+ options[:encode_iv].call(str)
43
+ else
44
+ Base64.strict_encode64(str)
45
+ end
46
+ end
47
+
48
+ def decode_iv(str)
49
+ if options.key?(:decode_iv)
50
+ options[:decode_iv].call(str)
51
+ else
52
+ Base64.strict_decode64(str)
53
+ end
54
+ end
55
+
56
+ def create_cipher
57
+ OpenSSL::Cipher.new(algorithm)
58
+ end
59
+
60
+ def secret_key
61
+ case @secret_key
62
+ when Proc
63
+ @secret_key.call
64
+ when String
65
+ @secret_key
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ module EncryptedField
6
+ # EncryptedField::Config keeps track of all policies and the policy separator
7
+ class Config
8
+ include Singleton
9
+
10
+ POLICY_DEFAULT_SEPARATOR = '.'
11
+
12
+ attr_reader :policy_separator
13
+
14
+ def policies
15
+ @policies ||= {}
16
+ end
17
+
18
+ def set_policy_separator(policy_separator)
19
+ @policy_separator = policy_separator
20
+ end
21
+
22
+ def policy_separator_or_default
23
+ policy_separator || POLICY_DEFAULT_SEPARATOR
24
+ end
25
+
26
+ def add_policy(policy_name, algorithm, secret_key, options = {})
27
+ add_custom_policy(policy_name, PolicyWithIV.new(algorithm, secret_key, options))
28
+ end
29
+
30
+ def add_policy_without_iv(policy_name, algorithm, secret_key, options = {})
31
+ add_custom_policy(policy_name, PolicyWithoutIV.new(algorithm, secret_key, options))
32
+ end
33
+
34
+ def add_custom_policy(policy_name, policy)
35
+ valid_policy_name!(policy_name)
36
+ policies[policy_name.to_s] = policy
37
+ end
38
+
39
+ def self.configure(&block)
40
+ instance.instance_eval(&block)
41
+ end
42
+
43
+ def reset!
44
+ @policies = nil
45
+ @policy_separator = nil
46
+ end
47
+
48
+ def valid_policy_name!(policy_name)
49
+ return unless policy_name.to_s.include?(policy_separator_or_default)
50
+
51
+ raise("policy name #{policy_name} can not include \"#{policy_separator_or_default}\"")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EncryptedField
4
+ # EncryptedField::Encoder prepends the policy name to the encrypted string
5
+ class Encoder
6
+ class << self
7
+ attr_writer :encoder
8
+
9
+ def encoder
10
+ @encoder ||= Encoder.new
11
+ end
12
+ end
13
+
14
+ def encrypt(str, policy_name)
15
+ policy = get_policy(policy_name)
16
+
17
+ if policy.prefix_with_policy_name?
18
+ policy_name.dup << policy_separator << policy.encrypt(str)
19
+ else
20
+ policy.encrypt(str)
21
+ end
22
+ end
23
+
24
+ def decrypt(encrypted_str_with_policy_name, fallback_policy_name = nil)
25
+ policy_name, encrypted_str = encrypted_str_with_policy_name.split(policy_separator, 2)
26
+ policy_name =
27
+ if policy?(policy_name) || fallback_policy_name.nil?
28
+ policy_name
29
+ else
30
+ encrypted_str = encrypted_str_with_policy_name
31
+ fallback_policy_name
32
+ end
33
+
34
+ get_policy(policy_name).decrypt(encrypted_str)
35
+ end
36
+
37
+ private
38
+
39
+ def config
40
+ Config.instance
41
+ end
42
+
43
+ def policy_separator
44
+ config.policy_separator_or_default
45
+ end
46
+
47
+ def get_policy(policy_name)
48
+ config.policies[policy_name] || raise("missing policy #{policy_name}")
49
+ end
50
+
51
+ def policy?(policy_name)
52
+ config.policies.key?(policy_name)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EncryptedField
4
+ # EncryptedField::Field adds the plain text methods to the class.
5
+ # Useful for hard coding the policy_name to the field during class creation.
6
+ class Field
7
+ attr_reader :field_name,
8
+ :encrypted_field_name,
9
+ :policy_name,
10
+ :fallback_policy_name
11
+
12
+ def initialize(field_name, encrypted_field_name, policy_name, fallback_policy_name = nil)
13
+ @field_name = field_name
14
+ @encrypted_field_name = encrypted_field_name
15
+ @policy_name = policy_name.to_s
16
+ @fallback_policy_name = fallback_policy_name && fallback_policy_name.to_s
17
+ end
18
+
19
+ def add_methods(klass)
20
+ klass.class_eval <<METHODS, __FILE__, __LINE__ + 1
21
+ def #{field_name}
22
+ #{encrypted_field_name} && ::EncryptedField::Encoder.encoder.decrypt(#{encrypted_field_name}, #{fallback_policy_name.inspect})
23
+ end
24
+
25
+ def #{field_name}=(v)
26
+ self.#{encrypted_field_name} = v && ::EncryptedField::Encoder.encoder.encrypt(v, #{policy_name.inspect})
27
+ end
28
+ METHODS
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module EncryptedField
7
+ # EncryptedField::PolicyWithIV all the logic required to encrypt/decrypt data using symmetric encryption.
8
+ class PolicyWithIV < BasePolicy
9
+ DEFAULT_SEPARATOR = '.'
10
+
11
+ def encrypt(str)
12
+ cipher = create_cipher.encrypt
13
+ cipher.key = secret_key
14
+ iv = cipher.random_iv
15
+ encrypted_str = cipher.update(str) << cipher.final
16
+ encode_iv(iv) << separator << encode_payload(encrypted_str)
17
+ end
18
+
19
+ def decrypt(encrypted_str)
20
+ iv, encrypted_str = encrypted_str.split(separator, 2)
21
+ cipher = create_cipher.decrypt
22
+ cipher.key = secret_key
23
+ cipher.iv = decode_iv(iv)
24
+ cipher.update(decode_payload(encrypted_str) << cipher.final)
25
+ end
26
+
27
+ def separator
28
+ options[:separator] || DEFAULT_SEPARATOR
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module EncryptedField
7
+ # EncryptedField::PolicyWithoutIV all the logic required to encrypt/decrypt data using symmetric encryption.
8
+ class PolicyWithoutIV < BasePolicy
9
+ def encrypt(str)
10
+ cipher = create_cipher.encrypt
11
+ cipher.key = secret_key
12
+ encode_payload(cipher.update(str) << cipher.final)
13
+ end
14
+
15
+ def decrypt(encrypted_str)
16
+ cipher = create_cipher.decrypt
17
+ cipher.key = secret_key
18
+ cipher.update(decode_payload(encrypted_str)) << cipher.final
19
+ end
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: encrypted-field
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Doug Youch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Encrypt data using policies, designed with key rotation in mind
14
+ email: dougyouch@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/encrypted-field.rb
20
+ - lib/encrypted-field/base_policy.rb
21
+ - lib/encrypted-field/config.rb
22
+ - lib/encrypted-field/encoder.rb
23
+ - lib/encrypted-field/field.rb
24
+ - lib/encrypted-field/policy_with_iv.rb
25
+ - lib/encrypted-field/policy_without_iv.rb
26
+ homepage: https://github.com/dougyouch/encrypted-field
27
+ licenses:
28
+ - MIT
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubygems_version: 3.0.8
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Creates a simple interface for encrypting fields
49
+ test_files: []