SnmpKit.SnmpLib.Security.Keys (snmpkit v0.6.3)

Key derivation and management for SNMPv3 User Security Model.

Implements RFC 3414 compliant key derivation functions for converting user passwords into cryptographic keys suitable for authentication and privacy operations.

Key Derivation Process

SNMPv3 uses a two-step key derivation process:

  1. Password Localization: Transform user password into a localized key using the authoritative engine ID
  2. Key Expansion: Derive authentication and privacy keys from the localized key based on protocol requirements

Security Properties

  • Keys are derived deterministically from passwords and engine IDs
  • Different engine IDs produce different keys for the same password
  • Key derivation uses cryptographic hash functions for security
  • Derived keys cannot be used to recover original passwords
  • Each protocol type (auth/priv) uses different key derivation parameters

Supported Algorithms

Authentication Key Derivation

  • MD5: RFC 3414 compliant (deprecated)
  • SHA-1: RFC 3414 compliant (deprecated)
  • SHA-224: RFC 7860 compliant
  • SHA-256: RFC 7860 compliant (recommended)
  • SHA-384: RFC 7860 compliant
  • SHA-512: RFC 7860 compliant

Privacy Key Derivation

  • DES: 8-byte keys from authentication keys
  • AES-128: 16-byte keys with salt mixing
  • AES-192: 24-byte keys with salt mixing
  • AES-256: 32-byte keys with salt mixing

Usage Examples

Authentication Key Derivation

# Derive SHA-256 authentication key
engine_id = <<0x80, 0x00, 0x1f, 0x88, 0x80, 0x01, 0x02, 0x03, 0x04>>
password = "authentication_password"

{:ok, auth_key} = SnmpKit.SnmpLib.Security.Keys.derive_auth_key(:sha256, password, engine_id)

Privacy Key Derivation

# Derive AES-256 privacy key
{:ok, priv_key} = SnmpKit.SnmpLib.Security.Keys.derive_priv_key(:aes256, password, engine_id)

# Or derive from existing authentication key
{:ok, priv_key} = SnmpKit.SnmpLib.Security.Keys.derive_priv_key_from_auth(:aes256, auth_key, engine_id)

Key Validation

# Validate key strength
:ok = SnmpKit.SnmpLib.Security.Keys.validate_password_strength(password)
{:error, :too_short} = SnmpKit.SnmpLib.Security.Keys.validate_password_strength("weak")

Summary

Functions

Derives authentication key from password and engine ID.

Derives multiple authentication keys for different protocols from the same password.

Derives privacy key from password and engine ID.

Derives privacy key from an existing authentication key.

Exports derived key in a secure format for storage or transmission.

Generates a cryptographically secure random password.

Securely compares two derived keys to prevent timing attacks.

Securely wipes sensitive key material from memory.

Validates imported key against expected parameters.

Validates password strength according to SNMPv3 security guidelines.

Types

auth_protocol()

@type auth_protocol() :: :md5 | :sha1 | :sha224 | :sha256 | :sha384 | :sha512

derived_key()

@type derived_key() :: binary()

engine_id()

@type engine_id() :: binary()

password()

@type password() :: binary()

priv_protocol()

@type priv_protocol() :: :des | :aes128 | :aes192 | :aes256

salt()

@type salt() :: binary()

Functions

derive_auth_key(protocol, password, engine_id)

@spec derive_auth_key(auth_protocol(), password(), engine_id()) ::
  {:ok, derived_key()} | {:error, atom()}

Derives authentication key from password and engine ID.

Implements RFC 3414 key localization algorithm for authentication protocols. The derived key is specific to the combination of password, protocol, and engine ID.

Parameters

  • protocol: Authentication protocol (:md5, :sha1, :sha256, etc.)
  • password: User password (minimum 8 characters recommended)
  • engine_id: Authoritative engine ID (5-32 bytes)

Returns

  • {:ok, key}: Successfully derived authentication key
  • {:error, reason}: Key derivation failed

Examples

# SHA-256 authentication key (recommended)
{:ok, key} = SnmpKit.SnmpLib.Security.Keys.derive_auth_key(
  :sha256, "my_secure_password", engine_id
)

# Legacy MD5 key derivation
{:ok, key} = SnmpKit.SnmpLib.Security.Keys.derive_auth_key(
  :md5, "legacy_password", engine_id
)

derive_auth_keys_multi(protocols, password, engine_id)

@spec derive_auth_keys_multi([auth_protocol()], password(), engine_id()) ::
  {:ok, %{required(auth_protocol()) => derived_key()}} | {:error, atom()}

Derives multiple authentication keys for different protocols from the same password.

Useful when supporting multiple authentication protocols simultaneously.

Examples

protocols = [:sha256, :sha384, :sha512]
{:ok, keys} = SnmpKit.SnmpLib.Security.Keys.derive_auth_keys_multi(protocols, password, engine_id)
# Returns: %{sha256: key1, sha384: key2, sha512: key3}

derive_priv_key(protocol, password, engine_id)

@spec derive_priv_key(priv_protocol(), password(), engine_id()) ::
  {:ok, derived_key()} | {:error, atom()}

Derives privacy key from password and engine ID.

Privacy keys are derived using a combination of authentication key derivation and protocol-specific key expansion techniques.

Parameters

  • protocol: Privacy protocol (:des, :aes128, :aes192, :aes256)
  • password: User password for privacy
  • engine_id: Authoritative engine ID

Returns

  • {:ok, key}: Successfully derived privacy key
  • {:error, reason}: Key derivation failed

Examples

# AES-256 privacy key (recommended)
{:ok, key} = SnmpKit.SnmpLib.Security.Keys.derive_priv_key(
  :aes256, "privacy_password", engine_id
)

# DES privacy key (legacy)
{:ok, key} = SnmpKit.SnmpLib.Security.Keys.derive_priv_key(
  :des, "legacy_privacy_password", engine_id
)

derive_priv_key_from_auth(protocol, auth_key, engine_id)

@spec derive_priv_key_from_auth(priv_protocol(), derived_key(), engine_id()) ::
  {:ok, derived_key()} | {:error, atom()}

Derives privacy key from an existing authentication key.

More efficient when both authentication and privacy keys are needed, as it avoids repeating the expensive key localization process.

Examples

# First derive authentication key
{:ok, auth_key} = derive_auth_key(:sha256, password, engine_id)

# Then derive privacy key from auth key
{:ok, priv_key} = SnmpKit.SnmpLib.Security.Keys.derive_priv_key_from_auth(
  :aes256, auth_key, engine_id
)

export_key(key, protocol, engine_id)

@spec export_key(derived_key(), auth_protocol() | priv_protocol(), engine_id()) ::
  map()

Exports derived key in a secure format for storage or transmission.

The exported format includes metadata for proper key reconstruction while maintaining security properties.

generate_secure_password(length \\ 16)

@spec generate_secure_password(pos_integer()) :: password()

Generates a cryptographically secure random password.

Examples

password = SnmpKit.SnmpLib.Security.Keys.generate_secure_password(16)
# Returns: "K7mN9pQ2rT8vW3xZ" (example)

secure_compare(key1, key2)

@spec secure_compare(derived_key(), derived_key()) :: boolean()

Securely compares two derived keys to prevent timing attacks.

Examples

true = SnmpKit.SnmpLib.Security.Keys.secure_compare(key1, key1)
false = SnmpKit.SnmpLib.Security.Keys.secure_compare(key1, key2)

secure_wipe(key)

@spec secure_wipe(derived_key()) :: :ok

Securely wipes sensitive key material from memory.

Note: This provides best-effort memory clearing but cannot guarantee complete removal due to Erlang VM memory management.

validate_imported_key(key, metadata)

@spec validate_imported_key(derived_key(), map()) :: :ok | {:error, atom()}

Validates imported key against expected parameters.

validate_password_strength(password)

@spec validate_password_strength(password()) ::
  :ok | {:error, atom()} | {:warning, atom()}

Validates password strength according to SNMPv3 security guidelines.

Requirements

  • Minimum 8 characters (RFC recommendation)
  • Should contain mix of character types for security
  • Should not be based on dictionary words

Examples

:ok = SnmpKit.SnmpLib.Security.Keys.validate_password_strength("strong_password_123")
{:error, :too_short} = SnmpKit.SnmpLib.Security.Keys.validate_password_strength("weak")
{:warning, :weak_complexity} = SnmpKit.SnmpLib.Security.Keys.validate_password_strength("password")