Decoding Apple App Store receipts (PKCS #7, ASN.1) in Java

According to the documentation, App Store receipts are binary files packed in a "PKCS #7 container, as defined by RFC 2315, with its payload encoded using ASN.1 (Abstract Syntax Notation One), as defined by ITU-T X.6901" The structure of a receipt is shown in the image below.

Structure of a receipt
Structure of a receipt

The documentation describes how to decode a receipt in C/C++, but what if you need to do this in Java? Apparently, the authors think that no one needs to decode receipts in Java because if it is Java, then we are talking about the server side, and in this case, one can obtain a JSON-encoded receipt data from the App Store validation service. In practice, however, things are not necessary as smooth and we had to decode receipts before validating them in order to handle and restore subscriptions. This post describes how I decoded receipts in King of Thieves at ZeptoLab.

Generating Java classes from the ASN.1 module

If you are familiar with Protocol Buffers, you will easily understand this step. The documentation specifies the following ASN.1 definition of a payload:

-- this definition was taken from https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html
-- app-store-receipt.asn
ReceiptModule DEFINITIONS ::=
BEGIN

ReceiptAttribute ::= SEQUENCE {
    type    INTEGER,
    version INTEGER,
    value   OCTET STRING
}

Payload ::= SET OF ReceiptAttribute

InAppAttribute ::= SEQUENCE {
    type                   INTEGER,
    version                INTEGER,
    value                  OCTET STRING
}

InAppReceipt ::= SET OF InAppAttribute

END

This definition describes the structure of a payload in a way similar to Protocol Buffers .proto files. Now we will use the compiler from the ASN1bean library to generate Java classes that are able to read data structured according to the above definition:

$ asn1bean-compiler -f app-store-receipt.asn -p stincmale.sandbox.examples.decodeappleappstorereceipt.asn1

The command above generates the following Java classes in the package stincmale.sandbox.examples.decodeappleappstorereceipt.asn1.receiptmodule:

  • Payload consists of ReceiptAttributes, which are designated as attributes inside a payload in the image above.
  • ReceiptAttribute is designated as an attribute inside a payload in the image above.
  • InAppReceipt is the value of a ReceiptAttribute, which is designated as an in-app purchase receipt in the image above. This value consists of InAppAttributes, which are designated as attributes inside an in-app purchase receipt in the image above.
  • InAppAttribute is designated as an attribute inside an in-app purchase receipt in the image above.

These classes require the com.beanit:asn1bean library, so make sure to include it in your project.

Extracting the receipt payload

Now we have classes that can decode a payload, but before doing that we need to extract it from a receipt. I use the Bouncy Castle org.bouncycastle:bcpkix-jdk15on library for this purpose. Before using it, we must make sure BouncyCastleProvider is added to the system:

static {
  Security.addProvider(new BouncyCastleProvider());
}

With Bouncy Castle and the previously generated Payload class extracting and decoding a payload can be done the following way (see AppStoreReceiptUtil.decodeReceipt(byte[] receipt)):

Payload decodeReceipt(byte[] receipt) {
  Payload payload;
  try {
    CMSSignedData signedData = new CMSSignedData(receipt);
    CMSTypedData signedContent = signedData.getSignedContent();
    ByteArrayOutputStream signedDataStream = new ByteArrayOutputStream();
    signedContent.write(signedDataStream);
    byte[] signedDataBytes = signedDataStream.toByteArray();
    payload = new Payload();
    payload.decode(new ByteArrayInputStream(signedDataBytes));
  } catch (CMSException | IOException e) {
    throw new RuntimeException(e);
  }
  return payload;
}

That is it. Now we have our Payload and can read everything there is in it. The class AppStoreReceiptUtil has some useful methods, e.g., the toString(Payload payload, boolean omitUnsupportedAttributes) method that allows converting a Payload in a human-readable formatted text. See AppStoreReceiptDecoderExample for an example.

  1. ITU stands for the International Telecommunication Union, ITU-T stands for the ITU Telecommunication Standardization Sector.