{"id":28497984,"url":"https://github.com/spreedly/r2d2","last_synced_at":"2025-07-03T13:31:58.834Z","repository":{"id":8783938,"uuid":"47289832","full_name":"spreedly/r2d2","owner":"spreedly","description":"Ruby library for decrypting Google Pay and Android Pay payment tokens","archived":false,"fork":false,"pushed_at":"2021-11-30T15:26:26.000Z","size":235,"stargazers_count":8,"open_issues_count":4,"forks_count":15,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-06-07T01:38:35.605Z","etag":null,"topics":["android-pay","ruby","spreedly"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spreedly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-12-02T21:32:24.000Z","updated_at":"2024-02-11T02:03:43.000Z","dependencies_parsed_at":"2022-08-07T04:16:46.087Z","dependency_job_id":null,"html_url":"https://github.com/spreedly/r2d2","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/spreedly/r2d2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spreedly%2Fr2d2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spreedly%2Fr2d2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spreedly%2Fr2d2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spreedly%2Fr2d2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spreedly","download_url":"https://codeload.github.com/spreedly/r2d2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spreedly%2Fr2d2/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263335421,"owners_count":23450833,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-pay","ruby","spreedly"],"created_at":"2025-06-08T13:30:40.920Z","updated_at":"2025-07-03T13:31:58.825Z","avatar_url":"https://github.com/spreedly.png","language":"Ruby","readme":"# R2D2\n\n[![CircleCI](https://circleci.com/gh/spreedly/r2d2.svg?style=svg)](https://circleci.com/gh/spreedly/r2d2)\n\nR2D2 is a Ruby library for decrypting Google Pay and Android Pay payment tokens.\n\n## Ruby support\n\nCurrently, Ruby v2.2 or later is supported.\n\n## Install\n\nAdd to your `Gemfile`:\n\n```ruby\ngem 'r2d2', git: 'https://github.com/spreedly/r2d2.git'\n```\n\n## Google Pay Usage\n\nFor Google Pay, R2D2 requires the token values in the form of a JSON hash, your `recipient_id`, Google's `verification_keys` \nfor the appropriate environment, and your private key.\n\nExample Google Pay token values:\n\n```json\n{\n  \"signature\": \"MEYCIQD5mAtwoptfXuDnEVvtSbPmRnkw94GXEHjog24SfIe4rAIhAKLeSY4xcHLK1liBoZFaeZG+FrqawI7Id2mJXwddP3KH\",\n  \"protocolVersion\": \"ECv1\",\n  \"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\\\"}\"\n}\n```\n\nThe `recipient_id` will be given to you by Google. Example: `merchant:12345678901234567890`. \n\nThe `verification_keys` must be fetched from Google's servers for the appropriate environment:\n- production: https://payments.developers.google.com/paymentmethodtoken/keys.json\n- test: https://payments.developers.google.com/paymentmethodtoken/test/keys.json\n\nIt's a good idea to cache these keys for performance and resiliency. The `Cache-Control: max-age` directive must be \nrespected to expire the cache. To prevent decryption failures by expiring caches, it's recommended by Google's \n[Tink](https://github.com/google/tink) reference library to pro-actively refresh the cache after half of the `max-age` \nduration has passed.\n \nThe JSON must be parsed into a Ruby hash before being passed to R2D2. Example:\n\n```ruby\n{\n  \"keys\" =\u003e\n    [\n      {\n        \"keyValue\" =\u003e \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIsFro6K+IUxRr4yFTOTO+kFCCEvHo7B9IOMLxah6c977oFzX/beObH4a9OfosMHmft3JJZ6B3xpjIb8kduK4/A==\", \n        \"protocolVersion\" =\u003e \"ECv1\"\n      }\n    ]\n}\n```\n\n```ruby\nrequire 'r2d2'\n\n# token_attrs = Google Pay token values { \"signature\": \"...\", \"protocolVersion\": \"...\", ...}\ntoken = R2D2.build_token(token_attrs, recipient_id: recipient_id, verification_keys: verification_keys)\n\nprivate_key_pem = File.read('private_key.pem')\ndecrypted_json = token.decrypt(private_key_pem)\n\nJSON.parse(decrypted_json)\n# =\u003e\n{\n  \"gatewayMerchantId\" =\u003e \"exampleGatewayMerchantId\",\n  \"messageExpiration\" =\u003e \"1528716120231\", \n  \"messageId\" =\u003e \"AH2EjtcpVGS3JvxlTP5kUbx3h0Laa30uVKjB9CqmnYiw8gZ-tpsxIoOdTbAU_DtCbkLVUPzkFeeqSbU1vTbAIAE4LlPHJqBiMMF4hZ5KRafml3764_6lK7aH7cQkIma40CI-rtCWTLCk\",\n  \"paymentMethod\" =\u003e \"CARD\",\n  \"paymentMethodDetails\" =\u003e\n  {\n    \"expirationYear\" =\u003e 2023,\n    \"expirationMonth\" =\u003e 12,\n    \"pan\" =\u003e \"4111111111111111\"\n  }\n}\n```\n\n\n## Android Pay Usage\n\nR2D2 takes input in the form of the hash of Android Pay token values:\n\n```json\n{\n  \"encryptedMessage\": \"ZW5jcnlwdGVkTWVzc2FnZQ==\",\n  \"ephemeralPublicKey\": \"ZXBoZW1lcmFsUHVibGljS2V5\",\n  \"tag\": \"c2lnbmF0dXJl\"\n}\n```\n\nand the merchant's private key (which is managed by a third-party such as a gateway or independent processor like [Spreedly](https://spreedly.com)).\n\n```ruby\nrequire 'r2d2'\n\n# token_json = raw token string you get from Android Pay { \"encryptedMessage\": \"...\", \"tag\": \"...\", ...}\ntoken = R2D2.build_token(token_attrs)\n\nprivate_key_pem = File.read('private_key.pem')\ndecrypted_json = token.decrypt(private_key_pem)\n\nJSON.parse(decrypted_json)\n# =\u003e\n{\n  “dpan”: “4444444444444444”,\n  “expirationMonth”: 10,\n  “expirationYear”: 2015 ,\n  “authMethod”: “3DS”,\n  “3dsCryptogram”: “AAAAAA...”,\n  “3dsEciIndicator”: “eci indicator”\n}\n```\n\n## Performance\n\nThe 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](https://github.com/daxtens/fast_secure_compare).\n\nTo enable `FastSecureCompare` in your environment, add the following to your Gemfile:\n\n```ruby\ngem 'fast_secure_compare'\n```\n\nand require the extension in your application prior to loading r2d2:\n\n```ruby\nrequire 'fast_secure_compare/fast_secure_compare'\nrequire 'r2d2'\n```\n\nBenchmarks illustrating the overhead of the pure Ruby version:\n\n```\n                          user     system      total        real\nsecure_compare        1.070000   0.010000   1.080000 (  1.231714)\nfast secure_compare   0.050000   0.000000   0.050000 (  0.049753)\n```\n\n## Testing\n\n```session\n$ bundle exec rake\n...\n5 tests, 18 assertions, 0 failures, 0 errors, 0 skips\n```\n\n## Releasing\n\nTo cut a new gem:\n\n### Setup RubyGems account\n\nMake sure you have a [RubyGems account](https://rubygems.org) and have setup your local gem credentials with something like this:\n\n```bash\n$ curl -u rwdaigle https://rubygems.org/api/v1/api_key.yaml \u003e ~/.gem/credentials; chmod 0600 ~/.gem/credentials\n\u003center rubygems account password\u003e\n```\n\nIf you are not yet listed as a gem owner, you will need to [request access](https://github.com/rwdaigle) from @rwdaigle.\n\n### Release\n\nBuild and release the gem with (all changes should be committed and pushed to Github):\n\n```bash\n$ rake release\n```\n\n## Changelog\n\n### v1.0.0\n\n* Breaking Changes: API now decrypts both Google Pay and Android Pay tokens\n* New method call to decrypt Android Pay tokens\n* Additional arguments included for Google Pay tokens\n* Update README.md\n\n### v0.1.2\n\n* Setup CircleCI for more exhaustive Ruby version compatibility tests\n* Add gem release instructions\n\n## Contributors\n\n* [mrezentes](https://github.com/mrezentes)\n* [rwdaigle](https://github.com/rwdaigle)\n* [methodmissing](https://github.com/methodmissing)\n* [bdewater](https://github.com/bdewater)\n* [deedeelavinder](https://github.com/deedeelavinder)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspreedly%2Fr2d2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspreedly%2Fr2d2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspreedly%2Fr2d2/lists"}