synta 0.1.8

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# 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

```python
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

```python
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](enveloped-data.md) for per-recipient key transport and
[CMS Overview](overview.md) for all content types.