Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

EncryptedData

EncryptedData implements RFC 5652 §8 — symmetric content encryption using a shared key. Unlike EnvelopedData, there is no per-recipient key transport; the caller provides the symmetric key directly.

Encryption and decryption require the openssl Cargo feature (maturin develop --features openssl).

Class

class EncryptedData:
    @staticmethod
    def from_der(data: bytes) -> EncryptedData: ...
    # Parse a DER- or BER-encoded EncryptedData SEQUENCE.
    # Raises ValueError if the outer SEQUENCE envelope is malformed.

    @staticmethod
    def create(
        plaintext: bytes,
        key: bytes,
        algorithm_oid: ObjectIdentifier | str,
        content_type_oid: ObjectIdentifier | str | None = None,
    ) -> EncryptedData: ...
    # Encrypt plaintext under key using the cipher identified by algorithm_oid.
    # A fresh random IV is generated for every call.
    # content_type_oid defaults to ID_DATA (1.2.840.113549.1.7.1).
    # Raises NotImplementedError if built without the openssl Cargo feature.

    def decrypt(self, key: bytes) -> bytes: ...
    # Recover plaintext.  Raises ValueError on key-length mismatch.
    # Raises NotImplementedError if built without the openssl Cargo feature.

    def to_der(self) -> bytes: ...

    version: int                             # always 0
    content_type: ObjectIdentifier           # e.g. ID_DATA
    content_encryption_algorithm_oid: ObjectIdentifier
    content_encryption_algorithm_params: bytes | None
    # For CBC ciphers: DER OCTET STRING (04 <len> <iv>) carrying the 16-byte IV.
    encrypted_content: bytes | None
    unprotected_attrs: bytes | None

Usage

from synta.cms import EncryptedData, ID_AES128_CBC, ID_AES256_CBC

KEY_128 = bytes.fromhex("00112233445566778899aabbccddeeff")
KEY_256 = bytes(range(32))

# ── Encrypt ───────────────────────────────────────────────────────────────────
plaintext = b"Confidential payload."
ed = EncryptedData.create(plaintext, KEY_128, ID_AES128_CBC)

# Inspect fields
print(ed.version)                            # 0
print(ed.content_type)                       # 1.2.840.113549.1.7.1  (id-data)
print(ed.content_encryption_algorithm_oid)   # 2.16.840.1.101.3.4.1.2
iv = ed.content_encryption_algorithm_params[2:]   # strip 04 <len> header
print(iv.hex())                              # 16-byte random IV

# ── Round-trip ────────────────────────────────────────────────────────────────
assert EncryptedData.from_der(ed.to_der()).decrypt(KEY_128) == plaintext

# ── Re-encrypt with replaced content ─────────────────────────────────────────
decrypted = ed.decrypt(KEY_128)
modified  = decrypted.replace(b"PENDING", b"APPROVED")
ed2 = EncryptedData.create(modified, KEY_256, ID_AES256_CBC)
assert ed2.decrypt(KEY_256) == modified

Each create call generates a fresh random IV; two calls on the same plaintext produce different ciphertext. See examples/example_cms_encrypted_data.py for a full example including openssl enc interop in both directions.

See also EnvelopedData for per-recipient key transport and CMS Overview for all content types.