Crypto++ for Google Pay schema

266 views
Skip to first unread message

Миша Винник

unread,
Mar 23, 2018, 9:11:00 AM3/23/18
to Crypto++ Users
Hello third time.

Totally newbie in C++ and crypto-things.
General schema is here: https://developers.google.com/pay/api/payment-data-cryptography#encrypt-spec

Am I right in understanding for what different cryptopp things to be used?

Short schema of my actions:

1)loading keys (PEM keys, I think) from string as here:

string base64_decode(string encoded) {
string decoded;

CryptoPP::Base64Decoder decoder;
decoder.Put((CryptoPP::byte*)encoded.data(), encoded.size());
decoder.MessageEnd();

CryptoPP::word64 size = decoder.MaxRetrievable();
if (size && size <= SIZE_MAX)
{
decoded.resize(size);
decoder.Get((CryptoPP::byte*)&decoded[0], decoded.size());
return decoded;
}
else {
return "";
}


}

string priv_key_encoded = "...";//big things in base64, like this:
//MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n
string priv_key = base64_decode(priv_key_encoded);
const CryptoPP::byte *priv_key_ptr = (const CryptoPP::byte*) priv_key.data();
CryptoPP::SecByteBlock privKey(*priv_key_ptr);

//similar actions on other keys - our private, accepted ephemeral and accepted public

CryptoPP::OID curve = CryptoPP::ASN1::secp256r1();
CryptoPP::ECDH<CryptoPP::ECP>::Domain dh(curve);
CryptoPP::DH2 dh2(dh);

//and finally:
dh2.Agree(shared_secret, privKey, ephPrivKey, g_pubKey, g_ephKey, false)



When I use outer keys auto-generated with GenerateKeyPair, I get shared_secret, but with Google keys - not. What can it be? Incorrect key loading?
Google ephemeral is 65-byte length after base64 decoding, but google public (from here https://payments.developers.google.com/paymentmethodtoken/keys.json) is 91-byte length. Tryed to use only last 65 - no effect.

Thank you very much!
I hope this post will help anyone wandering around googlePay api.

Jeffrey Walton

unread,
Mar 23, 2018, 9:46:13 AM3/23/18
to Crypto++ Users

Man, Google search is getting bad. I could not find the wiki page of interest...

Part of the Google Pay problem has been solved at https://www.cryptopp.com/wiki/Elliptic_Curve_Integrated_Encryption_Scheme#Android_Pay_Example . I'm not sure how complete the example is, but I seem to recall it is not complete.

Jeff

 

Миша Винник

unread,
Mar 23, 2018, 9:54:59 AM3/23/18
to Crypto++ Users
Hello, Jeffrey! Really waiting for man-that-solving-problems! 

It is correct and useful, but about signature verification, not about founding shared secret. The hardest question for me is - is it correct to just base64_decode strings to SecByteBlock and then push it to Agree(...)
And brain-drilling thing, that when I use auto-generated keys - all is good. Agree returns true and so on. But with google ephemeral and public - no. Maybe because of quoted possible incorrect key loading to SecByteBlock

Миша Винник

unread,
Mar 23, 2018, 9:55:32 AM3/23/18
to Crypto++ Users
WAS really waiting:)

Миша Винник

unread,
Mar 23, 2018, 10:31:01 AM3/23/18
to Crypto++ Users
Seems, that really incorrect key loading from here: https://payments.developers.google.com/paymentmethodtoken/keys.json 
While stepping into Agree debugging, validating outer public failed.
But how should it be loaded?

Key is MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENnoaYTAh15xpR65XRw7jHYj7vNUIGu5I4OmLCrORWwdjrcrED+bJo+nF2HyA5hnH12Dqt1bR8mqKBXynG3HBNw==

Is it PEM?

Jeffrey Walton

unread,
Mar 23, 2018, 12:07:17 PM3/23/18
to Crypto++ Users

It looks Base64 encoded to me.

Jeff

Миша Винник

unread,
Mar 23, 2018, 12:16:07 PM3/23/18
to Crypto++ Users
Understand all, finally. It is base64 encoded DER. In block "BIT STRING" there is public key, that should be used in DH2 Agree. Shared secret obtained at last!

Jeffrey Walton

unread,
Mar 23, 2018, 12:19:48 PM3/23/18
to Миша Винник, Crypto++ Users
Awesome, can you post the relevant code?

I'll start a new wiki page on Android Pay so others don't suffer the
problems you have worked through.

Jeff

Миша Винник

unread,
Mar 23, 2018, 12:21:08 PM3/23/18
to Crypto++ Users
Will gladly do it, but next week only, must go now.

Jeffrey Walton

unread,
Mar 23, 2018, 12:31:28 PM3/23/18
to Crypto++ Users


On Friday, March 23, 2018 at 12:21:08 PM UTC-4, Миша Винник wrote:
Will gladly do it, but next week only, must go now.

Perfect, thanks.

I started the wiki page at https://www.cryptopp.com/wiki/Android_Pay .

Jeff

Миша Винник

unread,
Mar 26, 2018, 6:58:03 AM3/26/18
to Crypto++ Users
Hello, Jeff.

Still have questions:/ Google manual says to do so:

Using your private key and the given ephemeralPublicKey, derive a 256 bit long shared key using ECIES-KEM. Use the following parameters as defined in ISO 18033-2:
  • Using your private key and the given ephemeralPublicKey, derive a 256 bit long shared key using ECIES-KEM. Use the following parameters as defined in ISO 18033-2:
    • Elliptic curve: NIST P-256 (also known in OpenSSL as prime256v1)
    • CheckMode, OldCofactorMode, SingleHashMode and CofactorMode are 0
    • Encoding function: Uncompressed Point format
    • Key derivation function: HKDFwithSHA256, as described in RFC 5869, using the following parameters:
      • Salt should not be provided (per the RFC, this should be equivalent to a salt of 32 zeroed bytes)
      • Info should be Android or Google encoded in ASCII for protocol versions ECv0 and Ecv1 respectively
  • Split the generated key into two 128-bit-long keys: symmetricEncryptionKey and macKey.
  • Verify that the tag field is a valid MAC for encryptedMessage:
    • For generating the expected MAC, use HMAC (RFC 5869) with hash function SHA256 and the macKeyobtained above
    • Use a constant time array comparison to avoid timing attacks
  • Decrypt encryptedMessage using AES128 CTR mode with a zero IV, no padding, and the symmetricEncryptionKey derived above.

Question is: is equal to do it so:
  • DH2 deriving shared secret using Agree method (own ephemeral private obtained by generating from GenerateEphemeralPrivateKey  )
  • using CryptoPP::HKDF<CryptoPP::SHA256> to derive key
  • splitting key
  • using CryptoPP::CTR_Mode<CryptoPP::AES> for final decryption
OR: should I use 
CryptoPP::DL_ES<
CryptoPP::DL_Keys_EC<CryptoPP::ECP>,
CryptoPP::KeyAgreementAlgorithm_DH<CryptoPP::ECP,CryptoPP::NO_COFACTOR_MULTIPLICTION>,
CryptoPP::DL_KeyDerivationAlgorithm

somehow? With overriding DL_KeyDerivationAlgorithm_P1363 with implementation of "HKDFwithSHA256" and so on?

Or this is equal approaches?

Still have questions, because on friday, I've recovered shared_key, but still cannot decrypt message for now.

Thank you very much!

Миша Винник

unread,
Mar 26, 2018, 9:20:46 AM3/26/18
to Crypto++ Users
Probably, keys should be loaded some other way, but no ideas how.
For now(and it is wrong I think) loading in such way:

string base64_decode(string encoded) {
string decoded;

CryptoPP::Base64Decoder decoder;
decoder.Put((CryptoPP::byte*)encoded.data(), encoded.size());
decoder.MessageEnd();

CryptoPP::word64 size = decoder.MaxRetrievable();
if (size && size <= SIZE_MAX)
{
decoded.resize(size);
decoder.Get((CryptoPP::byte*)&decoded[0], decoded.size());
return decoded;
}
else {
return "";
}
}
string priv_key = base64_decode(priv_key_accepted);
CryptoPP::SecByteBlock privKey((const CryptoPP::byte*)priv_key.data(), dh2.StaticPrivateKeyLength());

and then use privKey in Agree method.

Is it right to load keys in such way?

Миша Винник

unread,
Mar 26, 2018, 10:37:00 AM3/26/18
to Crypto++ Users
For now loading so:
string priv_accepted = "..."
CryptoPP::DL_PrivateKey_EC<CryptoPP::ECP> k1;
string decoded;
CryptoPP::StringSource ss1(priv_key_accepted.c_str(), true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decoded)));
k1.Load(CryptoPP::StringStore((const CryptoPP::byte*)decoded.data(), decoded.size()).Ref());
CryptoPP::Integer priv_int = k1.GetPrivateExponent();

CryptoPP::SecByteBlock privKey;
size_t encodedSize = priv_int.MinEncodedSize(CryptoPP::Integer::UNSIGNED);
privKey.resize(encodedSize);
priv_int.Encode(privKey.BytePtr(), encodedSize, CryptoPP::Integer::UNSIGNED);

still no effect. Do anybody no proper way to do it?

Jeffrey Walton

unread,
Mar 26, 2018, 11:56:10 AM3/26/18
to Миша Винник, Crypto++ Users
They should be equal approaches but I suspect they are not at the moment.

You will probably have to do the former - do things in discrete
pieces. I think you are most of the way there.

Eventually I plan on dropping patches for the later - add an AdroidPay
IES. I'm waiting for enough details and documentation to emerge for
the cut-in.

Jeff

Миша Винник

unread,
Mar 26, 2018, 11:58:25 AM3/26/18
to Crypto++ Users
Thanks, Jeff, keep crawling on it.

Миша Винник

unread,
Mar 27, 2018, 4:07:52 AM3/27/18
to Crypto++ Users
Hello, Jeff! Another question:

Google says about HKDFwithSHA256 : as in RFC5869.
Opened RFC5869, see:
PRK = HMAC-Hash(salt, IKM)
and then
HKDF-Expand(PRK, info, L) -> OKM

But in hkdf.h I see the following:
hmac.CalculateDigest(prk, secret, secretLen);

Is it on previous, RFC5689 standart? And for RFC5869 we should do it another way?

Миша Винник

unread,
Mar 27, 2018, 7:12:33 AM3/27/18
to Crypto++ Users
Question disappeared - salt used there, one line above:

const byte* key = (salt ? salt : GetNullVector());
const size_t klen = (salt ? saltLen : DIGEST_SIZE);

hmac.SetKey(key, klen);

Миша Винник

unread,
Mar 27, 2018, 7:16:24 AM3/27/18
to Crypto++ Users
Still cannot decrypt all in correct way. Maybe someone can easily found an error here?

bool get_shared = dh2.Agree(shared_secret, privKey, ephPrivKey, g_pubKey, g_ephKey);
//converting from bytes to string
string str_shared_secret((const char*)shared_secret.data(), 64);

//about options Google manual talking about:
//CheckMode, OldCofactorMode, SingleHashMode and CofactorMode are 0 google says
//OldCofactorMode - just the same as CofactorMode (0 for us)
//SingleHashMode - if somehow true(for us it is false) - then we would send ephemeral||secret to hkdf (not only secret) (0 for us)
//CheckMode - throw error while decrypting if R(P[r] = subgr generator over random from 1..q-1)[q(prime = subgroup order)] != 0 . also 0 for us

//retrieving shared key using "Key derivation function: HKDFwithSHA256":
//info always the same, as in manual
CryptoPP::byte info[] = {'G','o','o','g','l','e'};
size_t info_len = sizeof(info)/sizeof(*info);
//empty (32 empty bytes), as said in docs
CryptoPP::byte salt[32] = {0};
size_t salt_len = sizeof(salt) / sizeof(*salt);
//secret - what we have found from ECDH
CryptoPP::byte *secret = (CryptoPP::byte*) str_shared_secret.data();
size_t secret_len = str_shared_secret.size();
//to store result
//CryptoPP::byte derived[CryptoPP::SHA256::DIGESTSIZE];
CryptoPP::SecByteBlock derived(32);
size_t derived_len = 32;
//derivation function
CryptoPP::HKDF<CryptoPP::SHA256> hkdf;
//deriving, result in 'dervied'
hkdf.DeriveKey(derived, derived_len, secret, secret_len, salt, salt_len, info, info_len);

//splitting to two 128-bit length: symmetricEncryptionKey and macKey
CryptoPP::byte symmetricEncryptionKey[16];
CryptoPP::byte macKey[16];
memcpy(symmetricEncryptionKey, derived, 16);
memcpy(macKey, derived + 16, 16);

//checking accepted tag with MAC with HMAC SHA256
//TODO

//decrypting encryptedMessage with AES128 CTR zero IV, no padding, using symmetricEncryption key
//zero iv
CryptoPP::byte iv[16] = {0};
string decryptedtext;
CryptoPP::CTR_Mode< CryptoPP::AES >::Encryption d;
d.SetKeyWithIV(&(symmetricEncryptionKey[0]), 16, iv, 16);
CryptoPP::StringSource(reinterpret_cast<const unsigned char*>(&(encrypted_message[0])), encrypted_message.size(), true,
new CryptoPP::StreamTransformationFilter(d,
new CryptoPP::StringSink(decryptedtext)
)
);

cout << decryptedtext << std::endl;

But I cannot. Manual that talks about these things is right here: https://developers.google.com/pay/api/payment-data-cryptography#decrypting-the-payment-token


Message has been deleted

Миша Винник

unread,
Mar 27, 2018, 9:19:35 AM3/27/18
to Crypto++ Users
For now generating shared secret in correct way - using just private + accepted ephemeral, as said here: https://www.researchgate.net/publication/255970113_A_Survey_of_the_Elliptic_Curve_Integrated_Encryption_Scheme :
CryptoPP::SecByteBlock sharedA(dh.AgreedValueLength());
dh
.Agree(sharedA,privKey,g_ephKey);
string str_shared_secret((const char*)sharedA.data(), sharedA.size());

But still cannot decrypt. Any help is welcome.

Jeffrey Walton

unread,
Mar 27, 2018, 1:03:16 PM3/27/18
to Crypto++ Users


On Friday, March 23, 2018 at 9:11:00 AM UTC-4, Миша Винник wrote:
Hello third time.

Totally newbie in C++ and crypto-things.
General schema is here: https://developers.google.com/pay/api/payment-data-cryptography#encrypt-spec

The Google Pay example is this:

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

}

The workflow say Step 1 is:

    1. Fetch the Google signing keys

Do you know where the signing key is located? I cannot find a URL for it; and I cannot find a Hex or Base64 dump of it. Searching is producing a lot of irrelevant noise. (Maybe I missed it in my initial read of the page).

Jeff

Миша Винник

unread,
Mar 28, 2018, 1:55:16 AM3/28/18
to Crypto++ Users
Google signed is on https://payments.developers.google.com/paymentmethodtoken/keys.json . JSON there, in field "key"

Миша Винник

unread,
Mar 28, 2018, 3:32:22 AM3/28/18
to Crypto++ Users
Continuing on decrypting tryings.

For now shared secret is obtained as (x,y) from scalar multiplication result. Google says "use SingleHashMode = 0", I understand now, that this meanse that we should proivde not only shared, but accepted ephemeral public to hkdf too.

Looking at BouncyCastle:
ECPoint[] ghTilde = new ECPoint[]{
 basePointMultiplier
.multiply(ecParams.getG(), r),
 ecPubKey
.getQ().multiply(rPrime)
};


// NOTE: More efficient than normalizing each individually
curve
.normalizeAll(ghTilde);


ECPoint gTilde = ghTilde[0], hTilde = ghTilde[1];


// Encode the ephemeral public key
byte[] C = gTilde.getEncoded(false);
System.arraycopy(C, 0, out, outOff, C.length);


// Encode the shared secret value
byte[] PEH = hTilde.getAffineXCoord().getEncoded();


return deriveKey(keyLen, C, PEH);

Okay, we have ephemeral - accepted in request and used in scalar multiplication. But cannot find in CryptoPP method like getAffineXCoord()? Can it be done with CryptoPP like one-two-three?
Message has been deleted

Миша Винник

unread,
Mar 28, 2018, 9:47:59 AM3/28/18
to Crypto++ Users
Is code in BouncyCastle
ECMultiplier basePointMultiplier = createBasePointMultiplier();



ECPoint[] ghTilde = new ECPoint[]{
 basePointMultiplier
.multiply(ecParams.getG(), r),
 ecPubKey
.getQ().multiply(rPrime)
};

similar to field.SimultaneousMultiply in CryptoPP? Or it is totally another thing? Can at last, CryptoPP do normalizing? Or all points are also normalized? Affine coordinatesretieving? Anything?!

Миша Винник

unread,
Mar 28, 2018, 11:08:32 AM3/28/18
to Crypto++ Users
Seems that no affine in necessary - because result matches with Agree. Is it so, Jeff? Is there any progress in Google Pay on your side? you mentioned, you started to work on it

Миша Винник

unread,
Mar 28, 2018, 11:54:08 AM3/28/18
to Crypto++ Users
FUCKING YES! DOWNED IT!
Code for wiki will be on this week. 
Message has been deleted

Олег Ткаченко

unread,
Feb 6, 2020, 5:41:39 AM2/6/20
to Crypto++ Users

среда, 28 марта 2018 г., 18:54:08 UTC+3 пользователь Миша Винник написал:
FUCKING YES! DOWNED IT!
Code for wiki will be on this week. 

Привет! Можешь кодом поделиться? В вики я так понял не выложили?

Миша Винник

unread,
Feb 6, 2020, 6:13:19 AM2/6/20
to cryptop...@googlegroups.com
Привет!

Под NDA и на прошлой работе. Пороюсь у себя, если найду что-то - брошу.
В вики в целом потому и не выложили-то - по факту мне нужно было вычленить из прилаги работающий кусок и собрать в отдельное приложение. А задач как всегда море.

Короче, сегодня-завтра поищу. А тебе для чего?


Олег Ткаченко

unread,
Feb 6, 2020, 7:18:35 AM2/6/20
to Crypto++ Users

Мне нужно расшифровывать ответ от Google Pay.
Вот мне нужен аналог для C++.
Уже вторую неделю пытаемся на тинке реализовать, но бестолку. Не особо он подходит для этих целей на c++.
Reply all
Reply to author
Forward
0 new messages