Android Pay

From Crypto++ Wiki
Jump to navigation Jump to search

Android Pay or Google Pay is a mobile payment system created by Google for Android. It allows users to store payment card information in a virtual wallet and then use the cards to purchase goods and services. A component of Android Pay allows users to make in-app purchases and verify transactions.

Google uses ECIES for Android Pay tokens, and they provide a complete Java example at Payment data cryptography for merchants. The Crypto++ ECIES template cannot be used directly for Android Pay, but the primitives are available. The ECIES template cannot be used at the moment because a couple of classes are missing that would be needed to use it. For example, AES/CTR is available, but it is not wrapped in a class DL_EncryptionAlgorithm_AndroidPay which ECIES would use.

Android Pay is different from Apple Pay and Samsung Pay. This page covers Android Pay only. There are no pages for Apple Pay and Samsung Pay.

Google test framework and data is available on GitHub at tink/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken.

There are several mailing lists posts used as a source for this page. See Crypto++ for Google Pay schema.

Workflow

The Android Pay website states you must do the following in order to consume the Google Pay API's PaymentMethodToken payload:

  1. Fetch the Google signing keys
  2. Verify the signature of the payload is valid
  3. Decrypt the contents of the payload
  4. Verify the message is not expired by checking that the current time is less than the messageExpiration field in the decrypted contents
  5. Use the payment method in the decrypted contents and charge it

This wiki page will focus two items, which are (2) Verify the signature of the payload and (3) Decrypt the contents of the payload. The page does not detail how to do things like pares a JSON message.

Below is the sample message to verify and decrypt. It is the example payment method token response from the Android Pay website.

{
  "protocolVersion": "ECv1",
  "signature": "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
  "signedMessage": "{\"encryptedMessage\":
                     \"ZW5jcnlwdGVkTWVzc2FnZQ==\",
                     \"ephemeralPublicKey\": \"ZXBoZW1lcmFsUHVibGljS2V5\",
                     \"tag\": \"c2lnbmF0dXJl\"}"
}

Private Key

To load the private key named MERCHANT_PRIVATE_KEY_PKCS8_BASE64, perform the following:

string encoded =
    "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj"
    "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx"
    "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm";

int main(int argc, char* argv[])
{
    try
    {
        string decoded;
        StringSource ss(encoded, true, new Base64Decoder(new StringSink(decoded)));        

        ECIES<ECP>::Decryptor decryptor;
        decryptor.AccessKey().Load(StringStore((const byte*)decoded.data(), decoded.size()).Ref());

        AutoSeededRandomPool prng;
        decryptor.GetKey().ThrowIfInvalid(prng, 3);
        
        cout << "Private key appears to be valid" << endl;
    }
    catch(Exception& ex)
    {
        cerr << ex.what() << endl;
        return 1;
    }
    
    return 0;
}

Key Agreement

An integrated encryption scheme protects the infomation using a a symmetric cipher. However, the seed used for the key for the cipher is part embedded in the ciphertext using Diffie-Hellman. Effectively, half of the Diffie-Hellman scheme was applied to protect the secret seed, and you have to perform the other half of the scheme to recover the seed. Also see Миша Винник's work at Crypto++ for Google Pay schema.

Once you recover the seed you use a KDF to derive the other security parameters, like the symmetric cipher key and an initialization vector, if required. The derivation is covered in the section Key Derivation.

Key Derivation

The Android Pay KDF using HKDF would look similar to the following:

template <class H>
class AndroidPay_KDF
{
public:
    static void CRYPTOPP_API DeriveKey(byte *output, size_t outputLength, const byte *input, size_t inputLength,
            const byte *derivationParams, size_t derivationParamsLength)
    {
        CRYPTOPP_UNUSED(derivationParams), CRYPTOPP_UNUSED(derivationParamsLength);

        static const byte INFO[] = "Android";
        static const size_t INFO_SIZE = 7;
		
        HKDF<H> hkdf;
        hkdf.DeriveKey(output, outputLength, input, inputLength, NULL, 0, INFO, INFO_SIZE);
    }
};