Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
40cfb8b
Add PQSignature keys nist_level property.
Guiliano99 Dec 10, 2025
c19f74e
Add PQHashStatefulSig keys key_bit_security property.
Guiliano99 Dec 10, 2025
ba2299d
Implement nist_level property for ML-DSA and SLH-DSA keys.
Guiliano99 Dec 10, 2025
ce39078
Implement key_bit_security for HSS, XMSS and XMSS^MT keys.
Guiliano99 Dec 10, 2025
d963607
Update ALL_KNOWN_OIDS_2_NAME to update with OID_HASH_MAP.
Guiliano99 Dec 10, 2025
220fd94
Add security_utils with NIST level to strength mapping
Guiliano99 Jan 17, 2026
6ab92fe
Add HASH_ALG_TO_STRENGTH mapping in security_utils
Guiliano99 Jan 17, 2026
921b8ae
Add _rsa_security_strength function to map RSA key sizes to security …
Guiliano99 Jan 17, 2026
5744345
Add _ecc_security_strength function to map ECC key sizes to security …
Guiliano99 Jan 17, 2026
79e911a
Add _get_pq_stfl_nist_security_strength and _nist_level_strength util…
Guiliano99 Jan 17, 2026
222b183
Implement `estimate_key_security_strength` to calculate security stre…
Guiliano99 Jan 17, 2026
deed3c0
Reorder asymmetric imports in `security_utils.py` for consistency.
Guiliano99 Jan 17, 2026
c30e07a
Expose `get_key_security_strength` keyword to estimate key security s…
Guiliano99 Jan 17, 2026
bc87132
Add unit tests for `get_key_security_strength` with various key types…
Guiliano99 Jan 17, 2026
c89a0fe
Add type annotations to `KM_KD_ALG` and `KDF_OID_2_NAME` for stricter…
Guiliano99 Jan 17, 2026
70344b2
Fix invalid doc for the PQ keys nist_level attribute.
Guiliano99 Feb 10, 2026
6803a7f
Add nist_level method to ML-DSA and SLH-DSA classes for better doc de…
Guiliano99 Feb 10, 2026
0f8acd6
Run ruff format.
Guiliano99 Feb 10, 2026
299acca
Update security strength documentation for RSA, DSA, ECC, and HSS key…
Guiliano99 Feb 10, 2026
12a0fdd
Add not_keyword decorator to estimate_key_security_strength function
Guiliano99 Feb 10, 2026
2780266
Add not_keyword decorator to compute_hss_signature_index function
Guiliano99 Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions pq_logic/keys/abstract_pq.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ def sig_size(self) -> int:
"""Return the size of the signature."""
return self._sig_method.details["length_signature"]

@property
def nist_level(self) -> int:
"""Return the claimed NIST security level as int."""
return int(self._sig_method.details["claimed_nist_level"])


class PQSignaturePrivateKey(PQPrivateKey, ABC):
"""Abstract base class for Post-Quantum Signature Private Keys."""
Expand Down Expand Up @@ -162,6 +167,11 @@ def from_private_bytes(cls, data: bytes, name: str) -> "PQSignaturePrivateKey":
raise ValueError(f"Invalid key size expected {key.key_size}, but got: {len(data)}")
return key

@property
def nist_level(self) -> int:
"""Return the claimed NIST security level as int."""
return self.public_key().nist_level


class PQKEMPublicKey(PQPublicKey, KEMPublicKey, ABC):
"""Abstract base class for Post-Quantum KEM Public Keys."""
Expand Down Expand Up @@ -204,7 +214,7 @@ def encaps(self) -> Tuple[bytes, bytes]:

@property
def nist_level(self) -> int:
"""Return the claimed NIST security level as string."""
"""Return the claimed NIST security level as int."""
return int(self._kem_method.details["claimed_nist_level"])


Expand Down Expand Up @@ -272,7 +282,7 @@ def public_key(self) -> PQKEMPublicKey:

@property
def nist_level(self) -> int:
"""Return the claimed NIST security level as string."""
"""Return the claimed NIST security level as int."""
return self.public_key().nist_level

@staticmethod
Expand Down
10 changes: 10 additions & 0 deletions pq_logic/keys/abstract_stateful_hash_sig.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ def max_sig_size(self) -> int:
def hash_alg(self) -> str:
"""Return the hash algorithm used by this public key."""

@property
@abstractmethod
def key_bit_security(self) -> int:
"""Return the estimated security strength in bits."""


class PQHashStatefulSigPrivateKey(PQPrivateKey, ABC):
"""Abstract base class for Post-Quantum Hash Stateful Signature Private Keys."""
Expand Down Expand Up @@ -131,3 +136,8 @@ def used_keys(self) -> list[bytes]:
def hash_alg(self) -> str:
"""Return the hash algorithm used by this private key."""
return self.public_key().hash_alg

@property
def key_bit_security(self) -> int:
"""Return the estimated security strength in bits."""
return self.public_key().key_bit_security
27 changes: 27 additions & 0 deletions pq_logic/keys/sig_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ def from_public_bytes(cls, data: bytes, name: str) -> "MLDSAPublicKey":
)
return key

@property
def nist_level(self) -> int:
"""Return the NIST security level of the ML-DSA algorithm."""
nist_levels = {"ml-dsa-44": 2, "ml-dsa-65": 3, "ml-dsa-87": 5}
return nist_levels[self.name]


ML_DSA_PRIVATE_KEY_SIZE = {"ml-dsa-44": 2560, "ml-dsa-65": 4032, "ml-dsa-87": 4896}

Expand Down Expand Up @@ -405,6 +411,10 @@ def seed_size(self) -> int:
"""Return the size of the seed used for ML-DSA key generation."""
return self._seed_size(name=self.name)

def nist_level(self) -> int:
"""Return the NIST security level of the ML-DSA algorithm."""
return self.public_key().nist_level


##########################
# SLH-DSA
Expand Down Expand Up @@ -562,6 +572,19 @@ def from_public_bytes(cls, data: bytes, name: str) -> "SLHDSAPublicKey":
raise ValueError(f"Invalid public key size. Expected: {_n}, got: {len(data)}")
return key

@property
def nist_level(self) -> int:
"""Return the NIST security level of the SLH-DSA algorithm."""
if oqs is not None:
return super().nist_level
if self.name.endswith("128s") or self.name.endswith("128f"):
return 1
if self.name.endswith("192s") or self.name.endswith("192f"):
return 3
if self.name.endswith("256s") or self.name.endswith("256f"):
return 5
raise ValueError(f"Cannot determine NIST level for SLH-DSA algorithm: {self.name}")


class SLHDSAPrivateKey(PQSignaturePrivateKey):
"""Represents an SLH-DSA private key."""
Expand Down Expand Up @@ -805,6 +828,10 @@ def seed_size(self) -> int:
"""Return the size of the seed used for SLH-DSA key generation."""
return self._seed_size(name=self.name)

def nist_level(self) -> int:
"""Return the NIST security level of the SLH-DSA algorithm."""
return self.public_key().nist_level


##########################
# Falcon
Expand Down
23 changes: 23 additions & 0 deletions pq_logic/keys/stateful_sig_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pyhsslms.pyhsslms import LenI
from pyhsslms.pyhsslms import lmots_params as PYHSSLMS_LMOTS_PARAMS
from pyhsslms.pyhsslms import lms_params as PYHSSLMS_LMS_PARAMS
from robot.api.deco import not_keyword

from pq_logic.keys.abstract_stateful_hash_sig import PQHashStatefulSigPrivateKey, PQHashStatefulSigPublicKey
from resources.exceptions import InvalidKeyData
Expand Down Expand Up @@ -284,6 +285,7 @@ def _compute_hss_signature_length(details: Dict[str, int], levels: int) -> int:
return 4 + max(levels - 1, 0) * (per_sig + per_pub) + per_sig


@not_keyword
def compute_hss_signature_index(signature: bytes, key: Union["HSSPrivateKey", "HSSPublicKey"]) -> int:
"""Return the global HSS signature index and the encoded depth."""
# TODO fix, if needed for different LMS and LMOTS key types.
Expand Down Expand Up @@ -508,6 +510,12 @@ def hash_alg(self) -> str:
return "shake128"
return XMSS_ALG_DETAILS[self.name.lower()]["hash_alg"]

@property
def key_bit_security(self) -> int:
"""Return the traditional bit security of the XMSS public key."""
# Approximate bit security based on the hash output size
return int(self.name.split("_")[-1]) // 2


class XMSSPrivateKey(PQHashStatefulSigPrivateKey):
"""Class representing an XMSS private key."""
Expand Down Expand Up @@ -772,6 +780,12 @@ def hash_alg(self) -> str:
"""Return the hash algorithm used by this XMSSMT public key."""
return XMSSMT_ALG_DETAILS[self.name.lower()]["hash_alg"]

@property
def key_bit_security(self) -> int:
"""Return the pq bit security of the XMSSMT public key."""
# Approximate bit security based on the hash output size
return XMSSMT_ALG_DETAILS[self.name.lower()]["n"] * 4


class XMSSMTPrivateKey(PQHashStatefulSigPrivateKey):
"""Class representing an XMSSMT private key."""
Expand Down Expand Up @@ -1085,6 +1099,15 @@ def get_level_names(self) -> Sequence[Tuple[str, str]]:
levels = [(lms_name, lmots_name)] * num_levels
return levels

@property
def key_bit_security(self) -> int:
"""Return the key bit security of this HSS key."""
# TODO fix for multiple levels with different parameters.
n = self._details["n"]
if n == 24:
return 96
return 128


class HSSPrivateKey(PQHashStatefulSigPrivateKey):
"""Representation of an HSS private key."""
Expand Down
31 changes: 31 additions & 0 deletions resources/keyutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
TRAD_SIG_NAME_2_OID,
TRAD_STR_OID_TO_KEY_NAME,
)
from resources.security_utils import estimate_key_security_strength
from resources.suiteenums import KeySaveType
from resources.typingutils import PrivateKey, PublicKey, SignKey, TradPrivateKey, TradSignKey, TradVerifyKey, VerifyKey

Expand Down Expand Up @@ -1453,3 +1454,33 @@ def get_pq_stateful_sig_index_from_sig( # noqa D417 undocumented-params
return key.get_leaf_index(signature)

raise NotImplementedError(f"Unsupported key type for signature index extraction. Got: {type(key).__name__}")


@keyword(name="Get Key Security Strength")
def get_key_security_strength( # noqa D417 undocumented-params
key: Union[PrivateKey, PublicKey],
) -> int:
"""Return the estimated security strength (in bits) for the provided key.

Arguments:
---------
- `key`: The key instance to estimate the security strength for.

Returns:
-------
- The estimated security strength in bits.

Raises:
------
- `ValueError`: If the security strength cannot be estimated for the given key type.
- `NotImplementedError`: If the key type is not supported for security strength estimation.

Examples:
--------
| ${strength}= | Get Key Security Strength | ${public_key} |

"""
strength = estimate_key_security_strength(key)
if strength == 0:
raise ValueError(f"Could not estimate security strength for key type: {type(key)}")
return strength
5 changes: 3 additions & 2 deletions resources/oidutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@
KM_KA_ALG.update(ECMQV)
KM_KA_ALG_NAME_2_OID = {y: x for x, y in KM_KA_ALG.items()}

KM_KD_ALG = {rfc9481.id_PBKDF2: "pbkdf2"} # As per Section 4.4 in RFC 9481
KM_KD_ALG: Dict[univ.ObjectIdentifier, str] = {rfc9481.id_PBKDF2: "pbkdf2"} # As per Section 4.4 in RFC 9481
KM_KW_ALG = {
rfc9481.id_aes128_wrap: "aes128_wrap",
rfc9481.id_aes192_wrap: "aes192_wrap",
Expand All @@ -479,6 +479,7 @@
ALL_KNOWN_OIDS_2_NAME.update({rfc6664.id_ecPublicKey: "ecPublicKey"})
ALL_KNOWN_OIDS_2_NAME.update(RFC9481_OID_2_NAME)
ALL_KNOWN_OIDS_2_NAME.update(HMAC_NAME_2_OID)
ALL_KNOWN_OIDS_2_NAME.update(OID_HASH_MAP)


###########################
Expand All @@ -500,7 +501,7 @@
HKDF_OID_2_NAME = {v: k for k, v in HKDF_NAME_2_OID.items()}


KDF_OID_2_NAME = {}
KDF_OID_2_NAME: Dict[univ.ObjectIdentifier, str] = {}
KDF_OID_2_NAME.update(KM_KD_ALG)
KDF_OID_2_NAME.update(HKDF_OID_2_NAME)
KDF_OID_2_NAME.update({rfc9690.id_kdf_kdf3: "kdf3", rfc9690.id_kdf_kdf2: "kdf2"})
Expand Down
Loading
Loading