Skip to content

spreedly/r2d2

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
lib
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

R2D2

CircleCI

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

Ruby support

Currently, Ruby v2.2 or later is supported.

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 for the appropriate environment, 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 given to you by Google. Example: merchant:12345678901234567890.

The verification_keys must be fetched from Google's servers for the appropriate environment:

It's a good idea to cache these keys for performance and resiliency. The Cache-Control: max-age directive must be respected to expire the cache. To prevent decryption failures by expiring caches, it's recommended by Google's Tink reference library to pro-actively refresh the cache after half of the max-age duration has passed.

The JSON must be parsed into a Ruby hash before being passed to R2D2. 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

About

Ruby library for decrypting Google Pay and Android Pay payment tokens

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages