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.
- checksums.yaml +7 -0
- data/lib/encrypted-field.rb +23 -0
- data/lib/encrypted-field/base_policy.rb +69 -0
- data/lib/encrypted-field/config.rb +54 -0
- data/lib/encrypted-field/encoder.rb +55 -0
- data/lib/encrypted-field/field.rb +31 -0
- data/lib/encrypted-field/policy_with_iv.rb +31 -0
- data/lib/encrypted-field/policy_without_iv.rb +21 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -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: []
|