forked from diffcrypt/diffcrypt-ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencryptor.rb
More file actions
111 lines (96 loc) · 3.87 KB
/
encryptor.rb
File metadata and controls
111 lines (96 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# rubocop:disable Layout/LineLength
# frozen_string_literal: true
require 'pathname'
require 'tmpdir'
require 'securerandom'
require 'yaml'
require 'active_support' # NOTE: This is required because of a bug in 7.1 which needs deprecation libs
require 'active_support/message_encryptor'
require_relative './version'
module Diffcrypt
class Encryptor
DEFAULT_CIPHER = 'aes-256-gcm'
def self.generate_key(cipher = DEFAULT_CIPHER)
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(cipher))
end
def initialize(key, cipher: DEFAULT_CIPHER)
@key = key
@cipher = cipher
@encryptor ||= ActiveSupport::MessageEncryptor.new([key].pack('H*'), cipher: cipher)
end
# @param [String] contents The raw YAML string to be encrypted
def decrypt(contents)
yaml = YAML.safe_load contents
decrypted = decrypt_hash yaml['data']
YAML.dump decrypted
end
# @param [Hash] data
# @return [Hash]
# rubocop:disable Metrics/MethodLength
def decrypt_hash(data)
data.each do |key, value|
data[key] = case value
when Hash
decrypt_hash(value)
when Array
value.map { |v| decrypt_hash(v) }
else
decrypt_string value
end
end
data
end
# rubocop:enable Metrics/MethodLength
# @param [String] contents The raw YAML string to be encrypted
# @param [String, nil] original_encrypted_contents The original (encrypted) content to determine which keys have changed
# @return [String]
def encrypt(contents, original_encrypted_contents = nil, cipher: nil)
data = encrypt_data contents, original_encrypted_contents
YAML.dump(
'client' => "diffcrypt-#{Diffcrypt::VERSION}",
'cipher' => cipher || @cipher,
'data' => data,
)
end
# @param [String] contents The raw YAML string to be encrypted
# @param [String, nil] original_encrypted_contents The original (encrypted) content to determine which keys have changed
# @return [Hash] Encrypted hash containing the data
def encrypt_data(contents, original_encrypted_contents = nil)
yaml = YAML.safe_load contents
original_yaml = original_encrypted_contents ? YAML.safe_load(original_encrypted_contents)['data'] : nil
encrypt_values yaml, original_yaml
end
# @param [String] value Plain text string that needs encrypting
# @return [String]
def encrypt_string(value)
@encryptor.encrypt_and_sign value
end
# TODO: Fix the complexity of this method
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
# @param [Hash] keys
# @return [Hash]
def encrypt_values(data, original_data = nil)
data.each do |key, value|
original_encrypted_value = original_data&.dig(key)
data[key] = case value
when Hash
encrypt_values(value, original_encrypted_value)
when Array
value.map.with_index { |v, i| encrypt_values(v, original_encrypted_value&.dig(i)) }
else
original_decrypted_value = original_encrypted_value ? decrypt_string(original_encrypted_value) : nil
key_changed = original_decrypted_value.nil? || original_decrypted_value != value
key_changed ? encrypt_string(value) : original_encrypted_value
end
end
data.sort.to_h
end
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
# @param [String] value The encrypted value that needs decrypting
# @return [String]
def decrypt_string(value)
@encryptor.decrypt_and_verify value
end
end
end
# rubocop:enable Layout/LineLength