56 lines
2.4 KiB
Python
56 lines
2.4 KiB
Python
import base64, win32crypt
|
|
from Crypto.Cipher import AES
|
|
|
|
def decrypt_and_print_cipher_key(os_key_b64, config_key_hex):
|
|
print("GOT OS KEY:", os_key_b64)
|
|
print("GOT CONFIG KEY:", config_key_hex)
|
|
|
|
encrypted_os_key = base64.b64decode(os_key_b64)[5:] # Remove DPAPI prefix at the end.
|
|
master_key = win32crypt.CryptUnprotectData(encrypted_os_key, None, None, None, 0)[1] # This is the AES-GCM key.
|
|
assert len(master_key) == 32
|
|
|
|
print(f"OS Decoded length: {len(encrypted_os_key)} bytes")
|
|
print(f"OS Decoded data (hex): {encrypted_os_key.hex()}")
|
|
print(f"OS master key (hex): {master_key.hex()}")
|
|
|
|
# Determined the Signal config encryption by reading the source code and seeing that it's using
|
|
# Electron's crypt API. Tracing that, they're using Chromium's crypt API. Reading that, we can
|
|
# see exactly what's being done.
|
|
#
|
|
# Encrypted config key is AES-GCM encrypted. First 3 bytes is the version,
|
|
# next 12 bytes is the nonce (IV), last 16 bytes is the AES authentication tag,
|
|
# middle bytes is the ciphertext.
|
|
|
|
encrypted_key = bytes.fromhex(config_key_hex)
|
|
assert encrypted_key[:3] == b"v10"
|
|
|
|
print(f"Config Decoded length: {len(encrypted_key)} bytes")
|
|
print(f"Config Decoded data (hex): {encrypted_key.hex()}")
|
|
|
|
nonce = encrypted_key[3:15] # Skip the version.
|
|
ciphertext = encrypted_key[15:-16]
|
|
tag = encrypted_key[-16:]
|
|
|
|
print(f"Config nonce: {nonce.hex()}")
|
|
print(f"Config cipher: {ciphertext.hex()}")
|
|
print(f"Config auth tag: {tag.hex()}")
|
|
assert len(nonce) == 12
|
|
assert len(tag) == 16
|
|
|
|
cipher = AES.new(master_key, AES.MODE_GCM, nonce)
|
|
decrypted_cipher_key = cipher.decrypt_and_verify(ciphertext, tag)
|
|
cipher_key_len = len(decrypted_cipher_key)
|
|
|
|
print(f"Decrypted SQLCipher key: {decrypted_cipher_key.hex()}")
|
|
print(f"Key length: {cipher_key_len} bytes")
|
|
|
|
raw_binary_key = decrypted_cipher_key
|
|
|
|
if cipher_key_len == 64: # Likely hex-encoded
|
|
print("Likely hex-encoded. Final key should be 32 bytes.")
|
|
raw_binary_key = bytes.fromhex(decrypted_cipher_key.decode("utf-8").strip())
|
|
|
|
assert len(raw_binary_key) == 32
|
|
print(f"\n------------------------------------------------------------\nBinary key to use in SQLCipher (DB Browser for SQLite), using SQLCipher 4 defaults (4096 page size, raw key password):\n0x{raw_binary_key.hex()}")
|
|
|