Skip to content

Instantly share code, notes, and snippets.

@Liki4
Last active May 22, 2026 15:50
Show Gist options
  • Select an option

  • Save Liki4/d924257b6437acff98edda2b1c086c86 to your computer and use it in GitHub Desktop.

Select an option

Save Liki4/d924257b6437acff98edda2b1c086c86 to your computer and use it in GitHub Desktop.
Decrypt Keep App enkzip data

算法总结

enkzip 格式:<nonce_b64url>.<ciphertext+tag_b64url>,其中两部分都使用 base64url(无填充)编码。

加解密流程(Enigma.b.a / Enigma.b.b):

  1. 取 user_id:从 Authorization: Bearer 的 JWT payload 中读取 _id(24 位 ObjectId hex),见 libenigmaa.so.c:8958 中的 JWT 解析。
  2. 派生 AES 密钥:将 user_id 左旋 13 字节 → user_id[13:] + user_id[:13],对其做 MD5,把 16 字节摘要转成 32 位小写 hex,再以 ASCII 字节序列作为 AES‑256 密钥(32 字节)。libenigmaa.so.c:6693 中的反编译用 MD5 初值(0x67452301/0xefcdab89/0x98badcfe/0x10325476)和最后一个块拼接 param_2[0:8] + param_2[5:13] 到 offset 11/16,正好把原 _id 旋转成上述形式。
  3. AES‑256‑GCM:12 字节 nonce、空 AAD、16 字节 tag。libenigmaa.so.c:8025/8347 是 J0 = nonce‖0x00000001、E_K(J0) 做 tag mask 的标准 GCM。
"""
Keep enkzip decrypter.
Algorithm reverse-engineered from libenigmaa.so (Java_com_gotokeep_cryp_Enigma_decrypt
→ FUN_0011ae78 → FUN_0011f678 → FUN_001205e8/FUN_00120dac):
1. The "Bearer <jwt>" token is parsed as a JWT; the payload's "_id" field is
used as user_id (24-char Mongo ObjectId hex).
2. The bytes hashed are user_id rotated left by 13:
hash_input = user_id[13:] || user_id[:13]
(decompiled MD5 routine writes user_id[13:] into the buffer, then patches
in user_id[0:8] and user_id[5:13] on top — the net effect is the rotation.)
3. AES-256 key = lowercase hex(MD5(hash_input)).encode("ascii") (32 bytes).
4. enkzip is "<nonce_b64url>.<ciphertext+tag_b64url>".
- nonce_b64url decodes to 12 bytes (AES-GCM IV).
- ciphertext+tag_b64url decodes to ciphertext || tag(16) (AES-256-GCM).
5. Standard AES-256-GCM with empty AAD; the tag verifies.
"""
import base64
import hashlib
import json
import sys
from Crypto.Cipher import AES
def b64url(s: str) -> bytes:
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
def jwt_payload(token: str) -> dict:
return json.loads(b64url(token.split(".")[1]))
def derive_key(user_id: str) -> bytes:
rotated = user_id[13:] + user_id[:13]
return hashlib.md5(rotated.encode()).hexdigest().encode("ascii")
def decrypt_enkzip(enkzip: str, key: bytes) -> bytes:
nonce_part, body_part = enkzip.split(".", 1)
nonce = b64url(nonce_part)
blob = b64url(body_part)
ct, tag = blob[:-16], blob[-16:]
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
return cipher.decrypt_and_verify(ct, tag)
def encrypt_enkzip(plaintext: bytes, key: bytes, nonce: bytes) -> str:
assert len(nonce) == 12
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ct, tag = cipher.encrypt_and_digest(plaintext)
enc_nonce = base64.urlsafe_b64encode(nonce).rstrip(b"=").decode()
enc_body = base64.urlsafe_b64encode(ct + tag).rstrip(b"=").decode()
return f"{enc_nonce}.{enc_body}"
def main():
"""
token is jwt token located in Authorization header without `Bearer ` prefix
"""
token = "aaa.bbb.ccc"
enkzip = "ddd.eee"
user_id = jwt_payload(token)["_id"]
key = derive_key(user_id)
print(f"user_id : {user_id}")
print(f"hash input : {user_id[13:] + user_id[:13]}")
print(f"AES-256 key : {key.decode()} (md5 hex of rotated user_id, ASCII)")
print(f"enkzip : {len(enkzip)} chars ({enkzip[:40]}...)")
pt = decrypt_enkzip(enkzip, key)
print(f"plaintext : {len(pt)} bytes")
out_path = "res_decrypted.json"
with open(out_path, "wb") as f:
f.write(pt)
print(f"wrote {out_path}")
# Round-trip sanity check (pick the same nonce so we get the same enkzip).
nonce_part, _ = enkzip.split(".", 1)
nonce = b64url(nonce_part)
re_enc = encrypt_enkzip(pt, key, nonce)
assert re_enc == enkzip, "encrypt round-trip mismatch"
print("round-trip : OK (re-encrypt produces identical enkzip)")
head = json.loads(pt)
keys = list(head.keys()) if isinstance(head, dict) else []
print(f"top-level keys: {keys[:10]}{'...' if len(keys) > 10 else ''}")
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment