Skip to main content

Python bindings for the C2PA Content Authenticity Initiative (CAI) library

Project description

C2PA Python

This repository implements Python bindings for the Content Authenticity Initiative (CAI) library.

This library enables you to read and validate C2PA data in supported media files and add signed manifests to supported media files.

NOTE: Starting with version 0.5.0, this package has a completely different API from version 0.4.0. See Release notes for more information.

WARNING: This is an prerelease version of this library. There may be bugs and unimplemented features, and the API is subject to change.

Installation

Install from PyPI by entering this command:

pip install -U c2pa-python

This is a platform wheel built with Rust that works on Windows, macOS, and most Linux distributions (using manylinux). If you need to run on another platform, see Development for information on how to build from source.

Updating

Determine what version you've got by entering this command:

pip list | grep c2pa-python

If the version shown is lower than the most recent version, then update by reinstalling.

Reinstalling

If you tried unsuccessfully to install this package before the 0.40 release, then use this command to reinstall:

pip install --upgrade --force-reinstall c2pa-python

Usage

Import

Import the API as follows:

from c2pa import *

Define manifest JSON

The Python library works with both file-based and stream-based operations. In both cases, the manifest JSON string defines the C2PA manifest to add to an asset; for example:

manifest_json = json.dumps({
    "claim_generator": "python_test/0.1",
    "assertions": [
    {
      "label": "c2pa.training-mining",
      "data": {
        "entries": {
          "c2pa.ai_generative_training": { "use": "notAllowed" },
          "c2pa.ai_inference": { "use": "notAllowed" },
          "c2pa.ai_training": { "use": "notAllowed" },
          "c2pa.data_mining": { "use": "notAllowed" }
        }
      }
    }
  ]
 })

Signing function

The sign_ps256 function is defined in the library and is reproduced here to show how signing is performed.

# Example of using Python crypto to sign data using openssl with Ps256
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

def sign_ps256(data: bytes, key_path: str) -> bytes:
    with open(key_path, "rb") as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,
        )
    signature = private_key.sign(
        data,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    return signature

File-based operation

Read and validate C2PA data from an asset file

Use the Reader to read C2PA data from the specified asset file (see supported file formats).

This examines the specified media file for C2PA data and generates a report of any data it finds. If there are validation errors, the report includes a validation_status field.

An asset file may contain many manifests in a manifest store. The most recent manifest is identified by the value of the active_manifest field in the manifests map. The manifests may contain binary resources such as thumbnails which can be retrieved with resource_to_stream or resource_to_file using the associated identifier field values and a uri.

NOTE: For a comprehensive reference to the JSON manifest structure, see the Manifest store reference.

try:
  # Create a reader from a file path
  reader = c2pa.Reader.from_file("path/to/media_file.jpg")

  # Print the JSON for a manifest. 
  print("manifest store:", reader.json())

  # Get the active manifest.
  manifest = reader.get_active_manifest()
  if manifest != None:

    # get the uri to the manifest's thumbnail and write it to a file
    uri = manifest["thumbnail"]["identifier"]
    reader.resource_to_file(uri, "thumbnail_v2.jpg") 

except Exception as err:
    print(err)

Add a signed manifest to an asset file

WARNING: This example accesses the private key and security certificate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the C2PA Python Example.

Use a Builder to add a manifest to an asset:

try:
  # Define a function to sign the claim bytes
  # In this case we are using a pre-defined sign_ps256 method, passing in our private cert
  # Normally this cert would be kept safe in some other location
  def private_sign(data: bytes) -> bytes:
    return sign_ps256(data, "tests/fixtures/ps256.pem")

  # read our public certs into memory
  certs = open(data_dir + "ps256.pub", "rb").read()

  # Create a signer from the private signer, certs and a time stamp service url
  signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")

  # Create a builder add a thumbnail resource and an ingredient file.
  builder = Builder(manifest_json)

  # The uri provided here "thumbnail" must match an identifier in the manifest definition.
  builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")

  # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
  ingredient_json = {
    "title": "A.jpg",
    "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
    "thumbnail": {
        "identifier": "thumbnail",
        "format": "image/jpeg"
    }
  }

  # Add the ingredient to the builder loading information  from a source file.
  builder.add_ingredient_file(ingredient_json, "tests/fixtures/A.jpg")

  # At this point we could archive or unarchive our Builder to continue later.
  # In this example we use a bytearray for the archive stream.
  # all ingredients and resources will be saved in the archive
  archive = io.BytesIO(bytearray())
  builder.to_archive(archive)
  archive.seek()
  builder = builder.from_archive(archive)

  # Sign and add our manifest to a source file, writing it to an output file.
  # This returns the binary manifest data that could be uploaded to cloud storage.
  c2pa_data = builder.sign_file(signer, "tests/fixtures/A.jpg", "target/out.jpg")

except Exception as err:
    print(err)

Stream-based operation

Instead of working with files, you can read, validate, and add a signed manifest to streamed data. This example code does the same thing as the file-based example.

Read and validate C2PA data from a stream

try:
  # It's also possible to create a reader from a format and stream
  # Note that these two readers are functionally equivalent
  stream = open("path/to/media_file.jpg", "rb")
  reader = c2pa.Reader("image/jpeg", stream)

  # Print the JSON for a manifest. 
  print("manifest store:", reader.json())

  # Get the active manifest.
  manifest = reader.get_active_manifest()
  if manifest != None:

    # get the uri to the manifest's thumbnail and write it to a file
    uri = manifest["thumbnail"]["identifier"]
    reader.resource_to_file(uri, "thumbnail_v2.jpg") 

except Exception as err:
    print(err)

Add a signed manifest to a stream

WARNING: This example accesses the private key and security certificate directly from the local file system. This is fine during development, but doing so in production may be insecure. Instead use a Key Management Service (KMS) or a hardware security module (HSM) to access the certificate and key; for example as show in the C2PA Python Example.

Use a Builder to add a manifest to an asset:

try:
  # Define a function to sign the claim bytes
  # In this case we are using a pre-defined sign_ps256 method, passing in our private cert
  # Normally this cert would be kept safe in some other location
  def private_sign(data: bytes) -> bytes:
    return sign_ps256(data, "tests/fixtures/ps256.pem")

  # read our public certs into memory
  certs = open(data_dir + "ps256.pub", "rb").read()

  # Create a signer from the private signer, certs and a time stamp service url
  signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")

  # Create a builder add a thumbnail resource and an ingredient file.
  builder = Builder(manifest_json)

  # Add the resource from a stream
  a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
  builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)

  # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail
  ingredient_json = {
    "title": "A.jpg",
    "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
    "thumbnail": {
        "identifier": "thumbnail",
        "format": "image/jpeg"
    }
  }

  # Add the ingredient from a stream
  a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
  builder.add_ingredient("image/jpeg", a_jpg_stream)

  # At this point we could archive or unarchive our Builder to continue later.
  # In this example we use a bytearray for the archive stream.
  # all ingredients and resources will be saved in the archive
  archive = io.BytesIO(bytearray())
  builder.to_archive(archive)
  archive.seek()
  builder = builder.from_archive(archive)

  # Sign the builder with a stream and output it to a stream
  # This returns the binary manifest data that could be uploaded to cloud storage.
  input_stream = open("tests/fixtures/A.jpg", "rb")
  output_stream = open("target/out.jpg", "wb")
  c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)

except Exception as err:
    print(err)

Supported file formats

Extensions MIME type
avi video/msvideo, video/avi, application-msvideo
avif image/avif
c2pa application/x-c2pa-manifest-store
dng image/x-adobe-dng
gif image/gif
heic image/heic
heif image/heif
jpg, jpeg image/jpeg
m4a audio/mp4
mp3 audio/mpeg
mp4 video/mp4, application/mp4
mov video/quicktime
png image/png
svg image/svg+xml
tif,tiff image/tiff
wav audio/x-wav
webp image/webp

Development

It is best to set up a virtual environment for development and testing.

To build from source on Linux, install curl and rustup then set up Python.

First update apt then (if needed) install curl:

apt update
apt install curl

Install Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

Install Python, pip, and venv:

apt install python3
apt install pip
apt install python3.11-venv
python3 -m venv .venv

Build the wheel for your platform (from the root of the repository):

source .venv/bin/activate
pip install -r requirements.txt
python3 -m pip install build
pip install -U pytest

python3 -m build --wheel

Note: To peek at the Python code (uniffi generated and non-generated), run maturin develop and look in the c2pa folder.

ManyLinux build

Build using manylinux by using a Docker image as follows:

docker run -it quay.io/pypa/manylinux_2_28_aarch64 bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
export PATH=/opt/python/cp312-cp312/bin:$PATH
pip install maturin
pip install venv
pip install build
pip install -U pytest

cd home
git clone https://github.com/contentauth/c2pa-python.git
cd c2pa-python
python3 -m build --wheel
auditwheel repair target/wheels/c2pa_python-0.4.0-py3-none-linux_aarch64.whl

Testing

We use PyTest for testing.

Run tests by following these steps:

  1. Activate the virtual environment: source .venv/bin/activate
  2. (optional) Install dependencies: pip install -r requirements.txt
  3. Setup the virtual environment with local changes: maturin develop
  4. Run the tests: pytest
  5. Deactivate the virtual environment: deactivate

For example:

source .venv/bin/activate
maturin develop
python3 tests/training.py
deactivate

Release notes

Version 0.5.2

New features:

  • Allow EC signatures in DER format from signers and verify signature format during validation.
  • Fix bug in signing audio and video files in ISO Base Media File Format (BMFF).
  • Add the ability to verify PDF files (but not to sign them).
  • Increase speed of sign_file by 2x or more, when using file paths (uses native Rust file I/O).
  • Fixes for RIFF and GIF formats.

Version 0.5.0

New features in this release:

  • Rewrites the API to be stream-based using a Builder / Reader model.
  • The functions now support throwing c2pa.Error values, caught with try/except.
  • Instead of c2pa.read_file you now call c2pa_api.Reader.from_file and reader.json.
  • Read thumbnails and other resources use reader.resource_to_stream or reader.resource.to_file.
  • Instead of c2pa.sign_file use c2pa_api.Builder.from_json and builder.sign or builder.sign_file.
  • Add thumbnails or other resources with builder.add_resource or builder.add_resource_file.
  • Add Ingredients with builder.add_ingredient or builder.add_ingredient_file.
  • You can archive a Builder using builder.to_archive and reconstruct it with builder.from_archive.
  • Signers can be constructed with c2pa_api.create_signer.
  • The signer now requires a signing function to keep private keys private.
  • Example signing functions are provided in c2pa_api.py

Version 0.4.0

This release:

  • Changes the name of the import from c2pa-python to c2pa.
  • Supports pre-built platform wheels for macOS, Windows and manylinux.

Version 0.3.0

This release includes some breaking changes to align with future APIs:

  • C2paSignerInfo moves the alg to the first parameter from the 3rd.
  • c2pa.verify_from_file_json is now c2pa.read_file.
  • c2pa.ingredient_from_file_json is now c2pa.read_ingredient_file.
  • c2pa.add_manifest_to_file_json is now c2pa.sign_file.
  • There are many more specific errors types now, and Error messages always start with the name of the error i.e (str(err.value).startswith("ManifestNotFound")).
  • The ingredient thumbnail identifier may be jumbf uri reference if a valid thumb already exists in the active manifest.
  • Extracted file paths for read_file now use a folder structure and different naming conventions.

License

This package is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

Note that some components and dependent crates are licensed under different terms; please check the license terms for each crate and component for details.

Contributions and feedback

We welcome contributions to this project. For information on contributing, providing feedback, and about ongoing work, see Contributing.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

c2pa_python-0.6.1.tar.gz (345.3 kB view details)

Uploaded Source

Built Distributions

c2pa_python-0.6.1-py3-none-win_amd64.whl (5.4 MB view details)

Uploaded Python 3 Windows x86-64

c2pa_python-0.6.1-py3-none-win32.whl (4.8 MB view details)

Uploaded Python 3 Windows x86

c2pa_python-0.6.1-py3-none-manylinux_2_28_aarch64.whl (6.1 MB view details)

Uploaded Python 3 manylinux: glibc 2.28+ ARM64

c2pa_python-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (6.1 MB view details)

Uploaded Python 3 manylinux: glibc 2.17+ x86-64

c2pa_python-0.6.1-py3-none-macosx_11_0_arm64.whl (5.6 MB view details)

Uploaded Python 3 macOS 11.0+ ARM64

c2pa_python-0.6.1-py3-none-macosx_10_12_x86_64.whl (5.5 MB view details)

Uploaded Python 3 macOS 10.12+ x86-64

File details

Details for the file c2pa_python-0.6.1.tar.gz.

File metadata

  • Download URL: c2pa_python-0.6.1.tar.gz
  • Upload date:
  • Size: 345.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.4

File hashes

Hashes for c2pa_python-0.6.1.tar.gz
Algorithm Hash digest
SHA256 2f6d4cd9dfdbcb0920e4e909bf81ba281963cf54d85a95154ef4dabe40ef5d81
MD5 87f084f8c140c0874a4a035cf79b6bf0
BLAKE2b-256 2424e0f8e249e004e97ea69f77e5a70448500f8ff24882432647864bab946b8a

See more details on using hashes here.

File details

Details for the file c2pa_python-0.6.1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for c2pa_python-0.6.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 ba159f6458dc5da92a20d84f7c8851e29c68b402e693a29c8272546e21c1421b
MD5 579b002bbf7c9df790ed0c5167c92855
BLAKE2b-256 103933c97009fc3728592f8b6b33b2d87d12ec2649e54466a84da43fec9a9826

See more details on using hashes here.

File details

Details for the file c2pa_python-0.6.1-py3-none-win32.whl.

File metadata

File hashes

Hashes for c2pa_python-0.6.1-py3-none-win32.whl
Algorithm Hash digest
SHA256 cbc894f0f4435f4ef3915ac8273df295d774ebe992b7d2feeff56256752546f5
MD5 2d094ed568c91d74d32a989151a3bcd8
BLAKE2b-256 5d2f7e361161980464e3573d21b6e6b4fe3fa1a507a0368ed1f48b6b960ca487

See more details on using hashes here.

File details

Details for the file c2pa_python-0.6.1-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for c2pa_python-0.6.1-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 fd2fc5c78c381bd7acb4a87fff679ee8c039949a67647625f0baefda70d8b2a9
MD5 a4458dad01807518689822da803e3dae
BLAKE2b-256 07285ab43da18a294eeca6bdab6a8d361dcd5781fa4ac97ee850ad9416680e43

See more details on using hashes here.

File details

Details for the file c2pa_python-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for c2pa_python-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c059cab7418b98525d215f458a75bc06888ed1e489aad1682907524e654ebde6
MD5 763aa4a7cc5ab8efa5f8f909a7d5e895
BLAKE2b-256 932cfb2e3ce7f6c8a6d14c3e2e692e478251f0f4f1f928c578f61e1e83aac16b

See more details on using hashes here.

File details

Details for the file c2pa_python-0.6.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for c2pa_python-0.6.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0628c52703ec6d3d67623e27e1b6ed59a9154d8ac10107ad3a809a360f2a55db
MD5 001fd1982f5d785059e3af96e97ce7b2
BLAKE2b-256 f612ff0b79eab440adc2aa9f3d4bf77cb80ae0daf558dc1b6f8894d874cf5a5c

See more details on using hashes here.

File details

Details for the file c2pa_python-0.6.1-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for c2pa_python-0.6.1-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 9900b42d24eef8c35c47d3204913aeddc7b1441a6a700487e453100b64bad6da
MD5 b0f0773658e122e97a87e753fa4def7b
BLAKE2b-256 4948b1322835538dd8756951384af56741343a205635d1ed453d50ba7ff3b751

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page