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

Instance Attribute Summary

Attributes inherited from Familia::Encryption::Provider

#algorithm, #auth_tag_size, #nonce_size

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Familia::Encryption::Provider

#initialize

Constructor Details

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

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 38

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

.priorityObject



42
43
44
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 42

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

Instance Method Details

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



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

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



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 88

def derive_key(master_key, context, personal: nil)
  validate_key_length!(master_key)
  raw_personal = personal || Familia.config.encryption_personalization
  if raw_personal.include?("\0")
    raise EncryptionError, 'Personalization string must not contain null bytes'
  end
  personal_string = raw_personal.ljust(16, "\0")

  RbNaCl::Hash.blake2b(
    context.force_encoding('BINARY'),
    key: master_key,
    digest_size: 32,
    personal: personal_string
  )
end

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



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

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..-1],
    nonce: nonce
  }
end

#generate_nonceObject



73
74
75
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 73

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

#secure_wipe(key) ⇒ Object

Clear key from memory (no security guarantees in Ruby)



105
106
107
# File 'lib/familia/encryption/providers/xchacha20_poly1305_provider.rb', line 105

def secure_wipe(key)
  key&.clear
end