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 Type | Method Name and Description |
---|---|
C2PAData | LensSecurityUtil.verify(byte[] media) Returns the C2PA validation in an object. Accepts byte array of a media file. |
C2PAData | LensSecurityUtil.verify(String path) Returns the C2PA validation in an object. Accepts file path to the media object. |
C2PAData | LensSecurityUtil.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. |
String | LensSecurityUtil.verifyJson(byte[] media) Returns JSON String, which can be parsed and displayed in the UI. Accepts byte array of a media file. |
String | LensSecurityUtil.verifyJson(String path) Returns JSON String, which can be parsed and displayed in the UI. Accepts file path to the media object. |
boolean | LensSecurityUtil.isC2PA(byte[] media) Checks if media contains C2PA data. Accepts byte array of a media file. |
boolean | LensSecurityUtil.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.
Label | Field and parsing |
---|---|
Captured | manifest.signature.signed_on |
Captured with | manifest.certificate.subject_name , split on "in" and use the second part |
Signed by | manifest.signature.signed_by |
Signed with | manifest.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());
}
}
Updated 3 months ago