.. index:: single: crypto
.. _crypto/0:

.. rst-class:: right

**object**

``crypto``
==========

Transport-neutral cryptographic helper predicates.

| **Availability:** 
|    ``logtalk_load(crypto(loader))``

| **Author:** Paulo Moura
| **Version:** 1:0:0
| **Date:** 2026-07-20

| **Compilation flags:**
|    ``static, context_switching_calls``


| **Uses:**
|    :ref:`base64url_no_padding <base64url_no_padding/0>`
|    :ref:`fast_random(Algorithm) <fast_random/1>`
|    :ref:`hmac <hmac/0>`
|    :ref:`list <list/0>`
|    :ref:`md5 <md5/0>`
|    :ref:`os <os/0>`
|    :ref:`type <type/0>`

| **Remarks:**
|    (none)

| **Inherited public predicates:**
|    (none)

.. contents::
   :local:
   :backlinks: top

Public predicates
-----------------

.. index:: random_bytes/2
.. _crypto/0::random_bytes/2:

``random_bytes/2``
^^^^^^^^^^^^^^^^^^

Returns a list with the requested number of random bytes. It tries ``/dev/urandom`` first and falls back to a pseudo-random generator when necessary.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``random_bytes(Count,Bytes)``
| **Mode and number of proofs:**
|    ``random_bytes(+non_negative_integer,-list(byte))`` - ``one_or_error``

| **Exceptions:**
|    ``Count`` is a variable:
|        ``instantiation_error``
|    ``Count`` is neither a variable nor an integer:
|        ``type_error(integer,Count)``
|    ``Count`` is an integer but is less than zero:
|        ``domain_error(non_negative_integer,Count)``


------------

.. index:: token_hex/2
.. _crypto/0::token_hex/2:

``token_hex/2``
^^^^^^^^^^^^^^^

Returns a lowercase hexadecimal token generated from the requested number of random bytes.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``token_hex(Count,Token)``
| **Mode and number of proofs:**
|    ``token_hex(+non_negative_integer,-atom)`` - ``one_or_error``

| **Exceptions:**
|    ``Count`` is a variable:
|        ``instantiation_error``
|    ``Count`` is neither a variable nor an integer:
|        ``type_error(integer,Count)``
|    ``Count`` is an integer but is less than zero:
|        ``domain_error(non_negative_integer,Count)``


------------

.. index:: token_urlsafe/2
.. _crypto/0::token_urlsafe/2:

``token_urlsafe/2``
^^^^^^^^^^^^^^^^^^^

Returns an unpadded Base64URL token generated from the requested number of random bytes.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``token_urlsafe(Count,Token)``
| **Mode and number of proofs:**
|    ``token_urlsafe(+non_negative_integer,-atom)`` - ``one_or_error``

| **Exceptions:**
|    ``Count`` is a variable:
|        ``instantiation_error``
|    ``Count`` is neither a variable nor an integer:
|        ``type_error(integer,Count)``
|    ``Count`` is an integer but is less than zero:
|        ``domain_error(non_negative_integer,Count)``


------------

.. index:: random_below/2
.. _crypto/0::random_below/2:

``random_below/2``
^^^^^^^^^^^^^^^^^^

Returns a uniformly distributed random integer greater than or equal to zero and less than the given exclusive upper bound.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``random_below(UpperBound,Integer)``
| **Mode and number of proofs:**
|    ``random_below(+positive_integer,-non_negative_integer)`` - ``one_or_error``

| **Exceptions:**
|    ``UpperBound`` is a variable:
|        ``instantiation_error``
|    ``UpperBound`` is neither a variable nor an integer:
|        ``type_error(integer,UpperBound)``
|    ``UpperBound`` is an integer but is not positive:
|        ``domain_error(positive_integer,UpperBound)``


------------

.. index:: hex_bytes/2
.. _crypto/0::hex_bytes/2:

``hex_bytes/2``
^^^^^^^^^^^^^^^

Relates a hexadecimal atom with the corresponding list of bytes, generating lowercase hexadecimal atoms when converting from bytes.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``hex_bytes(Hex,Bytes)``
| **Mode and number of proofs:**
|    ``hex_bytes(+atom,-list(byte))`` - ``zero_or_one_or_error``
|    ``hex_bytes(-atom,+list(byte))`` - ``zero_or_one_or_error``

| **Exceptions:**
|    ``Hex`` and ``Bytes`` are both variables:
|        ``instantiation_error``
|    ``Hex`` is neither a variable nor an atom:
|        ``type_error(atom,Hex)``
|    ``Hex`` is an atom but not a valid hexadecimal atom:
|        ``domain_error(hexadecimal_atom,Hex)``
|    ``Bytes`` is neither a variable nor a list of bytes:
|        ``type_error(list(byte),Bytes)``
|    ``Bytes`` contains a variable byte:
|        ``instantiation_error``
|    ``Bytes`` contains a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``Bytes`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``


------------

.. index:: secure_compare/2
.. _crypto/0::secure_compare/2:

``secure_compare/2``
^^^^^^^^^^^^^^^^^^^^

Succeeds when both inputs are equal using a constant-time comparison strategy for byte sequences of the same representation.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``secure_compare(Expected,Candidate)``
| **Mode and number of proofs:**
|    ``secure_compare(+list(byte),+list(byte))`` - ``zero_or_one_or_error``
|    ``secure_compare(+atom,+atom)`` - ``zero_or_one_or_error``

| **Exceptions:**
|    ``Expected`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Expected`` is neither an atom nor a list of bytes:
|        ``type_error(list(byte),Expected)``
|    ``Expected`` is an atom and ``Candidate`` is not an atom:
|        ``type_error(atom,Candidate)``
|    ``Candidate`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Candidate`` is neither an atom nor a list of bytes when ``Expected`` is a byte list:
|        ``type_error(list(byte),Candidate)``
|    ``Expected`` or ``Candidate`` contains a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``Expected`` or ``Candidate`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``


------------

.. index:: hkdf/5
.. _crypto/0::hkdf/5:

``hkdf/5``
^^^^^^^^^^

Derives a byte sequence of the requested length from input keying material using HKDF with a hash object implementing the ``hash_digest_protocol`` protocol.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``hkdf(Hash,KeyMaterial,Length,Bytes,Options)``
| **Mode and number of proofs:**
|    ``hkdf(+object_identifier,+list(byte),+non_negative_integer,-list(byte),+list(compound))`` - ``one_or_error``

| **Exceptions:**
|    ``Hash`` is a variable:
|        ``instantiation_error``
|    ``Hash`` is not an object implementing the ``hash_digest_protocol`` protocol:
|        ``domain_error(crypto_hash,Hash)``
|    ``KeyMaterial`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``KeyMaterial`` is a list but not a list of bytes:
|        ``type_error(list(byte),KeyMaterial)``
|    ``KeyMaterial`` contains a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``KeyMaterial`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Length`` is a variable:
|        ``instantiation_error``
|    ``Length`` is neither a variable nor an integer:
|        ``type_error(integer,Length)``
|    ``Length`` is less than zero:
|        ``domain_error(non_negative_integer,Length)``
|    ``Length`` exceeds the maximum HKDF output length for the selected hash:
|        ``domain_error(hkdf_output_length(0,MaxLength),Length)``
|    ``Options`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Options`` is neither a variable nor a list of compound terms:
|        ``type_error(list(compound),Options)``
|    ``Options`` contains an invalid option term:
|        ``domain_error(hkdf_option,Option)``
|    ``Options`` contains a ``salt/1`` or ``info/1`` value that is not a list of bytes:
|        ``type_error(list(byte),Bytes)``
|    ``Options`` contains a ``salt/1`` or ``info/1`` value with a variable byte:
|        ``instantiation_error``
|    ``Options`` contains a ``salt/1`` or ``info/1`` value with a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``Options`` contains a ``salt/1`` or ``info/1`` value with an integer outside the byte range:
|        ``domain_error(byte,Byte)``

| **Remarks:**

    - Repeated options: When the same HKDF option is given multiple times, the last occurrence is used.
    - Option ``salt(Bytes)``: Uses the given byte list as the HKDF salt. When this option is absent, the salt defaults to a zero-filled byte list with the selected hash digest size.
    - Option ``info(Bytes)``: Uses the given byte list as the HKDF context information. When this option is absent, the context information defaults to the empty list.


------------

.. index:: pbkdf2/6
.. _crypto/0::pbkdf2/6:

``pbkdf2/6``
^^^^^^^^^^^^

Derives a key from a password byte sequence and a salt using PBKDF2 with a hash object implementing the ``hash_digest_protocol`` protocol.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``pbkdf2(Hash,Password,Salt,Iterations,Length,DerivedKey)``
| **Mode and number of proofs:**
|    ``pbkdf2(+object_identifier,+list(byte),+list(byte),+integer,+non_negative_integer,-list(byte))`` - ``one_or_error``

| **Exceptions:**
|    ``Hash`` is a variable:
|        ``instantiation_error``
|    ``Hash`` is not an object implementing the ``hash_digest_protocol`` protocol:
|        ``domain_error(crypto_hash,Hash)``
|    ``Password`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Password`` is a list but not a list of bytes:
|        ``type_error(list(byte),Password)``
|    ``Password`` contains a non-integer element:
|        ``type_error(integer,Byte)``
|    ``Password`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Salt`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Salt`` is a liust but not a list of bytes:
|        ``type_error(list(byte),Salt)``
|    ``Salt`` contains a non-integer element:
|        ``type_error(integer,Byte)``
|    ``Salt`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Iterations`` is a variable:
|        ``instantiation_error``
|    ``Iterations`` is neither a variable nor an integer:
|        ``type_error(integer,Iterations)``
|    ``Iterations`` is an integer but not a positive integer:
|        ``domain_error(positive_integer,Iterations)``
|    ``Length`` is a variable:
|        ``instantiation_error``
|    ``Length`` is neither a variable nor an integer:
|        ``type_error(integer,Length)``
|    ``Length`` is an integer but is less than zero:
|        ``domain_error(non_negative_integer,Length)``
|    ``Length`` exceeds the maximum PBKDF2 output length:
|        ``domain_error(pbkdf2_output_length,Length)``


------------

.. index:: apr1/3
.. _crypto/0::apr1/3:

``apr1/3``
^^^^^^^^^^

Computes the Apache APR1 encoded checksum for a password byte sequence and salt byte sequence.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``apr1(Password,Salt,Checksum)``
| **Mode and number of proofs:**
|    ``apr1(+list(byte),+list(byte),-list(byte))`` - ``one_or_error``

| **Exceptions:**
|    ``Password`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Password`` is a list but not a list of bytes:
|        ``type_error(list(byte),Password)``
|    ``Password`` contains a non-integer element:
|        ``type_error(integer,Byte)``
|    ``Password`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Salt`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Salt`` is a list but not a list of bytes:
|        ``type_error(list(byte),Salt)``
|    ``Salt`` contains a non-integer element:
|        ``type_error(integer,Byte)``
|    ``Salt`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Salt`` is not a valid APR1 salt:
|        ``domain_error(apr1_salt,Salt)``


------------

.. index:: password_hash/4
.. _crypto/0::password_hash/4:

``password_hash/4``
^^^^^^^^^^^^^^^^^^^

Computes a structured password-hash term using PBKDF2, a hash object implementing the ``hash_digest_protocol`` protocol, and the given derivation options.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``password_hash(Hash,Password,PasswordHash,Options)``
| **Mode and number of proofs:**
|    ``password_hash(+object_identifier,+list(byte),-compound,+list(compound))`` - ``one_or_error``

| **Exceptions:**
|    ``Hash`` is a variable:
|        ``instantiation_error``
|    ``Hash`` is not an object implementing the ``hash_digest_protocol`` protocol:
|        ``domain_error(crypto_hash,Hash)``
|    ``Password`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Password`` is a list but not a list of bytes:
|        ``type_error(list(byte),Password)``
|    ``Password`` contains a non-integer element:
|        ``type_error(integer,Byte)``
|    ``Password`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Options`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Options`` is a list but not a list of compound terms:
|        ``type_error(list(compound),Options)``
|    ``Options`` contains an invalid option term:
|        ``domain_error(password_hash_option,Option)``
|    ``Options`` contains an ``iterations/1`` value that is not an integer:
|        ``type_error(integer,Iterations)``
|    ``Options`` contains an ``iterations/1`` value that is not a positive integer:
|        ``domain_error(positive_integer,Iterations)``
|    ``Options`` contains a ``salt/1`` value that is not a list of bytes:
|        ``type_error(list(byte),Salt)``
|    ``Options`` contains a ``salt/1`` value with a variable byte:
|        ``instantiation_error``
|    ``Options`` contains a ``salt/1`` value with a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``Options`` contains a ``salt/1`` value with an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Options`` contains a ``salt_length/1`` or ``length/1`` value that is not an integer:
|        ``type_error(integer,Length)``
|    ``Options`` contains a ``salt_length/1`` or ``length/1`` value that is less than zero:
|        ``domain_error(non_negative_integer,Length)``

| **Remarks:**

    - Repeated options: When the same password-hash option is given multiple times, the last occurrence is used.
    - Option ``iterations(Count)``: Uses the given positive integer PBKDF2 iteration count. When this option is absent, the iteration count defaults to ``131072``.
    - Option ``salt(Bytes)``: Uses the given byte list as the PBKDF2 salt. When this option is present, no random salt is generated and any ``salt_length/1`` option only affects validation, not the selected salt value.
    - Option ``salt_length(Count)``: Generates a random salt with the given non-negative number of bytes when ``salt/1`` is absent. When this option is absent, the generated salt length defaults to ``16`` bytes.
    - Option ``length(Count)``: Uses the given non-negative derived-key length. When this option is absent, the derived-key length defaults to the selected hash digest size.


------------

.. index:: password_hash_needs_rehash/3
.. _crypto/0::password_hash_needs_rehash/3:

``password_hash_needs_rehash/3``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Succeeds when the given password-hash term does not match the selected PBKDF2 password-hash policy.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``password_hash_needs_rehash(PasswordHash,Hash,Options)``
| **Mode and number of proofs:**
|    ``password_hash_needs_rehash(+compound,+object_identifier,+list(compound))`` - ``zero_or_one_or_error``

| **Exceptions:**
|    ``PasswordHash`` is not a supported password-hash term:
|        ``domain_error(password_hash,PasswordHash)``
|    ``PasswordHash`` contains a variable hash object:
|        ``instantiation_error``
|    ``PasswordHash`` contains a hash object that does not implement the ``hash_digest_protocol`` protocol:
|        ``domain_error(crypto_hash,Hash)``
|    ``PasswordHash`` contains an ``Iterations`` value that is not an integer:
|        ``type_error(integer,Iterations)``
|    ``PasswordHash`` contains an ``Iterations`` value that is not a positive integer:
|        ``domain_error(positive_integer,Iterations)``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value that is not a list of bytes:
|        ``type_error(list(byte),Bytes)``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value with a variable byte:
|        ``instantiation_error``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value with a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value with an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``PasswordHash`` contains an invalid APR1 salt:
|        ``domain_error(apr1_salt,Salt)``
|    ``PasswordHash`` contains an invalid APR1 checksum:
|        ``domain_error(apr1_checksum,Checksum)``
|    ``Hash`` is a variable:
|        ``instantiation_error``
|    ``Hash`` is not an object implementing the ``hash_digest_protocol`` protocol:
|        ``domain_error(crypto_hash,Hash)``
|    ``Options`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Options`` is a list but not a list of compound terms:
|        ``type_error(list(compound),Options)``
|    ``Options`` contains an invalid option term:
|        ``domain_error(password_hash_option,Option)``
|    ``Options`` contains an ``iterations/1`` value that is not an integer:
|        ``type_error(integer,Iterations)``
|    ``Options`` contains an ``iterations/1`` value that is not a positive integer:
|        ``domain_error(positive_integer,Iterations)``
|    ``Options`` contains a ``salt/1`` value that is not a list of bytes:
|        ``type_error(list(byte),Salt)``
|    ``Options`` contains a ``salt/1`` value with a variable byte:
|        ``instantiation_error``
|    ``Options`` contains a ``salt/1`` value with a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``Options`` contains a ``salt/1`` value with an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``Options`` contains a ``salt_length/1`` or ``length/1`` value that is not an integer:
|        ``type_error(integer,Length)``
|    ``Options`` contains a ``salt_length/1`` or ``length/1`` value that is less than zero:
|        ``domain_error(non_negative_integer,Length)``
|    ``Options`` selects a ``length/1`` value that exceeds the maximum PBKDF2 output length:
|        ``domain_error(pbkdf2_output_length,Length)``

| **Remarks:**

    - Policy: The ``Hash`` and ``Options`` arguments use the same policy options as ``password_hash/4``.
    - Legacy hashes: Supported non-PBKDF2 password-hash terms need rehashing when valid.


------------

.. index:: verify_password_hash/2
.. _crypto/0::verify_password_hash/2:

``verify_password_hash/2``
^^^^^^^^^^^^^^^^^^^^^^^^^^

Succeeds when the password byte sequence matches the given structured password-hash term or stored digest term.

| **Compilation flags:**
|    ``static``

| **Template:**
|    ``verify_password_hash(PasswordHash,Password)``
| **Mode and number of proofs:**
|    ``verify_password_hash(+compound,+list(byte))`` - ``zero_or_one_or_error``

| **Exceptions:**
|    ``PasswordHash`` is not a supported password-hash term:
|        ``domain_error(password_hash,PasswordHash)``
|    ``PasswordHash`` contains a variable hash object:
|        ``instantiation_error``
|    ``PasswordHash`` contains a hash object that does not implement the ``hash_digest_protocol`` protocol:
|        ``domain_error(crypto_hash,Hash)``
|    ``PasswordHash`` contains an ``Iterations`` value that is not an integer:
|        ``type_error(integer,Iterations)``
|    ``PasswordHash`` contains an ``Iterations`` value that is not a positive integer:
|        ``domain_error(positive_integer,Iterations)``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value that is not a list of bytes:
|        ``type_error(list(byte),Bytes)``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value with a variable byte:
|        ``instantiation_error``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value with a non-integer byte:
|        ``type_error(integer,Byte)``
|    ``PasswordHash`` contains a ``Salt``, ``StoredKey``, ``StoredDigest``, or ``Checksum`` value with an integer outside the byte range:
|        ``domain_error(byte,Byte)``
|    ``PasswordHash`` contains an invalid APR1 salt:
|        ``domain_error(apr1_salt,Salt)``
|    ``PasswordHash`` contains an invalid APR1 checksum:
|        ``domain_error(apr1_checksum,Checksum)``
|    ``Password`` is a partial list or a list with an element which is a variable:
|        ``instantiation_error``
|    ``Password`` is a list but not a list of bytes:
|        ``type_error(list(byte),Password)``
|    ``Password`` contains a non-integer element:
|        ``type_error(integer,Byte)``
|    ``Password`` contains an integer outside the byte range:
|        ``domain_error(byte,Byte)``


------------

Protected predicates
--------------------

(no local declarations; see entity ancestors if any)

Private predicates
------------------

(no local declarations; see entity ancestors if any)

Operators
---------

(none)

