Class: Familia::Encryption::Providers::XChaCha20Poly1305Provider

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

Constant Summary collapse

ALGORITHM =
'xchacha20poly1305'.freeze
NONCE_SIZE =
24
AUTH_TAG_SIZE =
16

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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

Class Method Details

.auth_tag_sizeObject



123
124
125
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 123

def self.auth_tag_size
  AUTH_TAG_SIZE
end

.available?Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 40

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

.nonce_sizeObject



119
120
121
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 119

def self.nonce_size
  NONCE_SIZE
end

.priorityObject



44
45
46
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 44

def self.priority
  100 # Highest priority - best in class
end

Instance Method Details

#algorithmObject



135
136
137
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 135

def algorithm
  ALGORITHM
end

#auth_tag_sizeObject



131
132
133
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 131

def auth_tag_size
  AUTH_TAG_SIZE
end

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



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 63

def decrypt(ciphertext, key, nonce, auth_tag, additional_data = nil)
  validate_key_length!(key)
  box = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)

  ciphertext_with_tag = ciphertext + auth_tag
  aad = additional_data.to_s

  box.decrypt(nonce, ciphertext_with_tag, aad)
rescue RbNaCl::CryptoError
  raise EncryptionError, 'Decryption failed - invalid key or corrupted data'
end

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

Derives a context-specific encryption key using BLAKE2b.

The personalization parameter provides cryptographic domain separation, ensuring that derived keys are unique per application even when using identical master keys and contexts. This prevents key reuse across different applications or library versions.

Parameters:

  • master_key (String)

    The master key (must be >= 32 bytes)

  • context (String)

    Context string for key derivation

  • personal (String, nil) (defaults to: nil)

    Optional personalization override

Returns:

  • (String)

    32-byte derived key

Raises:



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 90

def derive_key(master_key, context, personal: nil)
  validate_key_length!(master_key)
  raw_personal = personal || Familia.config.encryption_personalization
  # Fail closed on a missing personalization rather than crashing with a
  # NoMethodError on nil (#311): a blank value gives no domain separation,
  # and the raw attr_writer can set nil/'' past the reader's guards.
  unless raw_personal.is_a?(String) && !raw_personal.empty?
    raise EncryptionError, 'encryption_personalization must be a non-empty string for key derivation'
  end
  raise EncryptionError, 'Personalization string must not contain null bytes' if raw_personal.include?("\0")

  personal_string = raw_personal.ljust(16, "\0")

  RbNaCl::Hash.blake2b(
    # to_s before .b: tolerate non-String contexts (Symbol, nil) and
    # always operate on a fresh BINARY copy (never mutate a frozen
    # caller string -- see issue #250 / FrozenError in benchmark).
    context.to_s.b,
    key: master_key,
    digest_size: 32,
    personal: personal_string
  )
end

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



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 48

def encrypt(plaintext, key, additional_data = nil)
  validate_key_length!(key)
  nonce = generate_nonce
  box = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)

  aad = additional_data.to_s
  ciphertext_with_tag = box.encrypt(nonce, plaintext.to_s, aad)

  {
    ciphertext: ciphertext_with_tag[0...-16],
    auth_tag: ciphertext_with_tag[-16..],
    nonce: nonce,
  }
end

#generate_nonceObject



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

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

#nonce_sizeObject



127
128
129
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 127

def nonce_size
  NONCE_SIZE
end

#secure_wipe(key) ⇒ Object

Clear key from memory (no security guarantees in Ruby)



115
116
117
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 115

def secure_wipe(key)
  key&.clear
end