PublicKey and PrivateKey
PublicKey and PrivateKey are backend-agnostic key types that wrap OpenSSL keys. They
support RSA, EC (NIST curves), EdDSA (Ed25519 / Ed448), ML-DSA (FIPS 204), and ML-KEM
(FIPS 203). Both types are exported from the top-level synta package.
PublicKey
Construction
PublicKey.from_pem(data: bytes) -> PublicKey
Load from PEM-encoded SubjectPublicKeyInfo.
PublicKey.from_der(data: bytes) -> PublicKey
Load from DER-encoded SubjectPublicKeyInfo.
PublicKey.from_rsa_components(n: bytes, e: bytes) -> PublicKey
Construct an RSA public key from big-endian modulus n and public exponent e bytes.
Raises ValueError if the inputs are not a valid RSA key.
PublicKey.from_ec_components(x: bytes, y: bytes, curve: str) -> PublicKey
Construct an EC public key from affine coordinates x, y and NIST curve name.
curve must be "P-256", "P-384", or "P-521". Raises ValueError for unknown curve
names or invalid coordinates.
Serialisation
pub_key.to_pem() -> bytes
pub_key.to_der() -> bytes
Properties
| Property | Type | Description |
|---|---|---|
key_type | str | Lowercase key algorithm: "rsa", "ec", "ed25519", "ed448", "dsa", "ml-dsa-44", "ml-dsa-65", "ml-dsa-87", "ml-kem-512", "ml-kem-768", "ml-kem-1024", or "unknown" |
key_size | int | None | Key size in bits, or None for EdDSA and post-quantum keys |
modulus | bytes | None | RSA modulus n as big-endian bytes, or None for non-RSA keys |
public_exponent | bytes | None | RSA public exponent e as big-endian bytes, or None for non-RSA keys |
curve_name | str | None | NIST curve name for EC keys (e.g. "P-256"), or None |
x | bytes | None | EC affine X coordinate as big-endian bytes, or None for non-EC keys |
y | bytes | None | EC affine Y coordinate as big-endian bytes, or None for non-EC keys |
Methods
pub_key.rsa_oaep_encrypt(plaintext: bytes, hash_algorithm: str = "sha256") -> bytes
pub_key.rsa_pkcs1v15_encrypt(plaintext: bytes) -> bytes
pub_key.kem_encapsulate() -> tuple[bytes, bytes] # ML-KEM: (ciphertext, shared_secret)
pub_key.verify_signature(
signature: bytes,
data: bytes,
algorithm: str | None = None,
context: bytes | None = None,
) -> None
Verify a signature over data. Raises ValueError on failure.
algorithm: hash name (e.g."sha256") for RSA and ECDSA. PassNonefor Ed25519, Ed448, and ML-DSA keys.context: ML-DSA domain-separation string (FIPS 204);None= empty. Ignored for non-ML-DSA keys.
Full class stub
class PublicKey:
@staticmethod
def from_pem(data: bytes) -> PublicKey: ...
@staticmethod
def from_der(data: bytes) -> PublicKey: ...
@staticmethod
def from_rsa_components(n: bytes, e: bytes) -> PublicKey: ...
@staticmethod
def from_ec_components(x: bytes, y: bytes, curve: str) -> PublicKey: ...
def to_pem(self) -> bytes: ...
def to_der(self) -> bytes: ...
key_type: str
key_size: int | None
modulus: bytes | None
public_exponent: bytes | None
curve_name: str | None
x: bytes | None
y: bytes | None
def rsa_oaep_encrypt(self, plaintext: bytes, hash_algorithm: str = "sha256") -> bytes: ...
def rsa_pkcs1v15_encrypt(self, plaintext: bytes) -> bytes: ...
def kem_encapsulate(self) -> tuple[bytes, bytes]: ...
def verify_signature(
self,
signature: bytes,
data: bytes,
algorithm: str | None = None,
context: bytes | None = None,
) -> None: ...
PrivateKey
Construction
PrivateKey.from_pem(data: bytes, password: bytes | None = None) -> PrivateKey
Load from PEM-encoded data (optionally password-protected).
PrivateKey.from_der(data: bytes) -> PrivateKey
Load from unencrypted PKCS#8 DER bytes.
PrivateKey.from_pkcs8_encrypted(data: bytes, password: bytes) -> PrivateKey
Decrypt and load from PKCS#8 EncryptedPrivateKeyInfo DER.
Serialisation
key.to_pem() -> bytes
key.to_der() -> bytes # unencrypted PKCS#8 DER
Properties
| Property | Type | Description |
|---|---|---|
key_type | str | Same values as PublicKey.key_type |
key_size | int | None | Key size in bits, or None |
Methods
key.public_key() -> PublicKey # extract the matching public key
key.sign(data: bytes, algorithm: str | None = None, context: bytes | None = None) -> bytes
key.rsa_oaep_decrypt(ciphertext: bytes, hash_algorithm: str = "sha256") -> bytes
key.rsa_pkcs1v15_decrypt(ciphertext: bytes) -> bytes
key.kem_decapsulate(ciphertext: bytes) -> bytes # ML-KEM: returns shared_secret
Full class stub
class PrivateKey:
@staticmethod
def from_pem(data: bytes, password: bytes | None = None) -> PrivateKey: ...
@staticmethod
def from_der(data: bytes) -> PrivateKey: ...
@staticmethod
def from_pkcs8_encrypted(data: bytes, password: bytes) -> PrivateKey: ...
def to_pem(self) -> bytes: ...
def to_der(self) -> bytes: ...
key_type: str
key_size: int | None
def public_key(self) -> PublicKey: ...
def sign(
self,
data: bytes,
algorithm: str | None = None,
context: bytes | None = None,
) -> bytes: ...
def rsa_oaep_decrypt(self, ciphertext: bytes, hash_algorithm: str = "sha256") -> bytes: ...
def rsa_pkcs1v15_decrypt(self, ciphertext: bytes) -> bytes: ...
def kem_decapsulate(self, ciphertext: bytes) -> bytes: ...
Usage
import synta
# Load a certificate and verify a signature using the embedded public key
cert = synta.Certificate.from_der(open("cert.der", "rb").read())
pub_key = synta.PublicKey.from_der(cert.public_key)
# For ECDSA with SHA-256:
try:
pub_key.verify_signature(signature, message, algorithm="sha256")
print("Valid")
except ValueError:
print("Invalid")
# Load a private key and sign
priv_key = synta.PrivateKey.from_pem(open("key.pem", "rb").read())
sig = priv_key.sign(message, algorithm="sha256")
# ML-KEM key exchange
kem_pub = synta.PublicKey.from_der(kem_pub_der)
ciphertext, shared_secret = kem_pub.kem_encapsulate()
priv_kem = synta.PrivateKey.from_der(kem_priv_der)
recovered = priv_kem.kem_decapsulate(ciphertext)
assert shared_secret == recovered
See also PKCS#8 for parsing PKCS#8 private-key envelopes and CMS-KEM for RFC 9629 quantum-safe KEM recipient structures.