From 51f09a6295034e3b126a54ccad115d033b734acf Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 25 Oct 2023 13:57:29 +0900 Subject: [PATCH] Remove the need to generate random CardKey --- docs/DETERMINISTIC.md | 101 ++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/docs/DETERMINISTIC.md b/docs/DETERMINISTIC.md index 7d21948..eec8171 100644 --- a/docs/DETERMINISTIC.md +++ b/docs/DETERMINISTIC.md @@ -1,17 +1,17 @@ ## Abstract -The NXP NTAG424DNA allows applications to configure five application keys, named `K0`, `K1`, `K2`, `K3`, and `K4`. In the Bolt card configuration: +The NXP NTAG424DNA allows applications to configure five application keys, named `K0`, `K1`, `K2`, `K3`, and `K4`. In the BoltCard configuration: * `K0` is the `App Master Key`, it is the only key permitted to change the application keys. * `K1` serves as the `encryption key` for the `PICCData`, represented by the `p=` parameter. * `K2` is the `authentication key` used for calculating the SUN MAC of the `PICCData`, represented by the `c=` parameter. * `K3` and `K4` are not used but should be configured as recommended in the [NTag424 application notes](https://www.nxp.com/docs/en/application-note/AN12196.pdf). -A simplistic approach to issuing Bolt cards would involve randomly generating the five different keys and storing them in a database. +A simple approach to issuing BoltCards would involve randomly generating the five different keys and storing them in a database. When a validation request is made, the verifier would attempt to decrypt the `p=` parameter using all existing encryption keys until finding a match. Once decrypted, the `p=` parameter would reveal the card's uid, which can then be used to retrieve the remaining keys. -The primary drawback of this method is its lack of scalability. If many cards have been issued, identifying the correct encryption key could become computationally expensive. +The primary drawback of this method is its lack of scalability. If many cards have been issued, identifying the correct encryption key could become a computationally intensive task. In this document, we propose a solution to this issue. @@ -19,43 +19,55 @@ In this document, we propose a solution to this issue. First, the `LNUrl Withdraw Service` generates a `IssuerKey` that it will use to generate the keys for every NTag424. -Then configure a Boltcard the following way: +Then, configure a BoltCard as follows: -* `CardKey = GetRandomBytes(16)` -* `K0 = PRF(CardKey, '2d003f76' || UID)` +* `CardKey = PRF(IssuerKey, '2d003f75' || UID || Version)` +* `K0 = PRF(CardKey, '2d003f76')` * `K1 = PRF(IssuerKey, '2d003f77')` -* `K2 = PRF(CardKey, '2d003f78' || UID)` -* `K3 = PRF(CardKey, '2d003f79' || UID)` -* `K4 = PRF(CardKey, '2d003f7a' || UID)` +* `K2 = PRF(CardKey, '2d003f78')` +* `K3 = PRF(CardKey, '2d003f79')` +* `K4 = PRF(CardKey, '2d003f7a')` +* `ID = PRF(IssuerKey, '2d003f7b' || UID)` +With the following parameters: +* `IssuerKey`: This 16-bytes key is used by an `LNUrl Withdraw Service` to setup all its BoltCards. * `UID`: This is the 7-byte ID of the card. You can retrieve it from the NTag424 using the `GetCardUID` function after identification with K1, or by decrypting the `p=` parameter, also known as `PICCData`. +* `Version`: A 4-bytes little endian version number. This must be incremented every time the user re-programs (reset/setup) the same BoltCard on the same `LNUrl Withdraw Service`. The Pseudo Random Function `PRF(key, message)` applied during the key generation is the CMAC algorithm described in NIST Special Publication 800-38B. [See implementation notes](#notes) -## How to setup a new boltcard +## How to setup a new BoltCard -1. Generate a random `CardKey` of 16 bytes. -2. `ReadData` or `ISOReaDBinary` on the boltcard, to make sure the card is blank. -3. Execute `AuthenticateEV2First` with `00000000000000000000000000000000` -4. Fetch the `UID` with `GetCardUID`. -2. Calculate `K0`, `K1`, `K2`, `K3`, `K4`. -4. [Setup the boltcard](./CARD_MANUAL.md). +1. Execute `ReadData` or `ISOReaDBinary` on the BoltCard to ensure the card is blank. +2. Execute `AuthenticateEV2First` with the application key `00000000000000000000000000000000` +3. Fetch the `UID` with `GetCardUID`. +4. Calculate `ID` +5. Fetch the `State` and `Version` of the BoltCard with the specified `ID` from the database. +6. Ensure either: + * If no BoltCard is found, insert an entry in the database with `Version=0` and its state set to `Configured`. + * If a BoltCard is found and its state is `Reset` then increment `Version` by `1`, and change its state to `Configured`. +7. Generate `CardKey` with `UID` and `Version`. +8. Calculate `K0`, `K1`, `K2`, `K3`, `K4`. +9. [Setup the BoltCard](./CARD_MANUAL.md). ## How to implement a Reset feature -If a `LNUrl Withdraw Service` offers a factory reset feature for a user's bolt card, here is the recommended procedure: +If a `LNUrl Withdraw Service` offers a factory reset feature for a user's BoltCard, here is the recommended procedure: 1. Read the NDEF lnurlw URL, extract `p=` and `c=`. -2. Derive `Encryption Key (K1)`, decrypts `p=` to get the `PICCData`. +2. Derive `Encryption Key (K1)`, decrypt `p=` to obtain the `PICCData`. 3. Check `PICCData[0] == 0xc7`. -4. Calculate `ID=PRF(IssuerKey, '2d003f7b' || UID)` with the `UID` from the `PICCData`. -5. Fetch `CardKey` from database with `ID`. -6. Derive `K0`, `K2`, `K3`, `K4` with `CardKey` and the `UID`. -7. Verify that the SUN MAC in `c=` matches the one calculated using `Authentication Key (K2)`. -8. Execute `AuthenticateEV2First` with `K0` -9. Erase the NDEF data file using `WriteData` or `ISOUpdateBinary` -10. Restore the NDEF file settings to default values with `ChangeFileSettings`. -11. Use `ChangeKey` with the recovered application keys to reset `K4` through `K0` to `00000000000000000000000000000000`. +4. Calculate `ID` with the `UID` from the `PICCData`. +5. Fetch the BoltCard's `Version` with `ID` from the database. +6. Ensure the BoltCard's state is `Configured`. +7. Generate `CardKey` with `UID` and `Version`. +8. Derive `K0`, `K2`, `K3`, `K4` with `CardKey` and the `UID`. +9. Verify that the SUN MAC in `c=` matches the one calculated using `Authentication Key (K2)`. +10. Execute `AuthenticateEV2First` with `K0` +11. Erase the NDEF data file using `WriteData` or `ISOUpdateBinary` +12. Restore the NDEF file settings to default values with `ChangeFileSettings`. +13. Use `ChangeKey` with the recovered application keys to reset `K4` through `K0` to `00000000000000000000000000000000`. +14. Update the BoltCard's state to `Reset` in the database. Rational: Attempting to call `AuthenticateEV2First` without validating the `p=` and `c=` parameters could render the NTag inoperable after a few attempts. @@ -66,12 +78,14 @@ If a `LNUrl Withdraw Service` needs to verify a payment request, follow these st 1. Read the NDEF lnurlw URL, extract `p=` and `c=`. 2. Derive `Encryption Key (K1)`, decrypts `p=` to get the `PICCData`. 3. Check `PICCData[0] == 0xc7`. -4. Calculate `ID=PRF(IssuerKey, '2d003f7b' || UID)` with the `UID` from the `PICCData`. -5. Fetch `CardKey` from database with `ID`. -6. Derive `Authentication Key (K2)` with `CardKey` and the `UID`. -7. Verify that the SUN MAC in `c=` matches the one calculated using `Authentication Key (K2)`. -8. Confirm that the last-seen counter for `ID` is lower than what is stored in `counter=PICCData[8..11]`. (Little Endian) -9. Update the last-seen counter. +4. Calculate `ID` with the `UID` from the `PICCData`. +5. Fetch the BoltCard's `Version` with `ID` from the database. +6. Ensure the BoltCard's state in the database is not `Reset`. +7. Generate `CardKey` with `UID` and `Version`. +8. Derive `Authentication Key (K2)` with `CardKey` and the `UID`. +9. Verify that the SUN MAC in `c=` matches the one calculated using `Authentication Key (K2)`. +10. Confirm that the last-seen counter for `ID` is lower than what is stored in `counter=PICCData[8..11]`. (Little Endian) +11. Update the last-seen counter. Rationale: The `ID` is calculated to prevent the exposure of the `UID` in the `LNUrl Withdraw Service` database. This approach provides both privacy and security. Specifically, because the `UID` is used to derive keys, it is preferable not to store it outside the NTag. @@ -79,20 +93,20 @@ Rationale: The `ID` is calculated to prevent the exposure of the `UID` in the `L A single `LNUrl Withdraw Service` can own multiple `IssuerKeys`. In such cases, it will need to attempt them all to decrypt `p=`, and pick the first one which satisfies `PICCData[0] == 0xc7` and verifies the `c=` checksum. -Using multiple `IssuerKeys`, can decrease the impact of a compromised `Encryption Key (K1)` at the cost of performance. +Using multiple `IssuerKeys` can decrease the impact of a compromised `Encryption Key (K1)` at the cost of performance. ## Security consideration ### K1 security -Since `K1` is shared among multiple Bolt Cards, the security of this scheme is based on the following assumptions: +Since `K1` is shared among multiple BoltCards, the security of this scheme is based on the following assumptions: * `K1` cannot be extracted from a legitimate NTag424. -* Bolt Card setup occurs in a trusted environment. +* BoltCard setup occurs in a trusted environment. While NXP gives assurance keys can't be extracted, a non genuine NTag424 could potentially expose these keys. -Furthermore, because blank NTag424 uses the well-known initial application keys `00000000000000000000000000000000`, communication between the PCD and the PICC could be intercepted. If the Bolt Card setup doesn't occurs in a trusted environment, `K1` could be exposed during the calls to `ChangeKey`. +Furthermore, because blank NTag424 uses the well-known initial application keys `00000000000000000000000000000000`, communication between the PCD and the PICC could be intercepted. If the BoltCard setup does not occur in a trusted environment, `K1` could be exposed during the calls to `ChangeKey`. However, if `K1` is compromised, the attacker still cannot produce a valid checksum and can only recover the `UID` for tracking purposes. @@ -102,7 +116,7 @@ Note that verifying the signature returned by `Read_Sig` can only prove NXP issu If the issuer's database is compromised, revealing both the IssuerKey and CardKeys, it would still be infeasible for an attacker to derive `K2` and thus to forge signatures for an arbitrary card. -This is because the database only stores `ID=PRF(IssuerKey, '2d003f7b' || UID)` and not the `UID` itself. +This is because the database only stores `ID` and not the `UID` itself. ## Implementation notes {#notes} @@ -179,7 +193,7 @@ static byte[] RotateLeft(byte[] b) ## Implementation -* [BTCPayServer.BoltCardTools](https://github.com/btcpayserver/BTCPayServer.BoltCardTools), a Boltcard/NTag424 library in C#. +* [BTCPayServer.BoltCardTools](https://github.com/btcpayserver/BTCPayServer.BoltCardTools), a BoltCard/NTag424 library in C#. ## Test vectors @@ -187,15 +201,16 @@ Input: ``` UID: 04a39493cc8680 Issuer Key: 00000000000000000000000000000001 -Card Key: 00000000000000000000000000000002 +Version: 1 ``` Expected: ``` -K0: 21940feffa2437910d8eb62b3b0a0648 +K0: a29119fcb48e737d1591d3489557e49b K1: 55da174c9608993dc27bb3f30a4a7314 -K2: 2934c4ab339979142dfd50ae0ca55dc2 -K3: b696f18e5a79e5a0defb25c38109b8e3 -K4: c9d493b9d3e62ce963586aafcd7c6cfe +K2: f4b404be700ab285e333e32348fa3d3b +K3: 73610ba4afe45b55319691cb9489142f +K4: addd03e52964369be7f2967736b7bdb5 ID: e07ce1279d980ecb892a81924b67bf18 +CardKey: ebff5a4e6da5ee14cbfe720ae06fbed9 ``` \ No newline at end of file