R2D2

CircleCI

R2D2 is a Ruby library for decrypting Google Pay and Android Pay payment tokens.

Ruby support

Currently, Ruby v2.2 is supported. For Ruby >= 2.3, work will need to be done (similar to what was done in Gala) to use a compatible aead decryption algorithm.

Install

Add to your Gemfile:

gem 'r2d2', git: 'https://github.com/spreedly/r2d2.git'

Google Pay Usage

For Google Pay, R2D2 requires the token values in the form of a JSON hash, your recipient_id, Google's verification_keys, and your private key.

Example Google Pay token values:

{
  "signature": "MEYCIQD5mAtwoptfXuDnEVvtSbPmRnkw94GXEHjog24SfIe4rAIhAKLeSY4xcHLK1liBoZFaeZG+FrqawI7Id2mJXwddP3KH",
  "protocolVersion": "ECv1",
  "signedMessage": "{\"encryptedMessage\":\"jzo38/Ufbt9qh/scrTJmG9v8Cgb7Y5S+zCTTbSou/NoLoE/XF9ixyIGNIspKkH4ulwwVX0/EoqKDKk86XDLw8qBjx1tfHefbLuhZbqkfu/8bs5D6QMz8LjcJU+EeXYcdZ+KeQ3jzrgS6B9CqEJJIF+PeySMJtTwF9Fh+X2sW4Yg0C34mHz0MHpVUpmzJZblTwzMkCVOdq7eMF9Ywb8kDnRFasMYALbRaEOMg2o9gXSfGEVPhS8ors4SRFcnLoVPfktHRJtY/UZEREJvGFY/s/wpmU9sRADYTMKQ/ChTMumT+1NG0r4XibDcaZjW/Wlz1Dwog+dNMYUblPjY613sBLtjoBbRDYYVuDn/TUYXOJwAgXoHFfMmvWm0ne0n9eXggxoaMFFgF5zXk9ZLl3FyH/hi3WWtsFt5sqQWgFdjsqTriL6i46m46hMaZ9gKZ8JQE912IG5kZts5L8XSMiG94Z3UiTA\\u003d\\u003d\",\"ephemeralPublicKey\":\"BIeq42AvLcEhz0oLmYdj++oBTS5PD131FAEgx4y91cwqbkZMUKADkzj2bD4MxneqgqFYirO29+y/G6YH9zmfjlk\\u003d\",\"tag\":\"sRILsawzbm53+9tVTh9ooBP5ivzxWki73UJbuOZ3IYY\\u003d\"}"
}

The recipient_id will be in the form gateway:processorname.

The verificiation_keys are available in Google's developer docs. Example:

{ "keys":
  [
    { "keyValue":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIsFro6K+IUxRr4yFTOTO+kFCCEvHo7B9IOMLxah6c977oFzX\/beObH4a9OfosMHmft3JJZ6B3xpjIb8kduK4\/A==",
      "protocolVersion":"ECv1"
    }
  ]
}
require 'r2d2'

# token_attrs = Google Pay token values { "signature": "...", "protocolVersion": "...", ...}
token = R2D2.build_token(token_attrs, recipient_id: recipient_id, verification_keys: verification_keys)

private_key_pem = File.read('private_key.pem')
decrypted_json = token.decrypt(private_key_pem)

JSON.parse(decrypted_json)
# =>
{
  "gatewayMerchantId" => "exampleGatewayMerchantId",
  "messageExpiration" => "1528716120231", 
  "messageId" => "AH2EjtcpVGS3JvxlTP5kUbx3h0Laa30uVKjB9CqmnYiw8gZ-tpsxIoOdTbAU_DtCbkLVUPzkFeeqSbU1vTbAIAE4LlPHJqBiMMF4hZ5KRafml3764_6lK7aH7cQkIma40CI-rtCWTLCk",
  "paymentMethod" => "CARD",
  "paymentMethodDetails" =>
  {
    "expirationYear" => 2023,
    "expirationMonth" => 12,
    "pan" => "4111111111111111"
  }
}

Android Pay Usage

R2D2 takes input in the form of the hash of Android Pay token values:

{
  "encryptedMessage": "ZW5jcnlwdGVkTWVzc2FnZQ==",
  "ephemeralPublicKey": "ZXBoZW1lcmFsUHVibGljS2V5",
  "tag": "c2lnbmF0dXJl"
}

and the merchant's private key (which is managed by a third-party such as a gateway or independent processor like Spreedly).

require 'r2d2'

# token_json = raw token string you get from Android Pay { "encryptedMessage": "...", "tag": "...", ...}
token = R2D2.build_token(token_attrs)

private_key_pem = File.read('private_key.pem')
decrypted_json = token.decrypt(private_key_pem)

JSON.parse(decrypted_json)
# =>
{
  “dpan”: “4444444444444444”,
  “expirationMonth”: 10,
  “expirationYear”: 2015 ,
  “authMethod”: “3DS”,
  “3dsCryptogram”: “AAAAAA...”,
  “3dsEciIndicator”: “eci indicator”
}

Performance

The library implements a constant time comparison algorithm for preventing timing attacks. The default pure ruby implementation is quite inefficient, but portable. If performance is a priority for you, you can use a faster comparison algorithm provided by the fast_secure_compare.

To enable FastSecureCompare in your environment, add the following to your Gemfile:

gem 'fast_secure_compare'

and require the extension in your application prior to loading r2d2:

require 'fast_secure_compare/fast_secure_compare'
require 'r2d2'

Benchmarks illustrating the overhead of the pure Ruby version:

                          user     system      total        real
secure_compare        1.070000   0.010000   1.080000 (  1.231714)
fast secure_compare   0.050000   0.000000   0.050000 (  0.049753)

Testing

$ bundle exec rake
...
5 tests, 18 assertions, 0 failures, 0 errors, 0 skips

Releasing

To cut a new gem:

Setup RubyGems account

Make sure you have a RubyGems account and have setup your local gem credentials with something like this:

$ curl -u rwdaigle https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
<enter rubygems account password>

If you are not yet listed as a gem owner, you will need to request access from @rwdaigle.

Release

Build and release the gem with (all changes should be committed and pushed to Github):

$ rake release

Changelog

v1.0.0

  • Breaking Changes: API now decrypts both Google Pay and Android Pay tokens
  • New method call to decrypt Android Pay tokens
  • Additional arguments included for Google Pay tokens
  • Update README.md

v0.1.2

  • Setup CircleCI for more exhaustive Ruby version compatibility tests
  • Add gem release instructions

Contributors