C2PA Guide

Sign Function

To use the following functions to sign an image or mp4, please review these dependency differences in your module build.gradle:

  • First, make sure you've followed the Basic Capture Integration steps. If you are only using the SDK for verification, you do not need implementation 'com.truepic:lens:X.X.X.' .
  • Adding implementation 'com.truepic:lens-security:X.X.X' is required.

Add the first signature to an image

Use this method when the image does not have existing C2PA data. It returns a byte[] array containing the signed photo.

sign(LensSecurity lensSecurity, // instance of the lens security
    String uuid, // uuid of the photo 
    byte[] ingredient, // byte array of the photo 
    List<Pair<String, String>> assertions, // list of assertions to add
    List<Pair<String, String>> customAssertions) // list of custom assertions to add

Add the first signature to a video

Use this method when the video does not have existing C2PA data. The provided video path is replaced with the signed file.

resignImage(LensSecurity lensSecurity, // instance of the lens security
    String uuid, // uuid of the video 
    String pathVideo, // path to the video to be signed, which will be replaced with the signed file
    @Nullable byte[] videoThumbnail, // thumbnail of the video
    List<Pair<String, String>> assertions, // list of assertions to add
    List<Pair<String, String>> customAssertions) // list of custom assertions to add

Resign an image

Use this method when the image already has C2PA data (also known as an existing C2PA manifest) and you want to add additional information and signatures. It returns a byte[] array containing the signed photo.

resignVideo(LensSecurity lensSecurity, // instance of the lens security
    byte[] ingredient, // original photo
    byte[] derived, // modified photo
    String uuid, // uuid of the photo 
    List<Pair<String, String>> assertions, // list of assertions to add
    List<Pair<String, String>> customAssertions) // list of custom assertions to add

Resign a video

Use this method to add additional information and signatures when the video already has C2PA data. The provided video path is replaced with the signed file.

resignImage(LensSecurity lensSecurity, // instance of the lens security
    String pathToIngredient, // original video
    String pathToDerived, // modified video
    String uuid, // uuid of the video 
    List<Pair<String, String>> assertions, // list of assertions to add
    List<Pair<String, String>> customAssertions, // list of custom assertions to add
    @Nullable byte[] videoThumbnail) // thumbnail of the video

Example

Here's an example of how an app could resign an image if it used AI editing. The original, signed capture is passed in, along with the newly edited version to be signed. A custom assertion is added to label the image as containing AI-generated content.

package com.truepic;

import android.content.Context;
import android.util.Pair;

import com.truepic.lenssdksecurity.LensSecurity;
import com.truepic.lenssdksecurity.utils.LensSecurityUtil;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;

public class AISignExample {

    public static void example(Context context) {
        // prepare lens security, startEnrollment should have already been called beforehand
        LensSecurity lensSecurity = new LensSecurity();
        lensSecurity.init(context.getApplicationContext(), "api key", false); // TODO fill in api key

        byte[] ingredient = null; // original image
        byte[] derived = null; // ai modified image
        String uuid = UUID.randomUUID().toString(); // uuid of the image,
        List<Pair<String, String>> assertions = new ArrayList<>(); // list that contains all the standard assertions as key->value pairs
        List<Pair<String, String>> customAssertions = new ArrayList<>(); // list that contains all the custom assertions as key->value pairs

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        String exifDate = simpleDateFormat.format(new Date());

        assertions.add(new Pair<>("thumbnail", "{\"MaxDimension\":\"1024\",\"Quality\":\"90\"}")); // thumbnail assertion
        assertions.add(new Pair<>("exif", "{\"exif:DateTimeOriginal\": \"" + exifDate + "\"}"));
        assertions.add(new Pair<>("actions", "{\"actions\":[{\"action\":\"c2pa.created\"}]}"));
        customAssertions.add(new Pair<>("ai", "{\"model_name\":\"Stable Diffusion\",\"model_version\":\"2.1\",\"prompt\":\"Cowboy on the moon\"}"));

        byte[] derived2 = LensSecurityUtil.resignImage(lensSecurity, ingredient, derived, uuid, assertions, customAssertions);
    }

}

Verify Function

Perform C2PA validation locally within your Android app, without sending media to a server.

To use the following functions to verify an image or mp4, please review these dependency differences in your module build.gradle:

  • First, make sure you've followed the Basic Capture Integration steps. If you are only using the SDK for verification, you do not need implementation 'com.truepic:lens:X.X.X.' .
  • Adding implementation 'com.truepic:lens-security:X.X.X' is required.
Return TypeMethod Name and Description
C2PADataLensSecurityUtil.verify(byte[] media)
Returns the C2PA validation in an object. Accepts byte array of a media file.
C2PADataLensSecurityUtil.verify(String path)
Returns the C2PA validation in an object. Accepts file path to the media object.
C2PADataLensSecurityUtil.verifyWithThumbnails(String path)
Returns the C2PA validation in an object. Additionally, it populates thumbnail data from the C2PA manifest into the C2PAData object. Accepts file path to the media object.
StringLensSecurityUtil.verifyJson(byte[] media)
Returns JSON String, which can be parsed and displayed in the UI. Accepts byte array of a media file.
StringLensSecurityUtil.verifyJson(String path)
Returns JSON String, which can be parsed and displayed in the UI. Accepts file path to the media object.
booleanLensSecurityUtil.isC2PA(byte[] media)
Checks if media contains C2PA data. Accepts byte array of a media file.
booleanLensSecurityUtil.isC2PA(String path)
Checks if media contains C2PA data. Accepts file path to the media object.

Example verifyJson Response

{
  "manifest_store": [
    {
      "URI": "com.truepic:urn:uuid:046e0cc4-6cbd-462b-883a-1abd7cdd8dc4",
      "claim": {
        "dc:title": "a437a4d7-e49a-4fdf-af28-4bb2dd279831.jpg",
        "dc:format": "image/jpeg",
        "instanceID": "a437a4d7-e49a-4fdf-af28-4bb2dd279831",
        "claim_generator": "Truepic_Lens_Android/1.2.0 libc2pa/2.6.4",
        "claim_generator_info": [
          {
            "name": "Truepic Lens Android",
            "version": "1.2.0"
          }
        ]
      },
      "is_active": true,
      "signature": {
        "status": "VALID",
        "signed_by": "Demo Org",
        "signed_on": "2023-04-20T18:04:59Z"
      },
      "assertions": {
        "stds.exif": [
          {
            "data": {
              "@context": {
                "dc": "http://purl.org/dc/elements/1.1/",
                "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
                "xmp": "http://ns.adobe.com/xap/1.0/",
                "EXIF": "http://ns.adobe.com/EXIF/1.0/",
                "tiff": "http://ns.adobe.com/tiff/1.0/",
                "EXIFEX": "http://cipa.jp/EXIF/2.32/"
              },
              "EXIF:Make": "Google",
              "EXIF:Model": "Pixel 4a"
            },
            "status": "VALID",
            "truepic_id": "make_and_model",
            "instance_index": 0
          },
          {
            "data": {
              "@context": {
                "dc": "http://purl.org/dc/elements/1.1/",
                "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
                "xmp": "http://ns.adobe.com/xap/1.0/",
                "EXIF": "http://ns.adobe.com/EXIF/1.0/",
                "tiff": "http://ns.adobe.com/tiff/1.0/",
                "EXIFEX": "http://cipa.jp/EXIF/2.32/"
              },
              "EXIF:DateTimeOriginal": "2023-04-20T18:04:58Z"
            },
            "status": "VALID",
            "truepic_id": "date_time_original",
            "instance_index": 1
          }
        ],
        "c2pa.hash.data": [
          {
            "status": "VALID",
            "instance_index": 0
          }
        ],
        "com.truepic.libc2pa": [
          {
            "data": {
              "git_hash": "57744b4",
              "lib_name": "Truepic C2PA C++ Library",
              "lib_version": "2.6.4",
              "target_spec_version": "1.2"
            },
            "status": "VALID",
            "instance_index": 0
          }
        ],
        "c2pa.thumbnail.claim.jpeg": [
          {
            "status": "VALID",
            "instance_index": 0
          }
        ],
        "com.truepic.custom.odometry": [
          {
            "data": {
              "lens": "Back",
              "gravity": [
                {
                  "x": -0.19322154,
                  "y": 6.1628942,
                  "z": 7.63004,
                  "timestamp": "2023-04-20T18:04:57Z"
                }
              ],
              "attitude": [
                {
                  "roll": 0.024598213,
                  "pitch": -0.6825746,
                  "azimuth": 2.9188325,
                  "timestamp": "2023-04-20T18:04:57Z"
                }
              ],
              "pressure": [
                {
                  "value": 993.3587,
                  "timestamp": "2023-04-20T18:04:57Z"
                }
              ],
              "geomagnetism": [
                {
                  "x": -3.8349998,
                  "y": -40.5375,
                  "z": -19.7375,
                  "timestamp": "2023-04-20T18:04:57Z"
                }
              ],
              "rotation_rate": [
                {
                  "x": -0.13545538,
                  "y": -0.037414394,
                  "z": -0.014354911,
                  "timestamp": "2023-04-20T18:04:57Z"
                }
              ],
              "user_acceleration": [
                {
                  "x": -0.18544376,
                  "y": 6.1292152,
                  "z": 7.537391,
                  "timestamp": "2023-04-20T18:04:57Z"
                }
              ]
            },
            "status": "VALID",
            "instance_index": 0
          }
        ]
      },
      "certificate": {
        "status": "VALID",
        "cert_der": "MIIDTzCCAjegAwIBAgIUQwdFyWu++1cX7a17fupdRA29lMcwDQYJKoZIhvcNAQEMBQAwVjELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB1RydWVwaWMxDTALBgNVBAsMBExlbnMxJjAkBgNVBAMMHUFuZHJvaWRDbGFpbVNpZ25pbmdDQS1TdGFnaW5nMB4XDTIzMDQyMDE3NTQzN1oXDTIzMDQyMTE3NTQzNlowbjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCERlbW8gT3JnMRYwFAYDVQQLDA1EZW1vIE9yZyBVbml0MTQwMgYDVQQDDCtUcnVlcGljIExlbnMgU0RLIHYxLjIuMCBpbiBMZW5zIERlbW8gdjEuMi4wMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES6k4olfmq6WymdqbT+YWsHuIkl3uUypkS0wtacirIvX+6ajAnjjyViHVt0Sl0q19EACI7m3+v59OjvGSYGKCYqOBxzCBxDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFBVTe3YeAki9G0xySsmvOzPZqQD9ME8GCCsGAQUFBwEBBEMwQTA/BggrBgEFBQcwAYYzaHR0cDovL3ZhLnRydWVwaWMubmluamEvZWpiY2EvcHVibGljd2ViL3N0YXR1cy9vY3NwMBMGA1UdJQQMMAoGCCsGAQUFBwMEMB0GA1UdDgQWBBS/7elj6Cab+QPO+5J+iZo7yxOv/TAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQEMBQADggEBALkAPvSwbxyOXIylcsaQHnejYdByAlMeIRYtsc9xo8OiOJDM91DhnHOC7scSyxdXOF/q8XzqjtzzBNx2Q642im2pnlikPfhQh0FrocUSm3QrJ3JiYmXkAyJmiQBog+HL5c5fkjYp8FWnzKny0gxbVVYKNn6on3Uwwj5bjIm0mwIheVGe+Av7hxALKAcH/LvAVVdw0N+jTxLihVJHjKi2HqrbsWkmdHG3LXDfevs+ZaImE3yAEzxc8bkaSwEgiICE00b2utnHX47p9NPZYZPxVyzqi9JxTXfZBWkHovCAeAQFN+f8Rog/3Xly1tZpgC1Pv0YoLVKnk3ScW+w3aTk+wvY=",
        "issuer_name": "AndroidClaimSigningCA-Staging",
        "subject_name": "Truepic Lens SDK v1.2.0 in Lens Demo v1.2.0",
        "valid_not_after": "2023-04-21T17:54:36Z",
        "valid_not_before": "2023-04-20T17:54:37Z",
        "organization_name": "Demo Org",
        "organization_unit_name": "Demo Org Unit"
      },
      "certificate_chain": [
        {
          "cert_der": "MIIEjjCCAnagAwIBAgIUcbJp9g9GFECteBnUYj1G41OrtAMwDQYJKoZIhvcNAQEMBQAwRzELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB1RydWVwaWMxDTALBgNVBAsMBExlbnMxFzAVBgNVBAMMDlJvb3RDQS1TdGFnaW5nMB4XDTIxMTAyNzE2MTE1NFoXDTI2MTAyNjE2MTE1M1owVjELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB1RydWVwaWMxDTALBgNVBAsMBExlbnMxJjAkBgNVBAMMHUFuZHJvaWRDbGFpbVNpZ25pbmdDQS1TdGFnaW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA04ABgn43znFn9eix4cYIcQ5fW627QhqT53r+glpiW6wgRXZv5Va6cPrKXADkzxCnnKQNtpFpj6zISfbAbJSPVJ6fE+25qRx1ZQjXN/537n/cVYCadNz/ZWuBU37WMu79WQs60VAG5mxXzOoH/dk4TL9IuTPb8GmnEPQ7xM3DpnlozimZhK8q4TkG7vLzL05G3vhcGia/HhsiH8tUVBbxL0Qnq29JcX7Vm+KEqGkyFCiaEMkevot/dXzXhmFv2vEo5jL5IO+wxGFH2GanXIkGFWbL8FrfFfIyYYQdEUi9WdWZtEfqgU1Xrm+5I//oGh4milFhusvDbN7ICjf3vwycvwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFLmae/xcYRU2zHVOXWsRU4MhvUvUMB0GA1UdDgQWBBQVU3t2HgJIvRtMckrJrzsz2akA/TAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAEhxHCDXu73emCMjZWGKT2QBJc6Hn2F83V/+fCBOkFuKt8b2tuAbo70jL5uAb1CrEdpacTY5MtTZKDyruUQZoDdIhf5l5RF+Ke25HygnryfaZOQjK+6XzI3MImFKGrthFAe1yWmZNUNaSUObPLTBxfzE/299MrSQ92McORX7EXq8OweOOr3AShNeK5ddKYyAMhvO1sG0J3Eld/6xD6V2I0nik7asY86wEYEILlUkaJ/+SmbUwRUITYAywGOkDtMlvENSP2+L3ZLsRobWrCvR8gP1i8S1PJIYag+xCLTFSWChGkTQP284W3PiwfPjBq9iqC2VDfUtGCZJM41Rj2cj/u4SgHfD1bSXAr8R3BgwzVOD+Gt+bWdz0+0IMuxMjr0R89GBG0XsUL2XpTR6YaM7u5lBumos7mvP8A5SFeMnQ8K7cNdRJLmIbuDNbOEz2dYAsQnibT2ApaLLnCospTNN2L7RyqFp21Qziyn8hzD3jB2HHyWdKiaEqrTaTmjLGPr2KTeowSN/jMl2c5ZcRGvt8kwT3JXv/c9wLjpz3Qb1F48QYZdKGDQbhjTH5pPngX3wvyHdnzcUCFDY3Az5sfJATnYvkZspxl+a+cRt0BxaucDgMs/9cJLm3R9Q6Cdm9WDFKOoFkWaXyLSEjYaTNXQ/mGq/G/al9eLvoXfna4w9FFbk"
        }
      ],
      "trusted_timestamp": {
        "TSA": "Truepic Lens Time-Stamping Authority",
        "status": "VALID",
        "timestamp": "2023-04-20T18:04:59Z"
      }
    }
  ]
}

To learn more about other parts of the validation report, see Validate C2PA.

Display Verification Data

You can use the verification output to create your own content credentials display, like this demo app:

To get you started, here is a mapping of some of the fields you may wish to populate in the user interface.

manifest is the first/only object in the manifest_store array.

LabelField and parsing
Capturedmanifest.signature.signed_on
Captured withmanifest.certificate.subject_name, split on "in" and use the second part
Signed bymanifest.signature.signed_by
Signed withmanifest.claim_generator_info.name + space + manifest.claim_generator_info.version

Example C2PAData Usage

Here's an example of how to use the C2PAData Java object.

String path = "/path/to/c2pa/media";
C2PAData c2pa = LensUtil.verifyWithThumbnails(path);

// manifests
List<ManifestStore> manifests = c2pa.getManifestStore();

// thumbnails
Map<String, byte[]> thumbnails = c2pa.getThumbnailStore();

for (ManifestStore manifestStore : manifests) {

    System.out.println("Reading manifest URI: " + manifestStore.getUri());

    if (thumbnails != null) {
        // retrieve thumbnail for the current manifest
        byte[] thumbnail = thumbnails.get(manifestStore.getUri());
    }

    // retrieve assertions for current manifest and print out number of exif assertions if any
    Assertions assertions = manifestStore.getAssertions();
    System.out.println("Exif assertions count " + (assertions.getStdsExif() != null ? assertions.getStdsExif().size() : 0));

    // retrieve signature for the current manifest and print captured date / signed by
    Signature signature = manifestStore.getSignature();
    System.out.println("Captured: " + signature.getSignedOn());
    System.out.println("Signed by: " + signature.getSignedBy());

    // retrieve certificate for the current manifest and print subject name / captured with
    Certificate certificate = manifestStore.getCertificate();
    System.out.println("Certificate subject: " + certificate.getSubjectName());

    if (certificate.getSubjectName().contains("in")) {
        System.out.println("Captured with: " +
                certificate.getSubjectName().substring(certificate.getSubjectName().indexOf("in") + 2).trim());
    }

    // retrieve claim generator and print signed with
    List<ClaimGeneratorInfo> claimGeneratorInfo = manifestStore.getClaim().getClaimGeneratorInfo();
    if (!claimGeneratorInfo.isEmpty()) {
        System.out.println("Signed with: " + claimGeneratorInfo.get(0).getName() + " " + claimGeneratorInfo.get(0).getVersion());
    }
}