logo elektroda
logo elektroda
X
logo elektroda

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

divadiow 201 2
ADVERTISEMENT
📢 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:

    ADVERTISEMENT


    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?

    Cool? Ranking DIY
    About Author
    divadiow
    Level 37  
    Offline 
    divadiow wrote 4280 posts with rating 754, helped 370 times. Live in city Bristol. Been with us since 2023 year.
  • ADVERTISEMENT
  • #2 21808335
    p.kaczmarek2
    Moderator Smart Home
    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?
    Helpful post? Buy me a coffee.
  • #3 21808412
    divadiow
    Level 37  
    p.kaczmarek2 wrote:
    Very interesting finding, how did you find that out?


    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 played a huge part of course ;)

    p.kaczmarek2 wrote:
    However... that missing key issue we was searching for in Ghidra is still not yet solved?


    I do recall, I think you're referring to the ECR6600 investigations in this thread: https://www.elektroda.com/rtvforum/topic4111822.html

    I would like to focus on ECR6600 next, it is still unknown.

    Added after 8 [minutes]:

    oh yeh. there was even some T-Head/CSKY debug server stuff with the W800 at one point to see if there was any sign of some ephemeral key in RAM at the app's boot/decryption stage. The STM32 CK-Link Lite without NRST limited my options/success somewhat.
📢 Listen (AI):
ADVERTISEMENT