SnmpKit.SnmpLib.Security.Priv (snmpkit v1.4.0)
Implements SNMPv3 privacy protocols for message encryption and decryption.
This module provides support for standard SNMPv3 privacy protocols like DES and AES, ensuring data confidentiality in SNMP communications.
Supported Protocols
:none- No privacy:des- DES-CBC (56-bit), RFC 3414 section 8:aes128- AES-CFB128 (128-bit), RFC 3826:aes192- AES-CFB128 (192-bit), draft-blumenthal-aes-usm / draft-reeder-snmpv3-usm-3desede:aes256- AES-CFB128 (256-bit), same as above
Security Considerations
- DES is considered weak and should only be used for compatibility with legacy devices.
- AES protocols are recommended for strong encryption.
- Keys should be derived securely using the functions in
SnmpKit.SnmpLib.Security.Keys. - Privacy provides confidentiality only. Integrity comes from authentication;
CFB-mode decryption with a wrong key silently yields garbage, which is why
SNMPv3 forbids
privwithoutauth.
Protocol Selection Guidelines
- For new deployments, prefer
:aes256for the strongest security. - Use
:aes128for a balance of performance and security. - Use
:desonly when required for interoperability.
Technical Details
This module implements the privacy aspects of the User-Based Security Model (USM) as defined in RFC 3414 and RFC 3826.
Keys
Privacy keys are localized keys produced by SnmpKit.SnmpLib.Security.Keys.
- DES uses a 16-octet key: the first 8 octets are the DES key, the last 8 octets are the "pre-IV" (RFC 3414 section 8.1.1.1).
- AES uses a 16, 24 or 32 octet key.
Initialization Vectors and privParameters
IVs are not random. They are derived from the authoritative engine's
snmpEngineBoots / snmpEngineTime and a per-message salt, and only the salt
travels on the wire as msgPrivacyParameters (8 octets for every protocol):
- DES (RFC 3414 section 8.1.1.1):
salt = engineBoots(4) || localInt(4),IV = preIV XOR salt. - AES (RFC 3826 section 3.1.2.1):
IV = engineBoots(4) || engineTime(4) || salt(8).
The local integer is a 64-bit counter seeded randomly at first use and
incremented for every message, so a (key, IV) pair is never reused.
Padding
DES-CBC requires the plaintext to be a multiple of 8 octets. RFC 3414 pads
with arbitrary octets and does not transmit the padding length; the receiver
relies on the BER length of the decrypted ScopedPDU. decrypt/6 therefore
returns the padded plaintext for DES, and callers decode it as a BER SEQUENCE
(trailing octets are ignored). AES-CFB is a stream mode and needs no padding.
Usage Examples
This module is typically used internally by the USM module.
Message Encryption
{:ok, {ciphertext, priv_params}} = SnmpKit.SnmpLib.Security.Priv.encrypt(
:aes256, priv_key, auth_key, scoped_pdu,
engine_boots: boots, engine_time: time
)
{:ok, decrypted} = SnmpKit.SnmpLib.Security.Priv.decrypt(
:aes256, priv_key, auth_key, ciphertext, priv_params,
engine_boots: boots, engine_time: time
)Protocol Information
iex> SnmpKit.SnmpLib.Security.Priv.protocol_info(:aes128)
%{algorithm: :aes_128_cfb128, key_size: 16, iv_size: 16, block_size: 16, priv_params_size: 8}
Summary
Functions
Benchmarks the performance of a given privacy protocol.
Decrypts ciphertext using the specified privacy protocol.
Decrypts a batch of ciphertexts efficiently.
Encrypts plaintext using the specified privacy protocol.
Encrypts a batch of plaintexts efficiently.
Retrieves the specification for a given privacy protocol.
Checks if a protocol is considered cryptographically secure.
Returns a list of cryptographically secure protocols.
Returns a list of all supported privacy protocols.
Validates if a privacy key is compliant with the protocol's requirements.
Types
@type auth_key() :: binary()
@type ciphertext() :: binary()
@type initialization_vector() :: binary()
@type plaintext() :: binary()
@type priv_key() :: binary()
@type priv_opts() :: [ engine_boots: non_neg_integer(), engine_time: non_neg_integer(), salt: binary() | non_neg_integer() ]
@type priv_params() :: binary()
@type priv_protocol() :: :none | :des | :aes128 | :aes192 | :aes256
Functions
@spec benchmark_protocol( priv_protocol(), priv_key(), auth_key(), plaintext(), non_neg_integer() ) :: %{encrypt_us: float(), decrypt_us: float(), ops_per_sec: float()}
Benchmarks the performance of a given privacy protocol.
@spec decrypt( priv_protocol(), priv_key(), auth_key(), ciphertext(), priv_params(), priv_opts() ) :: {:ok, plaintext()} | {:error, atom()}
Decrypts ciphertext using the specified privacy protocol.
Parameters
protocol: Privacy protocol used for encryptionpriv_key: Localized privacy key (same as used for encryption)auth_key: Authentication key (unused by the RFC algorithms, kept for API compatibility)ciphertext: Encrypted datapriv_params: The 8-octetmsgPrivacyParameterssalt from the received messageopts::engine_boots/:engine_timeas carried in the receivedmsgAuthoritativeEngineBoots/msgAuthoritativeEngineTime(needed for AES)
Returns
{:ok, plaintext}: Decryption successful. For DES the result still carries the RFC 3414 block padding; decode it as a BER SEQUENCE and ignore the tail.{:error, reason}: Decryption failed
Note that a wrong key cannot be detected here: CFB and unpadded CBC produce garbage rather than an error. Verify authentication before decrypting.
@spec decrypt_batch( priv_protocol(), priv_key(), auth_key(), [{ciphertext(), priv_params()}], priv_opts() ) :: [ok: plaintext(), error: atom()]
Decrypts a batch of ciphertexts efficiently.
@spec encrypt(priv_protocol(), priv_key(), auth_key(), plaintext(), priv_opts()) :: {:ok, {ciphertext(), priv_params()}} | {:error, atom()}
Encrypts plaintext using the specified privacy protocol.
Parameters
protocol: Privacy protocol to usepriv_key: Localized privacy key for the chosen protocolauth_key: Authentication key (unused by the RFC algorithms, kept for API compatibility)plaintext: Data to encrypt (normally the BER-encoded ScopedPDU)opts::engine_boots- authoritativesnmpEngineBootsplaced in the message (default 0):engine_time- authoritativesnmpEngineTimeplaced in the message (default 0):salt- explicit 8-octet salt / 64-bit integer, mainly for tests; a fresh counter value is used when omitted
Returns
{:ok, {ciphertext, priv_params}}: Encryption successful.priv_paramsis the 8-octet salt to send asmsgPrivacyParameters.{:error, reason}: Encryption failed
Examples
{:ok, {ciphertext, priv_params}} = SnmpKit.SnmpLib.Security.Priv.encrypt(
:aes128, priv_key, auth_key, scoped_pdu, engine_boots: 3, engine_time: 1200
)
@spec encrypt_batch( priv_protocol(), priv_key(), auth_key(), [plaintext()], priv_opts() ) :: {:ok, [{ciphertext(), priv_params()}]} | {:error, atom()}
Encrypts a batch of plaintexts efficiently.
Examples
iex> plaintexts = ["msg1", "msg2"]
iex> {:ok, encrypted_list} = Priv.encrypt_batch(:aes128, priv_key, auth_key, plaintexts)
iex> length(encrypted_list)
2
@spec protocol_info(priv_protocol()) :: map() | nil
Retrieves the specification for a given privacy protocol.
Returns a map with :algorithm, :key_size, :iv_size, :block_size and
:priv_params_size, or nil if the protocol is unsupported.
Examples
iex> Priv.protocol_info(:aes128)
%{algorithm: :aes_128_cfb128, key_size: 16, iv_size: 16, block_size: 16, priv_params_size: 8}
iex> Priv.protocol_info(:unsupported)
nil
@spec secure_protocol?(priv_protocol()) :: boolean()
Checks if a protocol is considered cryptographically secure.
@spec secure_protocols() :: [priv_protocol()]
Returns a list of cryptographically secure protocols.
@spec supported_protocols() :: [priv_protocol()]
Returns a list of all supported privacy protocols.
@spec validate_key(priv_protocol(), priv_key()) :: :ok | {:error, atom()}
Validates if a privacy key is compliant with the protocol's requirements.
Examples
iex> Priv.validate_key(:aes128, :crypto.strong_rand_bytes(16))
:ok
iex> Priv.validate_key(:des, <<1, 2, 3>>)
{:error, :invalid_key_size}