├── .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 |
6 |
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 |
--------------------------------------------------------------------------------