encrypted-field 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []