Module: Familia::Encryption::Providers::Blake2bPersonalization

Included in:
SecureXChaCha20Poly1305Provider, XChaCha20Poly1305Provider
Defined in:
lib/familia/encryption/providers/blake2b_personalization.rb

Overview

Shared BLAKE2b personalization handling for the XChaCha20-Poly1305 providers (XChaCha20Poly1305Provider and its unregistered prototype sibling SecureXChaCha20Poly1305Provider). Both providers include this module so the personalization rotation logic exists exactly once -- the two files have drifted apart before (#250, #356).

Mirrors the AES-GCM HKDF salt rotation design from #310/#311: encryption uses the fail-closed current value, decryption walks a permissive candidate list ending with the pre-rotation default. See issue #333.

Constant Summary collapse

LEGACY_PERSONALIZATION =

The built-in personalization every deployment shipped with before rotation support (#333). Retained ONLY as a decryption fallback (see

personalizations) so data written under the default stays readable

after an operator rotates to a deployment-specific value. Never used to encrypt new data once the config no longer resolves to it.

'FamilialMatters'

Instance Method Summary collapse

Instance Method Details

#current_personalizationString

The personalization string used to ENCRYPT new data.

Unlike #personalizations (the permissive decryption candidate list), this fails CLOSED. A nil or empty encryption_personalization is a misconfiguration -- and the raw attr_writer can set one, bypassing the reader's guards -- so refuse rather than silently deriving under a fallback (#311). Null-byte padding to BLAKE2b's 16 bytes happens at derive time (ljust in #derive_key), not here.

Returns:

  • (String)

    The validated current personalization string

Raises:



68
69
70
# File 'lib/familia/encryption/providers/blake2b_personalization.rb', line 68

def current_personalization
  validate_personalization!(Familia.config.encryption_personalization)
end

#personalizationsArray<String>

Ordered list of personalization strings to consider when DECRYPTING, current first.

Decryption walks this list until the authenticated decrypt succeeds, so it is intentionally permissive: it reads the raw config value (not

current_personalization, which raises) and ends with the built-in

default, so existing ciphertext stays readable even if the current config is broken. A wrong personalization derives a different key and fails Poly1305 authentication cleanly, so iterating never yields a false positive.

The filter is load-bearing, not cosmetic: a candidate that is not a String, is blank, contains null bytes, or exceeds BLAKE2b's 16-byte personalization limit would raise mid-walk (EncryptionError or RbNaCl::LengthError) and abort the remaining candidates, so such entries are dropped here instead of attempted.

ENCRYPTION does NOT use this list's head -- see

current_personalization, which fails closed rather than silently

encrypting under a fallback value.

Returns:

  • (Array<String>)

    Valid candidates, current first, deduplicated



48
49
50
51
52
53
54
55
# File 'lib/familia/encryption/providers/blake2b_personalization.rb', line 48

def personalizations
  current = Familia.config.encryption_personalization
  history = Familia.config.encryption_personalization_history
  [current, *history, LEGACY_PERSONALIZATION].select do |candidate|
    candidate.is_a?(String) && !candidate.empty? &&
      !candidate.include?("\0") && candidate.bytesize <= 16
  end.uniq
end