76 lines
1.8 KiB
Ruby
76 lines
1.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class EncryptionService
|
|
# Example: 202040
|
|
# Week number starts from 0
|
|
def self.current_salt
|
|
Time.now.strftime('%Y%W')
|
|
end
|
|
|
|
def self.pack(salt, value)
|
|
[salt, value].join('.')
|
|
end
|
|
|
|
def self.unpack(str)
|
|
raise "Invalid encrypted value: #{str}" unless str =~ (/(\w*)\.(.*)/)
|
|
[$1, $2]
|
|
end
|
|
|
|
def self.encrypt(value)
|
|
# Get current salt
|
|
salt = current_salt
|
|
# Get or generate new master key from salt
|
|
current_key = get_master_key(salt)
|
|
# Encrypt attribute value
|
|
encrypted_key = encryptor(current_key).encrypt_and_sign(value)
|
|
# Add salt before encrypted value
|
|
pack(salt, encrypted_key)
|
|
end
|
|
|
|
def self.decrypt(value)
|
|
# Unpack salt and encrypted_key
|
|
salt, encrypted_key = unpack(value)
|
|
# Get master key from salt
|
|
master_key = get_master_key(salt)
|
|
# Decrypt encrypted value for attribute
|
|
encryptor(master_key).decrypt_and_verify(encrypted_key)
|
|
end
|
|
|
|
private
|
|
|
|
def self.encryptor(key)
|
|
ActiveSupport::MessageEncryptor.new(key)
|
|
end
|
|
|
|
def self.get_master_key(salt)
|
|
@cache ||= {}
|
|
# Delete keys with expired date
|
|
delete_expired_keys
|
|
|
|
unless @cache[salt]
|
|
# Initialize hash for specific salt
|
|
@cache[salt] = {}
|
|
|
|
# Put key value from key generator
|
|
@cache[salt]['key'] = ActiveSupport::KeyGenerator.new(
|
|
ENV.fetch('SECRET_KEY_BASE')
|
|
).generate_key(salt, ActiveSupport::MessageEncryptor.key_len)
|
|
|
|
# Put expire date for specific key
|
|
@cache[salt]['expire_date'] = 1.week.from_now
|
|
end
|
|
|
|
@cache[salt]['key']
|
|
end
|
|
|
|
def self.delete_expired_keys
|
|
return unless @cache.is_a?(Hash)
|
|
|
|
# Iterate through all @cache values
|
|
@cache.each do |salt, values|
|
|
# Delete key if expire date expired
|
|
@cache.delete(salt) if values['expire_date'].present? && values['expire_date'] < Time.now
|
|
end
|
|
end
|
|
end
|