#pdflite/crypt_core

    moonbitlang/pdflite/crypt_core contains low-level PDF security-handler primitives: ARC4, AES block and object encryption, password padding, digest helpers, permission masks, file-key derivation, and object-key derivation. The root package builds document-level encryption on top of these primitives.

    flowchart LR Passwords[password bytes] --> Padding[pdf_pad_password] Padding --> FileKey[pdf_encryption_file_key] FileKey --> ObjectKey[pdf_encryption_object_key] ObjectKey --> ARC4[pdf_arc4_crypt_object] ObjectKey --> AES[pdf_aesv2_encrypt_object_data] Permissions[PdfPermission list] --> Mask[pdf_p_of_permissions]

    #Checked Examples

    ///|
    test "arc4 primitive is symmetric" {
    let key = try! @core.pdf_bytes_of_int_array([1, 2, 3, 4, 5])
    let plain = try! @core.pdf_bytes_of_int_array([80, 68, 70])
    let cipher = try! @crypt_core.pdf_arc4_crypt(key, plain)
    let roundtrip = try! @crypt_core.pdf_arc4_crypt(key, cipher)
    if @core.pdf_int_array_of_bytes(roundtrip) != [80, 68, 70] {
    fail("expected ARC4 to decrypt back to the original bytes")
    }
    }

    ///|
    test "digest and permission helpers expose PDF primitives" {
    let data = try! @core.pdf_bytes_of_int_array([97, 98, 99])
    inspect(@crypt_core.pdf_md5_digest(data).length(), content="16")
    let mask = @crypt_core.pdf_p_of_permissions([PdfNoPrint, PdfNoCopy])
    let permissions = @crypt_core.pdf_permissions_of_p(mask)
    let mut saw_copy = false
    for permission in permissions {
    if permission == PdfNoCopy {
    saw_copy = true
    }
    }
    if !saw_copy {
    fail("expected permission round trip to include PdfNoCopy")
    }
    }

    #Package Notes

    • APIs accept byte views so higher packages can pass slices without copying.
    • Permission helpers convert between explicit denied permissions and PDF /P bit masks.
    • Object-encryption helpers require object number, generation, crypt type, key length, and file key so document-level code stays explicit.

    #Pedantic Boundaries

    • This package owns cryptographic primitives and PDF security-handler math. It does not parse encryption dictionaries or decide document write policy.
    • Password inputs are byte views. Callers must decide how user-facing text is encoded before it reaches these APIs.
    • Permission conversion preserves PDF's denied-permission semantics. Tests should name denied permissions explicitly instead of treating the /P integer as self-documenting.
    • Object encryption must include object number, generation, crypt type, and key length so that callers cannot accidentally reuse a file key as an object key.

    #Verification Notes

    • README examples are blackbox tests for public low-level crypto APIs.
    • Prefer published-vector tests and exact byte assertions in regular tests; README examples should stay small and deterministic.
    • Run moon test crypt_core/README.mbt.md after editing this file.
    • Run moon info before review; this README should not change crypt_core/pkg.generated.mbti.

    PdfAesV3RandomField

    pub(all) enum PdfAesV3RandomField {
    PdfAesV3FileKey
    PdfAesV3UserValidationSalt
    PdfAesV3UserKeySalt
    PdfAesV3OwnerValidationSalt
    PdfAesV3OwnerKeySalt
    PdfAesV3PermissionsPadding
    } derive(Eq, ToJson,
    Debug
    )

    Random material requested while constructing PDF 2.0 AESV3 encryption data.

    Provider callbacks receive this field tag and a byte length, allowing tests and reproducible builds to supply deterministic file keys, salts, and permissions padding.

    PdfAesV3RandomField::equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfAesV3RandomField::equal(PdfAesV3RandomField, PdfAesV3RandomField) -> Bool

    PdfAesV3RandomField::not_equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfAesV3RandomField::not_equal(x : PdfAesV3RandomField, y : PdfAesV3RandomField) -> Bool

    PdfAesV3RandomField::to_json

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfAesV3RandomField::to_json(PdfAesV3RandomField) -> Json

    PdfAesV3RandomField::to_repr

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfAesV3RandomField::to_repr(PdfAesV3RandomField) ->
    Repr

    PdfEncryptionMethod

    pub(all) enum PdfEncryptionMethod {
    PdfEncryption40Bit
    PdfEncryption128Bit
    PdfEncryptionAES128(Bool)
    PdfEncryptionAES256(Bool)
    PdfEncryptionAES256ISO(Bool)
    PdfEncryptionAlreadyEncrypted
    } derive(Eq, ToJson,
    Debug
    )

    Encryption method detected from or requested for a document.

    AES entries carry the effective EncryptMetadata flag. The PdfEncryptionAlreadyEncrypted value is used by writer paths that preserve existing encrypted object data rather than decrypting and re-encrypting it.

    PdfEncryptionMethod::equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionMethod::equal(PdfEncryptionMethod, PdfEncryptionMethod) -> Bool

    PdfEncryptionMethod::not_equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionMethod::not_equal(x : PdfEncryptionMethod, y : PdfEncryptionMethod) -> Bool

    PdfEncryptionMethod::to_json

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionMethod::to_json(PdfEncryptionMethod) -> Json

    PdfEncryptionMethod::to_repr

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionMethod::to_repr(PdfEncryptionMethod) ->
    Repr

    PdfEncryptionValues

    pub(all) struct PdfEncryptionValues {
    crypt_type :
    PdfCryptType

    user_entry : Bytes
    owner_entry : Bytes
    permissions : Int
    file_id : Bytes
    encrypt_metadata : Bool
    permissions_entry : Bytes?
    user_encryption_key : Bytes?
    owner_encryption_key : Bytes?
    } derive(Eq,
    Debug
    )

    Parsed standard-security-handler values from an encryption dictionary.

    Password entries, permission masks, file IDs, and optional AESV3 wrapping entries are stored as PDF bytes. crypt_type describes the actual object encryption algorithm used for strings and streams.

    This is shared security state: it is parsed by the document crypt code and retained on a decrypted document (PdfSavedEncryption), so it lives in the low-level crypt_core package that the document core can depend on without a cycle. Fields are public so the (still root-resident) crypt logic can build and read them across the package boundary.

    PdfEncryptionValues::equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionValues::equal(PdfEncryptionValues, PdfEncryptionValues) -> Bool

    PdfEncryptionValues::not_equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionValues::not_equal(x : PdfEncryptionValues, y : PdfEncryptionValues) -> Bool

    PdfEncryptionValues::to_repr

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfEncryptionValues::to_repr(PdfEncryptionValues) ->
    Repr

    PdfPermission

    pub(all) enum PdfPermission {
    PdfNoEdit
    PdfNoPrint
    PdfNoCopy
    PdfNoAnnot
    PdfNoForms
    PdfNoExtract
    PdfNoAssemble
    PdfNoHqPrint
    } derive(Eq, ToJson,
    Debug
    )

    Permission that may be denied by a PDF standard security handler.

    These values are expressed as a "ban list" in the public API: passing PdfNoPrint means printing is denied. They are converted to and from the PDF /P permission bit mask by pdf_p_of_permissions and pdf_permissions_of_p.

    PdfPermission::equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfPermission::equal(PdfPermission, PdfPermission) -> Bool

    PdfPermission::not_equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfPermission::not_equal(x : PdfPermission, y : PdfPermission) -> Bool

    PdfPermission::to_json

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfPermission::to_json(PdfPermission) -> Json

    PdfPermission::to_repr

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfPermission::to_repr(PdfPermission) ->
    Repr

    PdfSavedEncryption

    pub(all) struct PdfSavedEncryption {
    values : PdfEncryptionValues
    } derive(Eq,
    Debug
    )

    Saved encryption state retained on a decrypted document.

    Recrypt helpers use this to put a previously encrypted document back into its original security-handler shape after edits.

    PdfSavedEncryption::equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfSavedEncryption::equal(PdfSavedEncryption, PdfSavedEncryption) -> Bool

    PdfSavedEncryption::not_equal

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfSavedEncryption::not_equal(x : PdfSavedEncryption, y : PdfSavedEncryption) -> Bool

    PdfSavedEncryption::to_repr

    #deprecated("implicit trait-method promotion is being removed; call via the trait")
    fn PdfSavedEncryption::to_repr(PdfSavedEncryption) ->
    Repr

    pdf_aes_cbc_decrypt

    fn pdf_aes_cbc_decrypt(key : BytesView, data : BytesView, remove_padding? : Bool) -> Bytes raise
    PdfError

    Decrypt AES-CBC data whose first block is the IV.

    Data shorter than or equal to one block decrypts to empty bytes. Block lengths that are not multiples of 16 raise @core.PdfError::InvalidCryptoDataLength.

    pdf_aes_cbc_encrypt_with_iv

    fn pdf_aes_cbc_encrypt_with_iv(key : BytesView, iv : BytesView, data : BytesView) -> Bytes raise
    PdfError

    Encrypt bytes with AES-CBC and an explicit IV.

    PKCS-style padding is added and the returned bytes include the IV as the first block, matching PDF AES string and stream data layout.

    pdf_aes_decrypt_block

    fn pdf_aes_decrypt_block(key : BytesView, block : BytesView) -> Bytes raise
    PdfError

    Decrypt one 16-byte AES block with a 128-, 192-, or 256-bit key.

    pdf_aes_ecb_decrypt

    fn pdf_aes_ecb_decrypt(key : BytesView, data : BytesView, remove_padding? : Bool) -> Bytes raise
    PdfError

    Decrypt AES-ECB data.

    The input length must be a non-zero multiple of 16 unless it is empty. Padding is removed by default.

    pdf_aes_ecb_encrypt

    fn pdf_aes_ecb_encrypt(key : BytesView, data : BytesView) -> Bytes raise
    PdfError

    Encrypt AES-ECB data without adding padding.

    The input length must be a non-zero multiple of 16 unless it is empty.

    pdf_aes_encrypt_block

    fn pdf_aes_encrypt_block(key : BytesView, block : BytesView) -> Bytes raise
    PdfError

    Encrypt one 16-byte AES block with a 128-, 192-, or 256-bit key.

    pdf_aesv2_decrypt_object_data

    fn pdf_aesv2_decrypt_object_data(object_number : Int, generation : Int, file_key : BytesView, key_length_bits : Int, data : BytesView) -> Bytes raise
    PdfError

    Decrypt AESV2 object data bytes with the derived object key.

    data must include the leading CBC IV as stored in encrypted PDF strings and streams.

    pdf_aesv2_encrypt_object_data

    fn pdf_aesv2_encrypt_object_data(object_number : Int, generation : Int, file_key : BytesView, key_length_bits : Int, data : BytesView, iv : BytesView) -> Bytes raise
    PdfError

    Encrypt bytes as AESV2 object data with an explicit CBC IV.

    The returned bytes include the IV prefix expected by PDF AESV2 strings and streams.

    pdf_aesv3_decrypt_object_data

    fn pdf_aesv3_decrypt_object_data(file_key : BytesView, data : BytesView) -> Bytes raise
    PdfError

    Decrypt AESV3 object data bytes with the document file key.

    data must include the leading CBC IV.

    pdf_aesv3_encrypt_object_data

    fn pdf_aesv3_encrypt_object_data(file_key : BytesView, data : BytesView, iv : BytesView) -> Bytes raise
    PdfError

    Encrypt bytes as AESV3 object data with an explicit CBC IV.

    The returned bytes include the IV prefix expected by PDF AES strings and streams.

    pdf_arc4_crypt

    fn pdf_arc4_crypt(key : BytesView, data : BytesView) -> Bytes raise
    PdfError

    Apply ARC4 to data with key.

    ARC4 is symmetric, so the same function encrypts and decrypts. Empty keys raise @core.PdfError::CryptoKeyExpected.

    pdf_arc4_crypt_object

    fn pdf_arc4_crypt_object(object_number : Int, generation : Int, file_key : BytesView, key_length_bits : Int, data : BytesView) -> Bytes raise
    PdfError

    ARC4-encrypt or decrypt data for one object using a file key.

    The per-object key is derived from object number and generation before ARC4 is applied.

    pdf_authenticate_owner_password

    fn pdf_authenticate_owner_password(no_encrypt_metadata : Bool, owner_password : BytesView, revision : Int, user_entry : BytesView, owner_entry : BytesView, permissions : Int, file_id : BytesView, key_length_bits : Int) -> Bool raise
    PdfError

    Authenticate a revision 2-4 owner password.

    This decrypts the owner entry to recover the padded user password and then validates that recovered password against the user entry.

    pdf_authenticate_user_password

    fn pdf_authenticate_user_password(no_encrypt_metadata : Bool, user_password : BytesView, revision : Int, user_entry : BytesView, owner_entry : BytesView, permissions : Int, file_id : BytesView, key_length_bits : Int) -> Bool raise
    PdfError

    Authenticate a revision 2-4 user password against a /U entry.

    pdf_bytes_prefix_equal

    fn pdf_bytes_prefix_equal(left : BytesView, right : BytesView, length : Int) -> Bool

    Return whether two byte views share the same prefix of the requested length.

    If either input is shorter than length, the prefix comparison fails.

    pdf_crypt_low_byte

    fn pdf_crypt_low_byte(value : Int, shift : Int) -> Byte

    Return one byte of an integer after shifting it into the low position.

    Standard-security permission values are serialized least-significant byte first when deriving the file encryption key.

    pdf_encryption_file_key

    fn pdf_encryption_file_key(no_encrypt_metadata : Bool, password : BytesView, revision : Int, owner_entry : BytesView, permissions : Int, file_id : BytesView, key_length_bits : Int) -> Bytes

    Derive the standard-security file key from a user password.

    This implements revisions 2-4 key derivation. AESV3 uses wrapped file keys instead and should go through the PdfEncryptionValues helpers.

    pdf_encryption_object_key

    fn pdf_encryption_object_key(crypt_type :
    PdfCryptType
    , object_number : Int, generation : Int, file_key : BytesView, key_length_bits : Int) -> Bytes

    Derive the per-object encryption key for a PDF object.

    The object number, generation, file key, key length, and AESV2 salt are hashed according to the PDF standard-security object-key algorithm.

    pdf_make_owner_password_entry

    fn pdf_make_owner_password_entry(revision : Int, owner_password : BytesView, user_password : BytesView, key_length_bits : Int) -> Bytes raise
    PdfError

    Build the /O owner-password entry for revisions 2-4.

    A blank owner password follows PDF behavior by using the user password as the owner-password source.

    pdf_make_user_password_entry

    fn pdf_make_user_password_entry(no_encrypt_metadata : Bool, user_password : BytesView, revision : Int, owner_entry : BytesView, permissions : Int, file_id : BytesView, key_length_bits : Int) -> Bytes raise
    PdfError

    Build the /U user-password entry for revisions 2-4.

    The entry is derived from the user password, owner entry, permission mask, file ID, key length, and metadata-encryption flag.

    pdf_md5_digest

    fn pdf_md5_digest(data : BytesView) -> Bytes

    Return the MD5 digest of data.

    pdf_p_of_permissions

    fn pdf_p_of_permissions(permissions : ArrayView[PdfPermission]) -> Int

    Convert denied permissions to a PDF /P permission mask.

    The input is a ban list. Required reserved bits are set according to the PDF standard-security-handler convention.

    pdf_pad_password

    fn pdf_pad_password(password : BytesView) -> Bytes

    Pad or truncate a raw PDF password to 32 bytes.

    Passwords are byte sequences. Inputs longer than 32 bytes are truncated; shorter inputs are filled with the standard PDF padding sequence.

    pdf_password_padding

    fn pdf_password_padding() -> Bytes

    Return the standard 32-byte PDF password padding sequence.

    pdf_permissions_of_p

    fn pdf_permissions_of_p(mask : Int) -> Array[PdfPermission]

    Decode a PDF /P permission mask to denied permissions.

    The returned array is a ban list ordered from later permission bits down to basic print/edit/copy permissions.

    pdf_sha256_digest

    fn pdf_sha256_digest(data : BytesView) -> Bytes

    Return the SHA-256 digest of data.

    pdf_sha384_digest

    fn pdf_sha384_digest(data : BytesView) -> Bytes

    Return the SHA-384 digest of data.

    pdf_sha512_digest

    fn pdf_sha512_digest(data : BytesView) -> Bytes

    Return the SHA-512 digest of data.

    pdf_user_password_from_owner_password

    fn pdf_user_password_from_owner_password(revision : Int, owner_password : BytesView, owner_entry : BytesView, key_length_bits : Int) -> Bytes raise
    PdfError

    Recover the padded user-password bytes from a revision 2-4 owner password.

    The returned bytes are the padded/truncated internal password form, not a decoded MoonBit string.