Decode Console

Decrypt a link

Paste a happ://crypt… deep link and fire the decoder. Everything runs locally in this tab — no payload leaves your browser.

Ctrl + Enter to run

Reverse Engineering happ:// — Decryption Dossier

This write-up summarizes how the Happ link formats were reverse- engineered and how the browser decrypts each generation locally.

5 generations 36 crypt5 marker keys Payload-derived marker Local browser runtime

Background: The happ:// URI Scheme

The happ:// URI scheme is a custom deep-link protocol used by a mobile application to wrap destination URLs in encrypted payloads. Instead of exposing a plain https:// link, the app emits a payload that only the bundled client logic can decode. This project reproduces that decode path entirely in the browser across every bundled generation, crypt through crypt5. JavaScript is the default for every known layout; native emulation is retained as a crypt5 compatibility fallback.

Generations 1–4: Hardcoded RSA Keys

The first four generations are direct RSA-PKCS1v15 wrappers. A target URL is encrypted, split into fixed-size blocks matching the key modulus, and Base64-encoded:

happ://crypt/<base64( RSA_block1 || RSA_block2 || ... )>
SchemeKey sizeBlock size (bytes)Algorithm
cryptRSA-1024128PKCS1v15
crypt2RSA-4096512PKCS1v15
crypt3RSA-4096512PKCS1v15
crypt4RSA-4096512PKCS1v15

To decrypt, the browser splits the Base64 payload back into RSA-sized blocks, decrypts each block independently, and concatenates the plaintext fragments to recover the final URL. The implementation accepts both standard and URL-safe Base64 alphabets, so the browser path normalizes - and _ and restores missing padding before decoding.

Each legacy private key is embedded directly in the application bytecode.

Generation 5: Direct RSA + ChaCha20-Poly1305

crypt5 combines a payload-derived marker, RSA-4096 key recovery, and authenticated ChaCha20-Poly1305 encryption. The browser now parses and decrypts this format directly, avoiding ARM64 instruction emulation during the normal path. Both the legacy body layout and the newer salted/XOR layout are recognized automatically.

Direct Pipeline

First, each complete four-character block has its halves swapped (ABCD → CDAB); a short tail is left unchanged. The first and last four characters of that result form an eight-character marker that selects a PKCS#8 private key. The middle body holds a 12-byte nonce, optional two-byte tag and eight-byte salt, a decimal segment length, a one-byte separator, the Base64 ChaCha ciphertext, and the Base64 RSA ciphertext.

In the legacy layout, the decimal length follows the nonce immediately. The salted layout inserts the tag and salt first. The parser tries the layout indicated by the first post-nonce byte, then tries the other layout if any parse, RSA, authentication, or decode step fails.

crypt5 URI payload ├─ swap 4-byte block halves // ABCD → CDAB ├─ marker = first 4 + last 4 bytes ├─ parse nonce + optional tag/salt + decimal segment length ├─ RSA-PKCS1v15 decrypt → pair-swapped Base64 key text ├─ swap adjacent bytes + Base64-decode → 32-byte RSA value ├─ salted layout: value XOR repeated 8-byte salt → ChaCha key ├─ ChaCha20-Poly1305 authenticate + decrypt ├─ swap adjacent Base64 bytes // ABCD → BADC └─ base64-decode → final URL

Compatibility Fallback

Direct decryption and its PKCS#8 table are attempted first. If loading that table, key lookup, parsing, RSA, authentication, or final decoding fails, the app lazily loads the existing liberror-code.so ARM64 emulator and retries the original native pipeline. Links handled directly never fetch the native library or unicorn.js runtime and never pay their CPU-emulation cost.

The direct path uses public/data/crypt5-keys.json for its 36 PKCS#8 keys. The fallback retains public/data/keytable.json, whose marker values are injected through the native library's JNI callback exactly as before. It also retains the native path's outer transforms: a six-character [1,3,5,0,2,4] shuffle before emulation, followed by an adjacent-pair swap and Base64 decode of the emulator's output.

Browser Implementation

Both decode paths run locally in the browser tab, with no server-side help.