mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2024-09-20 15:26:13 +08:00
Add id48lib and second half of key recovery.
This commit is contained in:
parent
5b038631ca
commit
4ebd6d4bff
|
@ -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)
|
||||||
|
|
|
@ -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 $@)
|
||||||
|
|
|
@ -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
21
client/deps/id48/LICENSE
Normal 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
13
client/deps/id48/Makefile
Normal 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
|
50
client/deps/id48/README.md
Normal file
50
client/deps/id48/README.md
Normal 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.
|
777
client/deps/id48/TECHNICAL.md
Normal file
777
client/deps/id48/TECHNICAL.md
Normal 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
206
client/deps/id48/id48.h
Normal 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
8366
client/deps/id48/id48_data.c
Normal file
File diff suppressed because it is too large
Load diff
894
client/deps/id48/id48_generator.c
Normal file
894
client/deps/id48/id48_generator.c
Normal 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);
|
||||||
|
}
|
53
client/deps/id48/id48_internals.h
Normal file
53
client/deps/id48/id48_internals.h
Normal 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__)
|
439
client/deps/id48/id48_recover.c
Normal file
439
client/deps/id48/id48_recover.c
Normal 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);
|
||||||
|
}
|
9
client/deps/id48lib.cmake
Normal file
9
client/deps/id48lib.cmake
Normal 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)
|
|
@ -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}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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];
|
||||||
|
|
Loading…
Reference in a new issue