Module: Familia::Encryption

Defined in:
lib/familia/encryption.rb,
lib/familia/encryption/manager.rb,
lib/familia/encryption/provider.rb,
lib/familia/encryption/registry.rb,
lib/familia/encryption_request_cache.rb,
lib/familia/encryption/providers/aes_gcm_provider.rb,
lib/familia/encryption/providers/xchacha20_poly1305_provider.rb,
lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb

Defined Under Namespace

Modules: Providers Classes: Manager, Provider, Registry

Constant Summary collapse

EncryptedData =
Data.define(:algorithm, :nonce, :ciphertext, :auth_tag, :key_version)

Class Method Summary collapse

Class Method Details

.benchmark(iterations: 1000) ⇒ Object

Benchmark available providers



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/familia/encryption.rb', line 123

def benchmark(iterations: 1000)
  require 'benchmark'
  test_data = 'x' * 1024 # 1KB test
  context = 'benchmark:test'

  results = {}
  Registry.providers.each do |algo, provider_class|
    next unless provider_class.available?

    mgr = Manager.new(algorithm: algo)
    time = Benchmark.realtime do
      iterations.times do
        encrypted = mgr.encrypt(test_data, context: context)
        mgr.decrypt(encrypted, context: context)
      end
    end

    results[algo] = {
      time: time,
      ops_per_sec: (iterations * 2 / time).round,
      priority: provider_class.priority
    }
  end

  results
end

.clear_request_cache!Object

Clear all cached keys and disable caching



29
30
31
32
33
34
35
36
# File 'lib/familia/encryption_request_cache.rb', line 29

def clear_request_cache!
  if (cache = Thread.current[:familia_request_cache])
    cache.each_value { |key| secure_wipe(key) }
    cache.clear
  end
  Thread.current[:familia_request_cache_enabled] = false
  Thread.current[:familia_request_cache] = nil
end

.decrypt(encrypted_json, context:, additional_data: nil) ⇒ Object

Quick decryption (auto-detects algorithm from data)



75
76
77
# File 'lib/familia/encryption.rb', line 75

def decrypt(encrypted_json, context:, additional_data: nil)
  manager.decrypt(encrypted_json, context: context, additional_data: additional_data)
end

.derivation_countObject

Derivation counter for monitoring no-caching behavior



89
90
91
# File 'lib/familia/encryption.rb', line 89

def derivation_count
  @derivation_count ||= Concurrent::AtomicFixnum.new(0)
end

.encrypt(plaintext, context:, additional_data: nil) ⇒ Object

Quick encryption with auto-selected best provider



70
71
72
# File 'lib/familia/encryption.rb', line 70

def encrypt(plaintext, context:, additional_data: nil)
  manager.encrypt(plaintext, context: context, additional_data: additional_data)
end

.encrypt_with(algorithm, plaintext, context:, additional_data: nil) ⇒ Object

Encrypt with specific algorithm



80
81
82
83
84
85
86
# File 'lib/familia/encryption.rb', line 80

def encrypt_with(algorithm, plaintext, context:, additional_data: nil)
  manager(algorithm: algorithm).encrypt(
    plaintext,
    context: context,
    additional_data: additional_data
  )
end

.hardware_acceleration?Boolean

Check if we’re using hardware acceleration

Returns:

  • (Boolean)


117
118
119
120
# File 'lib/familia/encryption.rb', line 117

def hardware_acceleration?
  provider = Registry.default_provider
  provider && provider.class.name.include?('Hardware')
end

.manager(algorithm: nil) ⇒ Object

Get or create a manager with specific algorithm



64
65
66
67
# File 'lib/familia/encryption.rb', line 64

def manager(algorithm: nil)
  @managers ||= {}
  @managers[algorithm] ||= Manager.new(algorithm: algorithm)
end

.reset_derivation_count!Object



93
94
95
# File 'lib/familia/encryption.rb', line 93

def reset_derivation_count!
  derivation_count.value = 0
end

.secure_wipe(key) ⇒ Object

Clear key from memory (no security guarantees in Ruby)



98
99
100
# File 'lib/familia/encryption.rb', line 98

def secure_wipe(key)
  key&.clear
end

.statusObject

Get info about current encryption setup



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/familia/encryption.rb', line 103

def status
  Registry.setup! if Registry.providers.empty?

  {
    default_algorithm: Registry.default_provider&.algorithm,
    available_algorithms: Registry.available_algorithms,
    preferred_available: Registry.default_provider&.class&.name,
    using_hardware: hardware_acceleration?,
    key_versions: encryption_keys.keys,
    current_version: current_key_version
  }
end

.validate_configuration!Object

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/familia/encryption.rb', line 150

def validate_configuration!
  raise EncryptionError, 'No encryption keys configured' if encryption_keys.empty?
  raise EncryptionError, 'No current key version set' unless current_key_version

  current_key = encryption_keys[current_key_version]
  raise EncryptionError, "Current key version not found: #{current_key_version}" unless current_key

  begin
    Base64.strict_decode64(current_key)
  rescue ArgumentError
    raise EncryptionError, 'Current encryption key is not valid Base64'
  end

  Registry.setup!
  raise EncryptionError, 'No encryption providers available' unless Registry.default_provider
end

.with_request_cacheObject

Enable request-scoped caching (opt-in for performance)



20
21
22
23
24
25
26
# File 'lib/familia/encryption_request_cache.rb', line 20

def with_request_cache
  Thread.current[:familia_request_cache_enabled] = true
  Thread.current[:familia_request_cache] = {}
  yield
ensure
  clear_request_cache!
end