Class: Familia::Encryption::Providers::SecureXChaCha20Poly1305Provider

Inherits:
Familia::Encryption::Provider show all
Includes:
Blake2bPersonalization
Defined in:
lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb

Overview

Enhanced XChaCha20Poly1305Provider with improved memory security

While complete avoidance of Ruby strings for secrets is challenging due to RbNaCl's internal implementation, this provider implements several security improvements:

  1. Minimizes key lifetime in memory
  2. Uses immediate secure wiping after operations
  3. Avoids unnecessary key duplication
  4. Uses locked memory where possible (future enhancement)

Constant Summary collapse

ALGORITHM =
'xchacha20poly1305-secure'
NONCE_SIZE =
24
AUTH_TAG_SIZE =
16
KEY_SIZE =
32

Constants included from Blake2bPersonalization

Blake2bPersonalization::LEGACY_PERSONALIZATION

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Blake2bPersonalization

#current_personalization, #personalizations

Constructor Details

This class inherits a constructor from Familia::Encryption::Provider

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 59

def self.available?
  !!defined?(RbNaCl) && !!defined?(FFI)
end

.dependency_hintObject



63
64
65
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 63

def self.dependency_hint
  'rbnacl/libsodium and ffi'
end

.priorityObject



67
68
69
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 67

def self.priority
  110 # Higher than regular XChaCha20Poly1305Provider
end

Instance Method Details

#decrypt(ciphertext, key, nonce, auth_tag, additional_data = nil) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 86

def decrypt(ciphertext, key, nonce, auth_tag, additional_data = nil)
  validate_key_length!(key)

  # Minimize key exposure by performing operation immediately
  begin
    result = perform_decryption(ciphertext, key, nonce, auth_tag, additional_data)
  ensure
    # Attempt to clear the key parameter (if mutable)
    secure_wipe(key)
  end

  result
end

#derive_key(master_key, context, personal: nil) ⇒ Object

Enhanced key derivation with immediate cleanup.

Personalization resolution and validation are shared with XChaCha20Poly1305Provider via Blake2bPersonalization -- this file has drifted from its sibling before (#250, #356), so the two providers must stay on the identical code path (#333).



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 110

def derive_key(master_key, context, personal: nil)
  validate_key_length!(master_key)

  # validate_personalization! guards explicitly-passed candidates with
  # the same fail-closed checks as the config path (defense in depth --
  # #personalizations already filters the decrypt walk).
  raw_personal = personal ? validate_personalization!(personal) : current_personalization
  personal_string = raw_personal.ljust(16, "\0")

  # Perform derivation and immediately clear intermediate values
  derived_key = RbNaCl::Hash.blake2b(
    # to_s before .b: tolerate non-String contexts (Symbol, nil) and
    # always operate on a fresh BINARY copy. force_encoding mutates its
    # receiver, which flipped the caller's context string to BINARY and
    # raised FrozenError on frozen literals (#356, mirroring the fix
    # made to XChaCha20Poly1305Provider in #250).
    context.to_s.b,
    key: master_key,
    digest_size: KEY_SIZE,
    personal: personal_string,
  )

  # Clear personalization string from memory
  personal_string.clear

  # Return derived key (caller responsible for secure cleanup)
  derived_key
end

#encrypt(plaintext, key, additional_data = nil) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 71

def encrypt(plaintext, key, additional_data = nil)
  validate_key_length!(key)

  # Generate nonce first to avoid holding onto key longer than necessary
  nonce = generate_nonce

  # Minimize key exposure by performing operation immediately
  result = perform_encryption(plaintext, key, nonce, additional_data)

  # Attempt to clear the key parameter (if mutable)
  secure_wipe(key)

  result
end

#generate_nonceObject



100
101
102
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 100

def generate_nonce
  RbNaCl::Random.random_bytes(NONCE_SIZE)
end

#secure_wipe(key) ⇒ Object

Clear key from memory (still no security guarantees in Ruby)



140
141
142
# File 'lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb', line 140

def secure_wipe(key)
  key&.clear
end