logo elektroda
logo elektroda
X
logo elektroda

New DeviceKey-Based Tuya Encrypted KV Decryption Method (BK7252U/TR6260/W800/LN8825B/RTL8720CM)

divadiow  41 1572 Cool? (+3)
📢 Listen (AI):
I've been trying to figure out why we cannot decrypt the key vault on some Tuya flash backups and why this seems to be the case for certain, typically older, platforms. BK7252U, TR6260, W800 to name a few.

The decryption method for BK7231N/T (and some RTLs) is known, though the Tuya 'seed' KEY_PART_1 has been known to deviate away from 8720_2M (effectively '8720') depending on the platform.

eg
Screenshot of BK7231 Easy UART Flasher showing Tuya config extraction tab

In an effort to find the assumed unknown, but present, seed key I spent quite a bit of time in LLM discussions, poring over various SDKs, programmatically trying different plain-text strings from dumps, PowerShell-generated key combinations numbering into the millions, breaking down libs with various csky tools (W800 dump).

What turned out to be different is that this “vault KV” path is not KEY_PART_1-seed driven at all.

Instead of the BK7231N/T-style config extractor model (where a platform “seed” influences the key used to decrypt a specific config blob), this vault mechanism is device-key centred and two-stage:

1) Key-record stage (per-device key material)
A dedicated “key record” region in flash contains the per-device keying material (including a 16-byte DeviceKey). That key record itself is wrapped/encrypted as a unit (sector/page sized, typically 4KB) and must be decrypted first before the DeviceKey can be read. This key-record page is wrapped using a fixed mechanism (typically a constant wrapper key), and decrypting it is what reveals the DeviceKey used for the vault pages.

2) Vault stage (derived vault key + page encryption)
The effective vault key is derived by combining a 16-byte BaseKey with that per-device 16-byte DeviceKey using bytewise addition mod 256. BaseKey is either caller-supplied (p_key) or constructed by the SDK when p_key is NULL (see below).

Code: Text
Log in, to see the code


The vault payload is then stored as fixed-size encrypted pages (typically 4KB each). Each decrypted page is expected to match a known page structure: a magic value in the header (e.g. 0x98761234) plus an integrity field (checksum or CRC) used to confirm the page decryption is correct.

Where the “default BaseKey” comes from (when not explicitly supplied)
The “default BaseKey” is not a flash-layout assumption; it comes from the NULL-key branch in Tuya’s DB init logic inside the prebuilt library.

Concretely, in libtuya_iot.a (object tuya_ws_db.c.o), ws_db_init checks whether p_key is NULL:

If p_key != NULL: it copies 16 bytes from the caller-provided buffer → BaseKey = p_key

If p_key == NULL: it constructs a 16-byte BaseKey in a 16-iteration loop by adding bytes from two embedded 16-byte constants.

Those two constants both contain the same ASCII seed:
HHRRQbyemofrtytf
(16 bytes)

So the NULL-key case is effectively:
BaseKey(byte_index) = (seed(byte_index) + seed(byte_index)) & 0xFF


Doubling the bytes of "HHRRQbyemofrtytf" yields:
9090a4a4a2c4f2cadadecce4e8f2e8cc

(which is the “Tuya default (NULL p_key)” BaseKey value).

How the JSON is recovered
Once the vault pages are decrypted correctly (i.e. page header/magic + checksum or CRC validates), the JSON is not separately encrypted again; it exists as plaintext within the reconstructed decrypted vault region. Extraction is therefore a straightforward carve/parse step over the decrypted bytes: locate candidate JSON starts ({ or [), bracket/quote-balance to a candidate end, then validate by parsing as JSON before emitting the object (with optional dedupe of identical objects/blocks when redundancy is present).

With that knowledge this tkinter/Python program was then developed with (a lot) of help from an LLM:

Screenshot of TY simple_flash Key Vault Decryptor with decoded JSON output

This uses the above mechanism to decrypt vault-style KV on dumps that are structured this way. I've added options to dedupe/collapse duplicate decoded objects (the vaults appear to contain duplicates / redundancy), export decoded JSON, export decrypted blobs (“swap” relates to a secondary region present in LN8825B dumps).

In summary, it turns out this method can successfully decrypt the KV on Tuya W800, TR6260, BK7252U, LN8825B and RTL8720CM. However, the key vaults don't appear to contain the same pin assignment information seen in successful extractions from BK7231N etc dumps.

Of all the dumps in Flashdumps, these can be decoded with this method:

Code: Text
Log in, to see the code


example JSON from a BK7252U:
Code: JSON
Log in, to see the code


This method may only apply to a minority of (mostly older) platforms, and the decoded JSON doesn’t appear to include pin assignments, so the immediate practical value is limited. That said, I still think it’s been a worthwhile adventure. Next step: should this be integrated into Easy Flasher?

About Author
divadiow
divadiow wrote 4570 posts with rating 810 , helped 400 times. Live in city Bristol. Been with us since 2023 year.

Comments

p.kaczmarek2 14 Jan 2026 09:57

Very interesting finding, how did you find that out? I think it's worth to add this to the flasher. However... that missing key issue we was searching for in Ghidra is still not yet solved? [Read more]

divadiow 14 Jan 2026 20:38

basically hours of trying stuff with LLM. So many angles explored, but ultimately feeding it the Tuya LN882X SDKs from the Lightning Semi FTP seemed to contain the answer. Expert handling of the LLM also... [Read more]

insmod 14 Jan 2026 20:53

While this can't extract pins from a backup, it's good for those with TuyaMCU devices. From https://www.elektroda.com/rtvforum/topic4097544.html { "tokenResponse": { ... [Read more]

divadiow 14 Jan 2026 21:19

true. I quite like seeing all the decoded json has to offer because it at the very least helps to label each dump file. ultimately, it'd be nice to see Easy Flasher support all methods and output full... [Read more]

p.kaczmarek2 14 Jan 2026 21:44

That's because there is some kind of secondary data structure, each fragment of ASCII text is prefixed with some kind of block header, and we don't parse it, we just naively skip non-printable charact... [Read more]

divadiow 16 Jan 2026 09:48

Good news. The pin data *is* present in at least ECR6600 KV, it's just the extractor was looking for real JSON. The extra info is stored as a brace-delimited key:value block with unquoted keys (not valid... [Read more]

divadiow 16 Jan 2026 10:01

RTL8720CM\Tuya_Master_Recessed_Downlight_(schemaID-000003wp7g)_key73ve9nrcgkhck_fy9psuhjzjga4tyg_CR3L_1.2.17.bin "data": { "Jsonver": "1.1.9", "brightmin": 10, "gmwb":... [Read more]

p.kaczmarek2 16 Jan 2026 10:07

Great finding, but I think that numerical values don't have to have quotes in JSON. Quotes are only required for keys (key names) and strings. I guess we need it in EasyFlasher [Read more]

divadiow 16 Jan 2026 12:26

ah OK. yes, a couple of new decoding and extraction methods now required in EF. + nice complete JSON output options Added after 2 [hours] 16 [minutes]: LN plain text https://obrazki.elektroda.pl/7066433600_1768562731_bigthumb.jpg... [Read more]

insmod 17 Jan 2026 05:29

https://github.com/openshwprojects/BK7231GUIFlashTool/pull/100 Based on TY_KV_Decrypt_Universal.py https://obrazki.elektroda.pl/4992193700_1768624084.png [Read more]

divadiow 17 Jan 2026 06:49

awesome! thank you ECR6600 Tuya_MCL-Y10-L-03W_keysceeymg7xqvk7_AXYU_1.0.15.bin https://obrazki.elektroda.pl/8892014300_1768628860_bigthumb.jpg Added after 2 [minutes]: RTL8720CM Tuya_Master_Recessed_Downlight_(schemaID-000003wp7g)_key73ve9nrcgkhck_fy9psuhjzjga4tyg_CR3L_1.2.17.bin... [Read more]

p.kaczmarek2 17 Jan 2026 11:54

Great! Ready to merge? [Read more]

divadiow 17 Jan 2026 12:56

so far so good for me in use and testing, but I've not been surgical re all changes in PR. [Read more]

insmod 18 Jan 2026 06:43

Debugging features in myDecrypt are gone (as is the function itself), because they're too slow. New algo scans everything in the dump, versus just after magic found. This solves the issue when MAGIC_FIRST_BLOCK... [Read more]

divadiow 18 Jan 2026 15:08

all good still? [Read more]

insmod 18 Jan 2026 23:25

It is, i've yet to encounter any error. It even successfully decrypts duplicate tuya configs in ~1gb file in about 15 seconds. I removed ILRepack from workflow, because antiviruses started going wild... [Read more]

divadiow 18 Jan 2026 23:48

excellent work [Read more]

insmod 19 Jan 2026 10:26

Added minimal KV parsing. (I don't know enough about the structure, but it often works. Is source available?) If user_param_key/baud_cfg checksum passes, then only that data is used. Otherwise it's using... [Read more]

divadiow 19 Jan 2026 11:17

nice https://obrazki.elektroda.pl/2279793600_1768817545_thumb.jpg Added after 5 [minutes]: with the XR806 offset info, shoud it read "And the Tuya section starts at 2052096 (0x1F5000),... [Read more]

%}