# Seed/key recovery — findings ## Question Is the Triumph Keihin KWP2000 Security Access (service `0x27`) seed/key algorithm recoverable, and does the logging APK help? ## Answer **Yes, fully — and from the *static* decompile, so the logging build isn't needed for it.** The algorithm is **AES-128**, and all key material is embedded in the app. RESEARCH.md's prediction ("Honda-style XOR / bit-shift with a factory constant, possibly brute-forced") was wrong about the primitive. ## Where it lives `com/tuneecu/m.java`, method `Vb()` (jadx), = `Lcom/tuneecu/m;->Vb()I` (smali). | Evidence in `Vb()` | What it proves | |---|---| | `sArr = {1,2,4,8,16,32,64,128,27,54}` | AES Rcon (0x01,02,04,08,10,20,40,80,**1B,36**) | | 4 table lookups/column at `+0,+256,+512,+768` | AES T-tables (Te0–Te3) | | 10-iteration loop building 44 words | AES-128 key expansion | | 9 rounds + 1 S-box-only final round | AES-128 encryption | | `iArr3 = w.f` first entry `0xA56363C6` | `(3·S0, S0, S0, 2·S0)` with S0=0x63 → standard S-box + MixColumns | **Verified:** all 256 entries of `w.f` match the standard AES Te0 table exactly (`keihin_seedkey.py` cross-checks them). Our reference AES also passes the FIPS-197 Appendix-B known-answer test. This is unmodified AES-128. ## What `Vb()` does 1. Loads a 128-bit seed block into `Ie[0..3]` from the ECU's `0x27` seed response. 2. AES-128-encrypts it with one of **three** embedded keys (`iArr4`, m.java:5351), selected by `MainActivity.h7 ∈ {0,1,2}`. 3. Returns the first ciphertext word; `Gd()` sends it as `27 02 <4 bytes>`. The three keys (little-endian, as the cipher consumes them): ``` h7=0: ef704ca051b800cc9287df6a3511a978 h7=1: d4b15ff4c92ab7f098316e7a5b11ac39 h7=2: dc9fdba46f2fad18a4b8e1123c7183c2 ``` `h7` is looked up from an ECU calibration-code string (`MainActivity.java:6221`). ## Still open (framing, not crypto — one captured pair resolves both) 1. **Which key** applies to a mechanical-odometer 865 twin. `h7` defaults toward 0 for the older no-code Keihin maps, but confirm rather than assume. 2. **Exact seed-block layout.** `Vb()` encrypts `Ie[0..3]`; m.java fills `Ie[1..3]` from the seed response and pads two bytes with fixed constants (`0x03`, `0x01`), with `Ie[0]` set on an earlier path. A single real (seed, key) pair pins this down exactly. ## The logging APK `work/TuneECU-logging.apk` is a clean apktool rebuild of the original (the ~17 KB size delta is re-signing/recompression). No custom instrumentation was found — every `Log` call is TuneECU's own, gated behind its debug boolean, and no `debuggable` flag is set. So it does not currently capture seed/key at runtime. It's still the right tool for the **validation** step above: make the app perform one real Security Access against the bike (or a bench ECU) and capture the `27 01` seed and `27 02` key off the wire, then check `compute_key(seed, h7)` reproduces it. That either confirms the port outright or tells us the exact `h7`/padding. ## Reference implementation `research/keihin_seedkey.py` — pure-Python, self-testing. Run it: ```sh python3 research/keihin_seedkey.py ``` ## Boundary This is **write-path** material. It is kept in `research/`, outside the read-only `tunie` package, and `tunie` does not import it. Reproducing the key does not make flashing safe — it's one of several pieces (checksum patching, a verified stock dump, a recovery plan, ideally a spare ECU) that all have to line up first.