├── .gitignore ├── assets ├── SMARTTAP.VASONLY.DEMO.webp └── SMARTTAP.VASANDPAY.DEMO.webp ├── resources └── README.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /assets/SMARTTAP.VASONLY.DEMO.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kormax/google-smart-tap/HEAD/assets/SMARTTAP.VASONLY.DEMO.webp -------------------------------------------------------------------------------- /assets/SMARTTAP.VASANDPAY.DEMO.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kormax/google-smart-tap/HEAD/assets/SMARTTAP.VASANDPAY.DEMO.webp -------------------------------------------------------------------------------- /resources/README.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | Any communication/other traces should be placed in this folder. 4 | 5 | A specific trace format is or file naming convention is not mandatory, but following conventions can be used: 6 | - For NFC, use words such as `request`/`response`, `outgoing`/`incoming`, or arrows `-->`/`<--`, `>`/`<` to denote commands and responses. 7 | Use `#` or `*` for comments or other notes; 8 | - Begin file name with communication type. Add 2 character username initials, and names of all known operations that happened during the communication, ending with success/fail denoting the end result; 9 | - Signify redacted data by sequences of `deadbeef` or `69`. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Smart Tap 2 | 3 | 4 |

5 | ![Smart Tap VAS only] 6 | ![Smart Tap VAS and payment] 7 |

8 | 9 | 10 | # Overview 11 | 12 | 13 | Google Smart Tap is a proprietary NFC protocol that can be used for sending data from a mobile device to an NFC terminal. 14 | 15 | Data is conveyed from the device to the terminal in encrypted form, using keys derived during the channel negotiation phase. At the moment of negotiation, the reader sends a device its collector id, key version, and a signature of derived data signed using the collector key, thus proving to the device that the reader is allowed to get the information. 16 | 17 | Only one pass (object) could be conveyed during a single tap (single read). 18 | If more than one pass is eligible for redemption, a selection carousel will appear and a user will be prompted to tap again. 19 | 20 | Version 2.1 was current at the time of writing. 21 | 22 | 23 | # Application identifiers 24 | 25 | 26 | Smart Tap can be activated using multiple application ids (AID): 27 | 28 | 1. Universal VAS AID (hex of encoded `OSE.VAS.01`), also used by Apple VAS. 29 | ``` 30 | 4f53452e5641532e3031 31 | ``` 32 | 2. Smart Tap 1 (Deprecated, does not work anymore) 33 | ``` 34 | a000000476d0000101 35 | ``` 36 | 3. Smart Tap 2 37 | ``` 38 | a000000476d0000111 39 | ``` 40 | 41 | The ususal implementation for most readers is to select `OSE.VAS.01` in order to detect what wallet provider is available on device (stored in TLV tag 50), if "AndroidPay" is the value, then we have a device with Google Wallet, and Smart Tap 2 can be reselected if required. 42 | As of version 2.1 device nonce and key is returned in OSE, so a separate selection of Smart Tap is not needed. 43 | 44 | 45 | # Command overview 46 | 47 | SmartTap-exclusive commands and responses use multi-layer nested NDEF messages and records for conveying information. 48 | As of version 2.1 following commands are available: 49 | 50 | | Command name | CLA | INS | P1 | P2 | DATA | LE | Response data | NOTES | 51 | | ------------------------ | --- | --- | --- | --- | ------------------------------------- | --- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 52 | | SELECT VAS APPLET | 00 | A4 | 04 | 00 | VAS AID | 00 | BER-TLV | Optional. Should be implemented if you want to support other wallets with value-added services, as this AID allows to find out what implementation is used without bruteforce | 53 | | SELECT SMART TAP APPLET | 00 | A4 | 04 | 00 | Smart Tap VX AID | 00 | NDEF message | Optional if SELECT VAS APPLET has been used. | 54 | | NEGOTIATE SECURE CHANNEL | 90 | 53 | 00 | 00 | NDEF message with nested NDEF message | 00 | NDEF message with nested NDEF | | 55 | | GET DATA | 90 | 50 | 00 | 00 | NDEF message with nested NDEF message | 00 | Part of NDEF message with encrypted and/or compressed nested NDEF | Can be used only after channel negotiation | 56 | | GET MORE DATA | 90 | C0 | 00 | 00 | No data (V 2.1) | 00 | Part of NDEF message with encrypted and/or compressed nested NDEF | Can be used if GET DATA response sw is 9100 | 57 | | PUSH DATA | 90 | 52 | 00 | 00 | NDEF message with nested NDEF | 00 | NDEF message with nested NDEF | Can be used before of after data read. Secure channel not needed. Exact use case of this command is not known. Possible use is to push signup URL but this feature seems to be disabled. | 58 | 59 | Commands are executed as follows: 60 | 1. SELECT VAS APPLET: 61 | Optional. May be the first command in a read flow. 62 | Reader transmits universal VAS AID; Device response with wallet implementation name in TLV tag `50`. 63 | If value is `416e64726f6964506179`, which is a value of `AndroidPay` string in ASCII-encoded form, we have a device that supports SmartTap. 64 | Device returns supported versions, other info. For, newer smart tap implementations it returns device nonce and device ephemeral key. 65 | 2. SELECT SMART TAP APPLET: 66 | Optional. May be the first command in a read flow, or a fallback if SELECT VAS APPLET returned no device nonce or device ephemeral key; 67 | Device returns version support range, and a device nonce. 68 | 3. NEGOTIATE SECURE CHANNEL: 69 | Reader generates a nonce, ephemeral public key. Reader generates a signature over concatenation of reader nonce, device nonce, collector id, and reader ephemeral public ke using a private key of collector. 70 | It then transmits session-related information together with the signature to the end device, proving to it that the reader is owned by a particular pass collector. 71 | Device responds with ephemeral public key, session information. 72 | Reader then uses all collected information in order to calculate session keys that will be used to encrypt and decrypt data. 73 | 4. GET DATA: 74 | Reader transmits its configuration, capabilityies, session information. 75 | Device responds with full or partial NDEF data with encrypted nested data. 76 | 5. GET MORE DATA: 77 | If a response to previous GET DATA or GET MORE DATA returned status word `91 00`, then more data has to be read. 78 | Reader transmits this command as many times as needed, until a device responds any response rather than `91 00`. 79 | 6. PUSH DATA: 80 | This command is not known to be used by any IRL readers. It was intended to be used by readers in order to push some information, like sign-up urls, pass data updates, pass addition, etc. 81 | None of the possible parameter combinations seem to have any visible effects, so theres a big chance that its a leftover of unfinished or cut functionality. 82 | Reader transmits session info, list of service statuses (changes to the passes, usage history, URL signups), total transaction info (ability to send receipts). 83 | Device responds with an acknowledgment and session info. 84 | 85 | 86 | 87 | # Entities, constants and data format 88 | 89 | SmartTap protocol uses NDEF records for transmitting data, sometimes in nested fashion. 90 | Before closer look at communication, it is important to undersand data representation used during communication. 91 | 92 | 93 | ## Data format 94 | 95 | Some records may contain a data format identifier in the first byte of payload 96 | 97 | Following data formats are known: 98 | 99 | | Name | Field | 100 | | ----------- | ----- | 101 | | UNSPECIFIED | 0x00 | 102 | | ASCII | 0x01 | 103 | | UTF_8 | 0x02 | 104 | | UTF_16 | 0x03 | 105 | | BINARY | 0x04 | 106 | | BCD | 0x05 | 107 | 108 | Most common used type is BINARY, but even it is not used for all payloads. 109 | There seems to be no particular pattern as for where data format identifier is mandatory, so its use will be mentioned for each record type. 110 | 111 | ## Record types 112 | 113 | Type in SmartTap defines what kind of object/data does the record hold. Each type is denoted by a specific short string. 114 | Depending on TNF, type value may be populated into either type or id fields: 115 | Type field follows following rules when being populated into NDEF Records 116 | 117 | | TNF | Field | 118 | | ---------------- | ----- | 119 | | WELL_KNOWN(0x01) | id | 120 | | EXTERNAL(0x04) | type | 121 | 122 | Older SmartTap versions required `id` field to be used instead to populate type, but new ones recognize only the described rules. 123 | 124 | 125 | ## Records 126 | 127 | Following types exist in SmartTap, but not all of them are used: 128 | 129 | | Name | Type | Payload | 130 | | --------------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | 131 | | HANDSET_NONCE | mdn | BINARY format flag + 32 byte long nonce | 132 | | SESSION | ses | 8 byte long id, 1 byte long sequence counter, 1 byte long status | 133 | | NEGOTIATE_REQUEST | ngr | 2 byte long version, nested NDEF message with `ses` and `cpr` records | 134 | | NEGOTIATE_RESPONSE | nrs | Nested NDEF message with `ses` and `dpk` records | 135 | | CRYPTO_PARAMS | cpr | 32 byte long reader nonce, 1 byte long auth flag, 33 byte long reader ephemeral public key, 4 byte long key version, NDEF message with `sig` and `cld` records | 136 | | SIGNATURE | sig | BINARY format flag + 72 byte long signature data encoded in ASN1 as Dss-Sig-Value | 137 | | SERVICE_REQUEST | srq | 2 byte long version, nested NDEF message with `ses`, `mer`, `slr`, `pcr` records | 138 | | ADDITIONAL_SERVICE_REQUEST | asr | Was used to get data continuation. Unused in SmartTap 2.0 | 139 | | SERVICE_RESPONSE | srs | Contains nested `ses` and `reb` records | 140 | | MERCHANT | mer | Nested NDEF message with mandatory `cld` and optional `lid`, `tid`, `mnr`, `mcr` records | 141 | | COLLECTOR_ID_V0 | mid | | 142 | | COLLECTOR_ID | cld | 4 byte long big endian representation of collector id number | 143 | | LOCATION_ID | lid | | 144 | | TERMINAL_ID | tid | | 145 | | MERCHANT_NAME | mnr | | 146 | | MERCHANT_CATEGORY | mcr | | 147 | | SERVICE_LIST | slr | Nested NDEF message with `str` record | 148 | | SERVICE_TYPE_REQUEST | str | List of 1 byte long service objects types to request | 149 | | HANDSET_EPHERMAL_PUBLIC_KEY | dpk | 33-byte long public ephemeral EC key | 150 | | ENCRYPTED_SERVICE_VALUE | enc | | 151 | | SERVICE_VALUE | asv | Contains `i` record. Depending on object type, may contain one of `cus`, `et`, `fl`, `gc`, `ly`, `of`, `pl`, `tr`, `gr` records. | 152 | | SERVICE_ID | sid | | 153 | | OBJECT_ID | oid | BINARY format flag + 8 byte identifier | 154 | | RECORD_BUNDLE | reb | First byte is response flag, other bytes is encrypted and/or compressed data depending on flag, containing `asv` object records | 155 | | CUSTOMER | cus | May be contained in unpacked record bundle. Contains | 156 | | CUSTOMER_ID | cid | BINARY format flag + 16 byte identifier | 157 | | CUSTOMER_LANGUAGE | cpl | NDEF-encoded string with language code | 158 | | CUSTOMER_TAP_ID | cut | BINARY format flag + 16 byte identifier | 159 | | EVENT | et | Object | 160 | | FLIGHT | fl | Object | 161 | | GIFT_CARD | gc | Object | 162 | | LOYALTY | ly | Object | 163 | | OFFER | of | Object | 164 | | PLC | pl | Object | 165 | | TRANSIT | tr | Object | 166 | | GENERIC | gr | Object | 167 | | ISSUER | i | BINARY format flag + 5 byte identifier | 168 | | SERVICE_NUMBER | n | UNSPECIFIED format flag + variable length value of a particular service. In essence, this is the pass data. | 169 | | TRANSACTION_COUNTER | tcr | | 170 | | PIN | p | | 171 | | EXPIRATION_DATE | ex | | 172 | | CVC | c1 | | 173 | | POS_CAPABILITIES | pcr | Contains 5 byte long flag POS CAPABILITIES mask | 174 | | PUSH_SERVICE_REQUEST | spr | Nested NDEF message with mandatory `ses` and optional, `bpr`, `nsr`, `ssr` records | 175 | | PUSH_SERVICE_RESPONSE | psr | Nested NDEF message with `ses` record | 176 | | SERVICE_STATUS | ssr | Nested NDEF message with `oid`, `sug` and `sup` records | 177 | | SERVICE_USAGE | sug | Nested NDEF message with `sut` and `sud` records | 178 | | SERVICE_USAGE_TITLE | sut | NDEF encoded text | 179 | | SERVICE_USAGE_DESCRIPTION | sud | NDEF encoded text | 180 | | SERVICE_UPDATE | sup | 1 byte long service operation code concatenated with value | 181 | | NEW_SERVICE | nsr | Nested NDEF message with `nst` and `nsu` records | 182 | | NEW_SERVICE_TITLE | nst | NDEF encoded text | 183 | | NEW_SERVICE_URI | nsu | NDEF encoded URI | 184 | | BASKET_PRICE | bpr | Nested NDEF message with `mon` and `ccd` records | 185 | | BASKET_PRICE_AMOUNT | mon | Numeric price value | 186 | | BASKET_PRICE_CURRENCY | ccd | NDEF encoded text currency code | 187 | 188 | Records marked as `Object` in value are transmitted in GET DATA response. 189 | Inner fields of objects depend on object type, but usually, each object contains at least `i` and `n` records in nested NDEF message. 190 | 191 | ## Statuses 192 | 193 | Status field is returned inside of the session record as the last byte. 194 | 195 | | Name | Value | 196 | | ----------------------- | ----- | 197 | | UNKNOWN | 0x00 | 198 | | OK | 0x01 | 199 | | NDEF_FORMAT_INVALID | 0x02 | 200 | | UNSUPPORTED_VERSION | 0x03 | 201 | | INVALID_SEQUENCE_NUMBER | 0x04 | 202 | | UNKNOWN_MERCHANT | 0x05 | 203 | | MERCHANT_INFO_MISSING | 0x06 | 204 | | SERVICE_DATA_MISSING | 0x07 | 205 | | RESEND_REQUEST | 0x08 | 206 | | DATA_NOT_AVAILABLE_YET | 0x09 | 207 | 208 | 209 | ## Terminal capabilities 210 | 211 | Used in side POS capabilities record. Capabilities are listed like bytes in order from left to right (aka reverse order) 212 | 213 | System capabilities: 214 | | Name | Value | Effect | 215 | | ---------------------- | ----- | -------------------------------------------- | 216 | | SYSTEM_STANDALONE | 0x01 | | 217 | | SYSTEM_SEMI_INTEGRATED | 0x02 | | 218 | | SYSTEM_UNATTENDED | 0x04 | | 219 | | SYSTEM_ONLINE | 0x08 | | 220 | | SYSTEM_OFFLINE | 0x10 | | 221 | | SYSTEM_MMP | 0x20 | | 222 | | SYSTEM_ZLIB_SUPPORTED | 0x40 | Pass data will be compressed. Adviced to use | 223 | 224 | User interface capabilities. No known effects: 225 | 226 | | Name | Value | Effect | 227 | | ------------------- | ----- | ------ | 228 | | UI_PRINTER | 0x01 | | 229 | | UI_PRINTER_GRAPHICS | 0x02 | | 230 | | UI_DISPLAY | 0x04 | | 231 | | UI_IMAGES | 0x08 | | 232 | | UI_AUDIO | 0x10 | | 233 | | UI_ANIMATION | 0x20 | | 234 | | UI_VIDEO | 0x40 | | 235 | 236 | Checkout capabilities. No known effects: 237 | 238 | | Name | Value | Effect | 239 | | --------------------------------- | ----- | ------ | 240 | | CHECKOUT_SUPPORT_PAYMENT | 0x01 | | 241 | | CHECKOUT_SUPPORT_DIGITAL_RECEIPT | 0x02 | | 242 | | CHECKOUT_SUPPORT_SERVICE_ISSUANCE | 0x04 | | 243 | | CHECKOUT_SUPPORT_OTA_POS_DATA | 0x08 | | 244 | 245 | CVM capabilities. No known effects: 246 | | Name | Value | Effect | 247 | | ------------------------- | ----- | ------ | 248 | | CVM_ONLINE_PIN | 0x01 | | 249 | | CVM_CD_PIN | 0x02 | | 250 | | CVM_SIGNATURE | 0x04 | | 251 | | CVM_NOCVM | 0x08 | | 252 | | CVM_DEVICE_GENERATED_CODE | 0x10 | | 253 | | CVM_SP_GENERATED_CODE | 0x20 | | 254 | | CVM_ID_CAPTURE | 0x40 | | 255 | | CVM_BIOMETRIC | 0x80 | | 256 | 257 | VAS Type capabilities. Set proper type for proper UI interaction: 258 | | Name | Value | Effect | 259 | | --------------------- | ----- | ------ | 260 | | TAP_PASS_ONLY | 0x01 | | 261 | | TAP_PAYMENT_ONLY | 0x02 | | 262 | | TAP_PASS_AND_PAYMENT | 0x04 | | 263 | | TAP_PASS_OVER_PAYMENT | 0x08 | | 264 | 265 | 266 | ## Request object type 267 | 268 | Used in service request list: 269 | | Name | Value | 270 | | ------------------------- | ----- | 271 | | ALL | 0x00 | 272 | | ALL_EXCEPT_PPSE | 0x01 | 273 | | PPSE | 0x02 | 274 | | LOYALTY | 0x03 | 275 | | OFFER | 0x04 | 276 | | GIFT_CARD | 0x05 | 277 | | PRIVATE_LABEL_CARD | 0x06 | 278 | | EVENT_TICKET | 0x07 | 279 | | FLIGHT | 0x08 | 280 | | TRANSIT | 0x09 | 281 | | CLOUD_BASED_WALLET | 0x10 | 282 | | MOBILE_MARKETING_PLATFORM | 0x11 | 283 | | GENERIC | 0x12 | 284 | | WALLET_CUSTOMER | 0x40 | 285 | 286 | 287 | ## Push Data related options 288 | 289 | PUSH DATA seems to be cut or limited in newer versions of SmartTap. This info is provided solely for reference, it has no use IRL. 290 | 291 | ### Push service type 292 | 293 | | Name | Value | 294 | | ----------- | ----- | 295 | | UNSPECIFIED | 0x00 | 296 | | VALUABLE | 0x01 | 297 | | RECEIPT | 0x02 | 298 | | SURVEY | 0x03 | 299 | | GOODS | 0x04 | 300 | | SIGNUP | 0x05 | 301 | 302 | ### Service status usage 303 | 304 | | Name | Value | 305 | | -------------- | ----- | 306 | | UNDEFINED | 0x00 | 307 | | SUCCESS | 0x01 | 308 | | INVALID_FORMAT | 0x02 | 309 | | INVALID_VALUE | 0x03 | 310 | | UNKNOWN | 0xff | 311 | 312 | ### Service update operation 313 | 314 | | Name | Value | 315 | | ---------------- | ----- | 316 | | NO_OP | 0x00 | 317 | | REMOVE | 0x01 | 318 | | SET_BALANCE | 0x02 | 319 | | ADD_BALANCE | 0x03 | 320 | | SUBTRACT_BALANCE | 0x04 | 321 | | FREE | 0x05 | 322 | | UNKNOWN | 0xFF | 323 | 324 | 325 | # Command and response data format 326 | 327 | ## Status words 328 | 329 | Following status words may be met during proper communication 330 | 331 | | SW1 | SW2 | Meaning | 332 | | --- | --- | ----------------------- | 333 | | 90 | 00 | Ok | 334 | | 90 | 91 | More data pending | 335 | | 90 | 01 | No passes | 336 | | 94 | 06 | Too many requests | 337 | | 93 | 02 | User has to choose pass | 338 | 339 | ## SELECT VAS APPLET 340 | 341 | ### Request: 342 | 343 | | CLA | INS | P1 | P2 | DATA | LE | 344 | | --- | --- | --- | --- | ---------------------- | --- | 345 | | 00 | A4 | 04 | 00 | `4f53452e5641532e3031` | 00 | 346 | 347 | Data contains an ASCII encoded form of "OSE.VAS.01" string; 348 | 349 | ### Response 350 | 351 | | SW1 | SW2 | DATA | 352 | | --- | --- | ------------------------------------------ | 353 | | 90 | 00 | Dynamic. Data format below. 135 bytes long | 354 | 355 | 356 | Response data example: 357 | - Payload: 358 | - `6f8184500a416e64726f6964506179c0020001c108cc00000000008080c22056d2ec8f857f0049aa54f1ca1de2791b5693a7014e6e4565d5644b1c2a305136c32103dfee38dbdb68a607383ad622640b180cc7e27d796b4e788c40e5d994291c71fca523bf0c20611e4f09a000000476d0000111870101730edf6d020000df4d020001df620103` 359 | - TLV decoded: 360 | ``` 361 | TLV: 362 | 6f[8184]: # File Control Information Template 363 | 50[0a]: # Application Label 364 | 416e64726f6964506179 # ASCII form of "AndroidPay" 365 | c0[02]: # Application version 366 | 0001 367 | c1[08]: # Unknown. Feature flags? 368 | cc00000000008080 369 | c2[20]: # Mobile device Nonce 370 | 56d2ec8f857f0049aa54f1ca1de2791b5693a7014e6e4565d5644b1c2a305136 371 | c3[21]: # Mobile device ephemeral key 372 | 03dfee38dbdb68a607383ad622640b180cc7e27d796b4e788c40e5d994291c71fc 373 | a5[23]: 374 | bf0c[20]: 375 | 61[1e]: 376 | 4f[09]: # Application ID 377 | a000000476d0000111 378 | 87[01]: # Priority 379 | 01 380 | 73[0e]: # Proprietary data 381 | df6d[02]: # Minimum version 382 | 0000 383 | df4d[02]: # Maximum version 384 | 0001 385 | df62[01]: # Unknown. Format flags? 386 | 03 387 | ``` 388 | 389 | Tags `c1`, `c2`, `c3`, `a5`, `73` may be missing depending on device software version. Be aware of this fact. 390 | 391 | 392 | ## SELECT SMART TAP APPLET 393 | 394 | ### Request: 395 | 396 | | CLA | INS | P1 | P2 | DATA | LE | 397 | | --- | --- | --- | --- | -------------------- | --- | 398 | | 00 | A4 | 04 | 00 | `a000000476d0000111` | 00 | 399 | 400 | ### Response 401 | 402 | | SW1 | SW2 | DATA | 403 | | --- | --- | ----------------------------------------- | 404 | | 90 | 00 | Dynamic. Data format below. 47 bytes long | 405 | 406 | Response data example: 407 | - Payload: 408 | - `00000001dc0321036d646e6d646e0456d2ec8f857f0049aa54f1ca1de2791b5693a7014e6e4565d5644b1c2a305136` 409 | - Decoded: 410 | ``` 411 | min_version=0000 412 | max_version=0001 413 | message=NDEFMessage( 414 | NDEFRecord( 415 | tnf=EXTERNAL(04), 416 | type=bytearray(b'mdn'), 417 | id=bytearray(b'mdn'), 418 | payload=0x0456d2ec8f857f0049aa54f1ca1de2791b5693a7014e6e4565d5644b1c2a305136 419 | ) 420 | ) 421 | ``` 422 | 423 | ## NEGOTIATE SECURE CHANNEL 424 | 425 | ### Request: 426 | 427 | | CLA | INS | P1 | P2 | DATA | LE | 428 | | --- | --- | --- | --- | --------------------------- | --- | 429 | | 90 | 53 | 00 | 00 | Dynamic. Data format below. | 00 | 430 | 431 | Command data example: 432 | - Payload: 433 | - `d403b86e6772000194030a7365736b159a80fc8283fd00015403a0637072a8aa2bae1ba891783d8c5be8a95bf2f9e5bb90fd9d197f8b2b1a84d9cc80427501027b2e12f1a1a542084b4d01b8799380fa4cb77e530ba2305b0bf2b3e4b474fe7d0000000194034973696704304602210086e43dc483b22e51aa177ae8112ed83d399a58b41d6d8cbe900cde03c4524da5022100e94b6a919c9e097568f4efa9a7123b86b97ee44342593f8a77fc9e12e3f95ae4540305636c640401020304` 434 | - Decoded: 435 | ``` 436 | message=NDEFMessage( 437 | NDEFRecord( 438 | tnf=EXTERNAL(04), 439 | type=b'ngr', 440 | id=b'', 441 | payload=[ 442 | Version(00:01), 443 | NDEFMessage( 444 | NDEFRecord( 445 | tnf=EXTERNAL(04), 446 | type=b'ses', 447 | id=b'', 448 | payload=[ 449 | 0x6b159a80fc8283fd, 450 | 0x00, 451 | OK(01) 452 | ] 453 | ), 454 | NDEFRecord( 455 | tnf=EXTERNAL(04), 456 | type=b'cpr', 457 | id=b'', 458 | payload=[ 459 | 0xa8aa2bae1ba891783d8c5be8a95bf2f9e5bb90fd9d197f8b2b1a84d9cc804275, 460 | 0x01, 461 | 0x027b2e12f1a1a542084b4d01b8799380fa4cb77e530ba2305b0bf2b3e4b474fe7d, 462 | 0x00000001, 463 | NDEFMessage( 464 | NDEFRecord( 465 | tnf=EXTERNAL(04), 466 | type=b'sig', 467 | id=b'', 468 | payload=[ 469 | BINARY(04), 470 | 0x304602210086e43dc483b22e51aa177ae8112ed83d399a58b41d6d8cbe900cde03c4524d 471 | a5022100e94b6a919c9e097568f4efa9a7123b86b97ee44342593f8a77fc9e12e3f95ae4 472 | ] 473 | ), 474 | NDEFRecord( 475 | tnf=EXTERNAL(04), 476 | type=b'cld', 477 | id=b'', 478 | payload=[ 479 | BINARY(04), 480 | 0x01020304 481 | ] 482 | ) 483 | ) 484 | ] 485 | ) 486 | ) 487 | ] 488 | ) 489 | ) 490 | ``` 491 | 492 | ### Response 493 | 494 | | SW1 | SW2 | DATA | 495 | | --- | --- | ----------------------------------------- | 496 | | 90 | 00 | Dynamic. Data format below. 61 bytes long | 497 | 498 | Response data example: 499 | - Payload: 500 | - `d403376e727394030a7365736b159a80fc8283fd010154032164706b03dfee38dbdb68a607383ad622640b180cc7e27d796b4e788c40e5d994291c71fc` 501 | - Decoded: 502 | ``` 503 | NDEFMessage( 504 | NDEFRecord( 505 | tnf=EXTERNAL(04), 506 | type=bytearray(b'nrs'), 507 | id=bytearray(b''), 508 | payload=NDEFMessage( 509 | NDEFRecord( 510 | tnf=EXTERNAL(04), 511 | type=b'ses', 512 | id=b'', 513 | payload=[ 514 | 0x6b159a80fc8283fd, 515 | 0x01, 516 | 0x01 517 | ] 518 | ), 519 | NDEFRecord( 520 | tnf=EXTERNAL(04), 521 | type=b'dpk', 522 | id=b'', 523 | payload=[ 524 | 0x03dfee38dbdb68a607383ad622640b180cc7e27d796b4e788c40e5d994291c71fc 525 | ] 526 | ) 527 | ) 528 | ) 529 | ) 530 | ``` 531 | 532 | ## GET DATA 533 | 534 | ### Request: 535 | 536 | | CLA | INS | P1 | P2 | DATA | LE | 537 | | --- | --- | --- | --- | --------------------------- | --- | 538 | | 90 | 50 | 00 | 00 | Dynamic. Data format below. | 00 | 539 | 540 | Command data example: 541 | - Payload: 542 | - `d4033b737271000194030a7365736b159a80fc8283fd010114030b6d6572d40305636c640401020304140307736c72d40301737472005403057063724100000004` 543 | - Decoded: 544 | ``` 545 | message=NDEFMessage( 546 | NDEFRecord( 547 | tnf=EXTERNAL(04), 548 | type=b'srq', 549 | id=b'', 550 | payload=[ 551 | Version(00:01), 552 | NDEFMessage( 553 | NDEFRecord( 554 | tnf=EXTERNAL(04), 555 | type=b'ses', 556 | id=b'', 557 | payload=[ 558 | 0x6b159a80fc8283fd, 559 | 0x01, 560 | OK(01) 561 | ] 562 | ), 563 | NDEFRecord( 564 | tnf=EXTERNAL(04), 565 | type=b'mer', 566 | id=b'', 567 | payload=NDEFMessage( 568 | NDEFRecord( 569 | tnf=EXTERNAL(04), 570 | type=b'cld', 571 | id=b'', 572 | payload=[ 573 | BINARY(04), 574 | 0x01020304 575 | ] 576 | ) 577 | ) 578 | ), 579 | NDEFRecord( 580 | tnf=EXTERNAL(04), 581 | type=b'slr', 582 | id=b'', 583 | payload=NDEFMessage( 584 | NDEFRecord( 585 | tnf=EXTERNAL(04), 586 | type=b'str', 587 | id=b'' 588 | payload=0x00 589 | ) 590 | ) 591 | ), 592 | NDEFRecord( 593 | tnf=EXTERNAL(04), 594 | type=b'pcr', 595 | id=b'', 596 | payload=[SYSTEM_ZLIB_SUPPORTED(40) + SYSTEM_STANDALONE(01), 0x00, 0x00, 0x00, TAP_PASS_AND_PAYMENT(04)] 597 | ) 598 | ) 599 | ] 600 | ) 601 | ) 602 | ``` 603 | 604 | ### Response: 605 | 606 | | SW1 | SW2 | DATA | 607 | | --- | --- | --------------------------- | 608 | | XX | XX | Dynamic. Data format below. | 609 | 610 | Status word differs if: 611 | 1) Pass data is available; 612 | 2) Pass data is available, but more data is pending; 613 | 3) Pass data is unavailable; 614 | 4) User has to select a pass 615 | 616 | Response may contain a part or a full NDEF message with nested encrypted and or compressed nested NDEF message 617 | 618 | Full* response data example: 619 | - Payload: 620 | - `d403ce73727394030a7365736b159a80fc8283fd02015403b8726562031c5b7d2e9f0f6937a7c043dc6913dd976002ce5cf179dedbcaaab629442a89a72b6a92caa26e808903a6d02207fd2d42702161d21c76f0a2a135bd8d45ac796786787e268bcc7498d17eccab47ad98be601a3470618fcd9790b40e3c324fc01ec29a98d1cb784fd4390ea1b045437cfdfa121447854c5ae5375c3f721a7a9f80b7da35868e063f18c545f07d934e4cc557fc93ee52b6231c95ef2d2d1de6be82be0d76e8d555dc41cc4894e44af98c68e4245e5bb6cecc` 621 | - Decoded: 622 | ``` 623 | message=NDEFMessage( 624 | NDEFRecord( 625 | tnf=EXTERNAL(04), 626 | type=b'srs', 627 | id=b'', 628 | payload=NDEFMessage( 629 | NDEFRecord( 630 | tnf=EXTERNAL(04), 631 | type=b'ses', 632 | id=b'', 633 | payload=[ 634 | 0x6b159a80fc8283fd, 635 | 0x02, 636 | OK(01) 637 | ] 638 | ), 639 | NDEFRecord( 640 | tnf=EXTERNAL(04), 641 | type=b'reb', 642 | id=b'', 643 | payload=0x031c5b7d2e9f0f6937a7c043dc6913dd976002ce5cf179dedbcaaab629442a89a72b6a92caa26e808903a6d02207fd2d42702161d21c76f0a2a135bd8d45ac796786787e268bcc7498d17eccab47ad98be601a3470618fcd9790b40e3c324fc01ec29a98d1cb784fd4390ea1b045437cfdfa121447854c5ae5375c3f721a7a9f80b7da35868e063f18c545f07d934e4cc557fc93ee52b6231c95ef2d2d1de6be82be0d76e8d555dc41cc4894e44af98c68e4245e5bb6cecc 644 | ) 645 | ) 646 | ) 647 | ) 648 | ``` 649 | 650 | ## GET MORE DATA 651 | 652 | ### Request: 653 | 654 | | CLA | INS | P1 | P2 | DATA | LE | 655 | | --- | --- | --- | --- | ---- | --- | 656 | | 90 | c0 | 00 | 00 | None | 00 | 657 | 658 | ### Response: 659 | 660 | Same format and rules as in GET DATA 661 | 662 | 663 | ## PUSH DATA 664 | 665 | **This command has no effect and seems to be a leftover from unfinished/cut functionality.** 666 | Information is provided for reference only 667 | 668 | ### Request: 669 | 670 | | CLA | INS | P1 | P2 | DATA | LE | 671 | | --- | --- | --- | --- | --------------------------- | --- | 672 | | 90 | 52 | 00 | 00 | Dynamic. Data format below. | 00 | 673 | 674 | Command data example: 675 | - Payload: 676 | - `d403b9737072000194030a7365736b159a80fc8283fd02011403126270729403016d6f6e0059010303546363645553441403246e73720599010703546e737402656e5041535359010c03556e7375046578616d706c652e636f6d54035f7373729403096f696404010203dc3be19e4814034373756701990116035473757402656e534552564943455f55534147455f5449544c4559011c035473756402656e534552564943455f55534147455f4445534352495054494f4e54030173757005` 677 | - Decoded: 678 | ``` 679 | message=NDEFMessage( 680 | NDEFRecord( 681 | tnf=EXTERNAL(04), 682 | type=b'spr', 683 | id=b'', 684 | payload=[ 685 | Version(00:01), 686 | NDEFMessage( 687 | NDEFRecord( 688 | tnf=EXTERNAL(04), 689 | type=b'ses', 690 | id=b'', 691 | payload=[0x6b159a80fc8283fd, 0x02, OK(01)] 692 | ), 693 | NDEFRecord( 694 | tnf=EXTERNAL(04), 695 | type=b'bpr', 696 | id=b'', 697 | payload=NDEFMessage( 698 | NDEFRecord( 699 | tnf=EXTERNAL(04), 700 | type=b'mon', 701 | id=b'', 702 | payload=0x00 703 | ), 704 | NDEFRecord( 705 | tnf=WELL_KNOWN(01), 706 | type=b'T', 707 | id=b'ccd', 708 | payload=b'USD' 709 | ) 710 | ) 711 | ), 712 | NDEFRecord( 713 | tnf=EXTERNAL(04), 714 | type=b'nsr', 715 | id=b'', 716 | payload=[ 717 | SIGNUP(05), 718 | NDEFMessage( 719 | NDEFRecord( 720 | tnf=WELL_KNOWN(01), 721 | type=b'T', 722 | id=b'nst', 723 | payload=0x02656e50415353 724 | ), 725 | NDEFRecord( 726 | tnf=WELL_KNOWN(01), 727 | type=b'U', 728 | id=b'nsu', 729 | payload=0x046578616d706c652e636f6d 730 | ) 731 | ) 732 | ] 733 | ), 734 | NDEFRecord( 735 | tnf=EXTERNAL(04), 736 | type=b'ssr', 737 | id=b'', 738 | payload=NDEFMessage( 739 | NDEFRecord( 740 | tnf=EXTERNAL(04), 741 | type=b'oid', 742 | id=b'', 743 | payload=[BINARY(04), 0x01, 0x02, 0x03, 0xdc, 0x3b, 0xe1, 0x9e, 0x48] 744 | ), 745 | NDEFRecord( 746 | tnf=EXTERNAL(04), 747 | type=b'sug', 748 | id=b'', 749 | payload=[ 750 | SUCCESS(01), 751 | NDEFMessage( 752 | NDEFRecord( 753 | tnf=WELL_KNOWN(01), 754 | type=b'T', 755 | id=b'sut', 756 | payload=02656e534552564943455f55534147455f5449544c45 757 | ), 758 | NDEFRecord( 759 | tnf=WELL_KNOWN(01), 760 | type=b'T', 761 | id=b'sud', 762 | payload=02656e534552564943455f55534147455f4445534352495054494f4e 763 | ) 764 | ) 765 | ] 766 | ), 767 | NDEFRecord( 768 | tnf=EXTERNAL(04), 769 | type=b'sup', 770 | id=b'', 771 | payload=[FREE(05)] 772 | ) 773 | ) 774 | ) 775 | ) 776 | ] 777 | ) 778 | ) 779 | ``` 780 | 781 | ### Response 782 | 783 | | SW1 | SW2 | DATA | 784 | | --- | --- | ----------------------------------------- | 785 | | 90 | 00 | Dynamic. Data format below. 61 bytes long | 786 | 787 | Response data example: 788 | - Payload: 789 | - `d40310707372d4030a7365736b159a80fc8283fd0301` 790 | - Decoded: 791 | ``` 792 | message=NDEFMessage( 793 | NDEFRecord( 794 | tnf=EXTERNAL(04), 795 | type=b'psr', 796 | id=b'', 797 | payload=NDEFMessage( 798 | NDEFRecord( 799 | tnf=EXTERNAL(04), 800 | type=b'ses', 801 | id=b'', 802 | payload=0x6b159a80fc8283fd0301 803 | ) 804 | ) 805 | ) 806 | ) 807 | ``` 808 | 809 | # Cryptography 810 | 811 | ## NEGOTIATE SECURE CHANNEL Signature 812 | 813 | During the NEGOTIATE SECURE CHANNEL command, reader has to prove to the device that it is allowed to retreive particular objects (read as passes). 814 | 815 | To generate a proof, device uses a collector private key in order to sign following data retreived prior during communication: 816 | 817 | | Order | Name | Length | Example | Notes | 818 | | ----- | --------------------------------- | ------ | ------------------------------------------------------------------ | ----- | 819 | | 1 | reader_nonce | 32 | 7131b05f5cfbd94feae19204d59d4ee5a4ce8172462e3f4577426040916e5b48 | | 820 | | 2 | device_nonce | 32 | 00f363e09bd98d971bda253bb5e001e554d5255b6adf0713c8bfc7eea4e3957f | | 821 | | 3 | collector_id | 4 | 01020304 | | 822 | | 4 | reader_ephemeral_public_key_bytes | 33 | 03c3d36bf9509924f159e9b5f02cb3d479d2fde4dedde1a8054fd5018286b2e6f8 | | 823 | 824 | When concatenated, the byte string to sign would be: 825 | ``` 826 | data_to_sign = 7131b05f5cfbd94feae19204d59d4ee5a4ce8172462e3f4577426040916e5b4800f363e09bd98d971bda253bb5e001e554d5255b6adf0713c8bfc7eea4e3957f0102030403c3d36bf9509924f159e9b5f02cb3d479d2fde4dedde1a8054fd5018286b2e6f8` 827 | ``` 828 | 829 | Then, using reader long term private key (the version of which is defined inside `cpr` record), device generates a 72-byte long ASN1 encoded Dss-Sig-Value signature: 830 | ``` 831 | signature = 3046022100cc4414b542a2fc42d41a29da56e897cb38593380fe529d473f24b8c450f422c7022100f0160f981dd28ec2842f8ed5e9adc533b685258987fd602815caf88aa08f8ddd 832 | ``` 833 | 834 | Keep values of `data_to_sign` and `signature` in mind, as they'll be used later for session key generation. 835 | 836 | ## GET DATA Encryption 837 | 838 | To decrypt GET DATA response payload, we have to establish encryption keys. 839 | 840 | 841 | Shared key is generated using ECDH exchange of ephemeral device public and reader private keys: 842 | 843 | ``` 844 | shared_key = ECDH(reader_ephemeral_private_key, device_ephemeral_public_key) 845 | ``` 846 | 847 | Then, using the HKDF, data is generated with following parameters 848 | 849 | | Name | Value | 850 | | ------------ | --------------------------------- | 851 | | algorithm | SHA256 | 852 | | length | 48 | 853 | | salt | device_ephemeral_public_key_bytes | 854 | | shared_info | data_to_sign + signature | 855 | | key_material | shared_key | 856 | 857 | As a result, we get 48 bytes of keying material `derived_ephemeral_key`. 858 | 859 | First 16 bytes are used as an AES encryption key: 860 | `aes_key=derived_ephemeral_key[:16]` 861 | 862 | Next 32 bytes are used for HMAC key: 863 | `hmac_key=derived_ephemeral_key[16:]` 864 | 865 | Those keys are used to verify and decrypt GET DATA responses: 866 | Encryption/Decryption uses AES CTR algorithm. 867 | 868 | IV is provided in first 12 bytes of payload: 869 | `iv = payload[:12]` 870 | Ciphertext is the middle bytes starting from 12 and ending with -32 from the end: 871 | `ciphertext = payload[12:-32]` 872 | HMAC is provided in last 32 bytes of payload: 873 | `hmac = payload[-32:]` 874 | 875 | HMAC has to be verified over whole IV + Ciphertext value. 876 | For decryption, IV is appended with zero bytes until it's length becomes 16. 877 | 878 | Following Python pseudocode (with cryptography library) decribes the decryption proccess: 879 | ``` 880 | 881 | from cryptography.hazmat.primitives import hashes 882 | from cryptography.hazmat.primitives.asymmetric import ec 883 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 884 | from cryptography.hazmat.primitives.hmac import HMAC 885 | from cryptography.hazmat.primitives.kdf.hkdf import HKDF 886 | 887 | iv = payload[:12] 888 | ciphertext = payload[12:-32] 889 | hmac = payload[-32:] 890 | 891 | h = HMAC(hmac_key, hashes.SHA256()) 892 | h.update(iv + ciphertext) 893 | hmac_to_verify = h.finalize() 894 | 895 | cipher = Cipher(algorithms.AES(aes_key), modes.CTR(iv + (b'\x00' * (16 - len(iv))))) 896 | decryptor = cipher.decryptor() 897 | decrypted_data = decryptor.update(ciphertext) + decryptor.finalize() 898 | payload = decrypted_data 899 | ``` 900 | 901 | # Other 902 | 903 | ## Compression 904 | 905 | If ZLIB support has been toggled on in POS capabilities, inner decrypted payload will be compressed with ZLIB. 906 | To uncompress, use libraries or methods depending on your programming language of choice. 907 | 908 | # Notes 909 | 910 | - If you find any mistakes/typos or have extra information to add, feel free to raise an issue or create a pull request. 911 | - Information provided in this repository is intended for educational, research, and personal use. Its use in any other way is not encouraged. 912 | - **Beware** that SmartTap, just like any other proprietary technology, might be a subject to legal protections depending on jurisdiction. A mere fact of it being reverse-engineered **does not** always mean that it can be used in a commercial product as-is without causing an infringement. For use in commercial applications, you should contact Google through official channels in order to get approval. 913 | - Reverse-engineering efforts were started way before Google published an open-source implementation example at the end of 2022, which made this project somewhat obsolete. 914 | There are some aspects still left uncovered (such as data format, parameters, extra commands), goal of this repo would be to describe blind spots in more detail in the near future ©, plus to provide examples, such as communication logs. 915 | 916 | 917 | # References 918 | 919 | 920 | - Google resources: 921 | - [Google Smart Tap](https://developers.google.com/wallet/smart-tap); 922 | - [Smart Tap overview](https://developers.google.com/wallet/smart-tap/introduction/overview); 923 | - [Smart Tap communication flow](https://developers.google.com/wallet/smart-tap/introduction/communication-flow); 924 | - [Smart Tap example project](https://github.com/google-pay/smart-tap-sample-app). Note that it does not implement the full protocol, for instance "Get more data" and "Push data" commands are missing; 925 | - Analysed applications: 926 | - [Google Play services](https://play.google.com/store/apps/details?id=com.google.android.gms&hl=en_US); 927 | - [Google Wallet](https://wallet.google). 928 | - Software analysis tools: 929 | - [Jadx](https://github.com/skylot/jadx). 930 | --------------------------------------------------------------------------------