Skip to content

Instantly share code, notes, and snippets.

@Pldare
Created July 26, 2026 18:12
Show Gist options
  • Select an option

  • Save Pldare/cca34b4d08fcb0bb33860972f965eb55 to your computer and use it in GitHub Desktop.

Select an option

Save Pldare/cca34b4d08fcb0bb33860972f965eb55 to your computer and use it in GitHub Desktop.
tokimeki-idol tool
"""
Decrypt DB/*.db.bytes files (SQLCipher-encrypted) to plaintext SQLite databases.
Reverse-engineered from libil2cpp.so (IL2CPP) via IDA Pro analysis.
=== Decryption Algorithm ===
Encryption: SQLCipher 3.x format
- AES-256-CBC
- PBKDF2-HMAC-SHA1 key derivation
- 64000 KDF iterations
- Page size: 1024 bytes
- Reserve: 48 bytes per page (16 IV + 20 HMAC + 12 padding)
- Password passed to sqlite3_key()
=== Password Derivation ===
Key function: ProjectIP.Auy.WTDataController<object>.CreateSQLiteConnection
Address: 0x1A04550 in libil2cpp.so
Logic (C# pseudocode):
string str2 = tableName + ".db";
string password;
int openFlags;
if (tableName.StartsWith("ms_"))
{
password = "password"; // hardcoded literal
openFlags = 196609; // READWRITE | CREATE | FULLMUTEX
}
else
{
string hashCode = ExtensionMethods.GetHashCode(str2);
string combined = "t0k1d0l4ever" + hashCode;
password = ExtensionMethods.GetHashCode(combined);
openFlags = 196614; // READWRITE | CREATE | FULLMUTEX | SHAREDCACHE
}
ExtensionMethods.GetHashCode (Address: 0x186E2EC):
1. UTF-8 encode input string
2. SHA1 hash
3. BitConverter.ToString(hash) -> "XX-XX-XX-..." (uppercase hex, dash-separated)
4. Replace("-", "") -> 40-char uppercase hex string
=== Verified Result ===
All 38 DB files in the DB/ directory start with "ms_", so the password is
the literal string "password" for every file.
Usage:
python decrypt_db.py # Decrypt all DB/*.db.bytes
python decrypt_db.py <file> <output> # Decrypt a single file
python decrypt_db.py list # List passwords for all files
"""
import hashlib
import os
import struct
import sys
from Crypto.Cipher import AES
# --- Password derivation ---
def get_hash_code(s: str) -> str:
"""Replicate ExtensionMethods.GetHashCode: SHA1 -> uppercase hex (no dashes)."""
b = s.encode("utf-8")
h = hashlib.sha1(b).digest()
hexstr = "-".join("{:02X}".format(x) for x in h)
return hexstr.replace("-", "")
def derive_password(db_name_no_ext: str) -> str:
"""Derive the SQLCipher password for a given DB table name."""
str2 = db_name_no_ext + ".db"
if db_name_no_ext.startswith("ms_"):
return "password"
else:
h1 = get_hash_code(str2)
combined = "t0k1d0l4ever" + h1
return get_hash_code(combined)
# --- SQLCipher decryption ---
def pbkdf2_hmac_sha1(password, salt, iterations, dklen):
return hashlib.pbkdf2_hmac('sha1', password, salt, iterations, dklen)
def decrypt_sqlcipher_db(db_bytes, password, page_size=1024, kdf_iter=64000, reserve=48):
"""Decrypt a SQLCipher 3.x database to plaintext SQLite format."""
salt = db_bytes[:16]
key = pbkdf2_hmac_sha1(password.encode(), salt, kdf_iter, 32)
num_pages = len(db_bytes) // page_size
output = bytearray()
for i in range(num_pages):
page_start = i * page_size
page = db_bytes[page_start:page_start + page_size]
is_page1 = (i == 0)
iv_offset = page_size - reserve
iv = page[iv_offset:iv_offset + 16]
if is_page1:
ciphertext = page[16:iv_offset]
else:
ciphertext = page[0:iv_offset]
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(ciphertext)
if is_page1:
output.extend(b'SQLite format 3\x00')
output.extend(decrypted)
current_len = 16 + len(decrypted)
if current_len < page_size:
output.extend(b'\x00' * (page_size - current_len))
else:
output.extend(decrypted)
if len(decrypted) < page_size:
output.extend(b'\x00' * (page_size - len(decrypted)))
# Set reserved-space field (header offset 20) to 0 for plaintext SQLite
output[20] = 0
return bytes(output)
def verify_decryption(decrypted_bytes):
"""Check if decrypted bytes start with a valid SQLite header."""
magic = decrypted_bytes[:16]
if magic != b'SQLite format 3\x00':
return False, "Invalid SQLite header magic"
page_size = struct.unpack('>H', decrypted_bytes[16:18])[0]
if page_size == 1:
page_size = 65536
if page_size not in (512, 1024, 2048, 4096, 8192, 16384, 32768, 65536):
return False, f"Invalid page size: {page_size}"
return True, f"Valid SQLite header, page size: {page_size}"
def decrypt_file(db_path, output_path, password=None):
"""Decrypt a single .db.bytes file to a plaintext .db file."""
with open(db_path, 'rb') as f:
data = f.read()
name = os.path.basename(db_path)
if name.endswith('.db.bytes'):
base_name = name[:-len('.db.bytes')]
elif name.endswith('.db'):
base_name = name[:-len('.db')]
else:
base_name = name
if password is None:
password = derive_password(base_name)
decrypted = decrypt_sqlcipher_db(data, password)
valid, msg = verify_decryption(decrypted)
with open(output_path, 'wb') as f:
f.write(decrypted)
return password, valid, msg
def list_db_files(db_dir):
"""List all .db.bytes files and their derived passwords."""
files = sorted(f for f in os.listdir(db_dir) if f.endswith('.db.bytes'))
print(f"{'File':<45} {'Password':<45}")
print("-" * 90)
for fname in files:
name = fname[:-len(".db.bytes")]
pw = derive_password(name)
print(f"{fname:<45} {pw:<45}")
def main():
if len(sys.argv) >= 2 and sys.argv[1] == "list":
db_dir = sys.argv[2] if len(sys.argv) > 2 else "DB"
list_db_files(db_dir)
return
if len(sys.argv) >= 3:
db_file = sys.argv[1]
out_file = sys.argv[2]
password = sys.argv[3] if len(sys.argv) > 3 else None
pw, valid, msg = decrypt_file(db_file, out_file, password)
print(f"Input: {db_file}")
print(f"Output: {out_file}")
print(f"Password: {pw}")
print(f"Status: {msg}")
return
# Default: decrypt all files in DB/
script_dir = os.path.dirname(os.path.abspath(__file__))
db_dir = os.path.join(script_dir, "DB")
output_dir = os.path.join(script_dir, "DB_decrypted")
os.makedirs(output_dir, exist_ok=True)
db_files = sorted(f for f in os.listdir(db_dir) if f.endswith('.db.bytes'))
print(f"Decrypting {len(db_files)} database files...")
print(f"Output: {output_dir}\n")
print(f"{'File':<45} {'Password':<12} {'Status'}")
print("-" * 75)
success = 0
for fname in db_files:
in_path = os.path.join(db_dir, fname)
out_name = fname[:-len('.db.bytes')] + '.db'
out_path = os.path.join(output_dir, out_name)
try:
pw, valid, msg = decrypt_file(in_path, out_path)
status = "OK" if valid else f"FAILED: {msg}"
if valid:
success += 1
print(f"{fname:<45} {pw:<12} {status}")
except Exception as e:
print(f"{fname:<45} {'ERROR':<12} {e}")
print(f"\nDone: {success}/{len(db_files)} succeeded")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment