Exposure Key export file format and verification

This document describes the file formats passed into the Exposure Notification API. Applications are responsible for downloading these files from backends and invoking provideDiagnosisKeys(). This document also explains how the files are verified by the API.

File format

Files consist of a ZIP archive containing two entries:

  • export.bin: The binary containing the exposure keys.
  • export.sig: A signature to verify the export binary.

Exposure file binary format

The export.bin file of the archive contains the Temporary Exposure Keys that are broadcast by the devices of people who are diagnosed with COVID-19. It’s an incremental file containing the latest keys the server received in a given window of time. This window is typically 24 hours, allowing devices to perform nightly matching.

The binary format file consists of a special header followed by a protocol buffer. The header must be exactly “EK Export v1” right-padded to 16 bytes with UTF-8 whitespaces. After the header is the serialization of a protocol buffer message named TemporaryExposureKeyExport defined as follows:

syntax = "proto2";
message TemporaryExposureKeyExport {
  // Time window of keys in this batch based on arrival to server, in UTC seconds.
  optional fixed64 start_timestamp = 1;
  optional fixed64 end_timestamp = 2;
  // Region for which these keys came from, such as country.
  optional string region = 3;
  // For example, file 2 in batch size of 10. Ordinal, 1-based numbering.
  // Note: Not yet supported on iOS.
  optional int32 batch_num = 4;
  optional int32 batch_size = 5;
  // Information about associated signatures
  repeated SignatureInfo signature_infos = 6;
  // The TemporaryExposureKeys for initial release of keys.
  // Keys should be included in this list for initial release,
  // whereas revised or revoked keys should go in revised_keys.
  repeated TemporaryExposureKey keys = 7;
  // TemporaryExposureKeys that have changed status.
  // Keys should be included in this list if they have changed status
  // or have been revoked.
  repeated TemporaryExposureKey revised_keys = 8;
}
message SignatureInfo {
  // The first two fields have been deprecated
  reserved 1, 2;
  reserved "app_bundle_id", "android_package";
  // Key version for rollovers
  // Must be in character class [a-zA-Z0-9_]. For example, 'v1'
  optional string verification_key_version = 3;
  // Alias with which to identify public key to be used for verification
  // Must be in character class [a-zA-Z0-9_.]
  // For cross-compatibility with Apple, you can use your region's three-digit
  // mobile country code (MCC). If your region has more than one MCC, choose the
  // one that Apple has configured.
  optional string verification_key_id = 4;
  // ASN.1 OID for Algorithm Identifier. For example, `1.2.840.10045.4.3.2'
  optional string signature_algorithm = 5;
}
message TemporaryExposureKey {
  // Key of infected user
  optional bytes key_data = 1;
  // Varying risk associated with a key depending on diagnosis method
  optional int32 transmission_risk_level = 2 [deprecated = true];
  // The interval number since epoch for which a key starts
  optional int32 rolling_start_interval_number = 3;
  // Increments of 10 minutes describing how long a key is valid
  optional int32 rolling_period = 4
  [default = 144]; // defaults to 24 hours
  // Data type representing why this key was published.
  enum ReportType {
    UNKNOWN = 0;  // Never returned by the client API.
    CONFIRMED_TEST = 1;
    CONFIRMED_CLINICAL_DIAGNOSIS = 2;
    SELF_REPORT = 3;
    RECURSIVE = 4;  // Reserved for future use.
    REVOKED = 5;  // Used to revoke a key, never returned by client API.
  }

  // Type of diagnosis associated with a key.
  optional ReportType report_type = 5;

  // Number of days elapsed between symptom onset and the TEK being used.
  // E.g. 2 means TEK is 2 days after onset of symptoms.
  optional sint32 days_since_onset_of_symptoms = 6;
}

If the server stores exposure keys in the order that they were uploaded from devices, shuffle them before you export them. The shuffle ensures that there is no linkage between keys from a device. Since the keys are essentially random, a simple way to do this is to sort by key.

Exposure file signature

The server creates an asymmetric key pair for signing exports. When the app is allowlisted, the public key is provided to Google. The server signs each export.bin with a private key. The export.sig file of the archive contains the raw signature as well as additional information needed for verification. This key is different from the signing key for the APK.

The signature file is the serialization of the TEKSignatureList protobuf message defined as follows:

message TEKSignatureList {
  repeated TEKSignature signatures = 1;
}
message TEKSignature {
  // Info about the signing key, version, algorithm, and so on.
  optional SignatureInfo signature_info = 1;
  // For example, file 2 in batch size of 10. Ordinal, 1-based numbering.
  optional int32 batch_num = 2;
  optional int32 batch_size = 3;
  // Signature in X9.62 format (ASN.1 SEQUENCE of two INTEGER fields)
  optional bytes signature = 4;
}

Example

For the most common case of a backend exporting files for consumption by a handful of apps in some region, these archive entries could look as shown in the following example. Assume for the example that the backend serves apps in the U.S. and that splitting the export into multiple files is not needed yet. The files in the archive might look as follows:

export.bin

"EK Export v1    " // the special 16 byte header
TemporaryExposureKeyExport {
  start_timestamp: 1589068800
  end_timestamp: 1589155200
  region: "US"
  batch_num: 1
  batch_size: 1
  signature_infos: [
    SignatureInfo {
      verification_key_id: "310"
      verification_key_version: "v1"
      signature_algorithm: "1.2.840.10045.4.3.2"
    },
  ]
  keys = [...]
}

export.sig

message TEKSignatureList {
  signatures: [
    TEKSignature {
      signature_info:
        SignatureInfo {
          verification_key_id: "310"
          verification_key_version: "v1"
          signature_algorithm: "1.2.840.10045.4.3.2"
        }
      signature: [...] // signature byte contents using key version 1
    },
  ]
}

Create batches

These files can grow so big that they become prohibitive to download. This is especially true for devices that don't have Wi-Fi access. You can break up files into batches to keep the file size below 16MB—about 750K keys. Each chunk of data in the batch is its own ZIP archive with its own signature. Each archive covers just a portion of the batch. The serialized protos populate the batch_num and batch_size fields.

Verification

The API verifies the signature against the content of the exposure binary file. This can be optionally disabled on test devices using the setting in Debug Mode. However, prior to handing over your public key to Google for use in verification it is important to confirm that files are being successfully verified.

The API uses the fields in the signature file to identify which verification key to use for the application. You must verify at least one signature for each ZIP archive in a batch.

The API will also verify either that batch_size is equal to 1, or, if greater than 1:

  • Pre v1.5:
    • There are batch_size number of files in the batch and each batch_num is present. That is, only one batch has been passed in and it is whole.
  • Post v1.5:
    • For every batch passed in (identified by grouping by start_timestamp, end_timestamp, region, and batch_size), all parts of the batch are present.
  • If the files should be cosigned by multiple keys, all are present in the signatures.

The API completes all verification before it invokes and releases the results. You should see "Signature verification succeeded" in the logs. If you do not, matching is not invoked and your app will not properly show exposure notifications.

Reference server export utilities

The reference server design offers a set of utilities to create and verify the file format and the signature.