Skip to content

Instantly share code, notes, and snippets.

@xperylab
Created January 2, 2021 16:52
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xperylab/1436d02fe7bffa7ea96ec0e9116fa5f0 to your computer and use it in GitHub Desktop.
Save xperylab/1436d02fe7bffa7ea96ec0e9116fa5f0 to your computer and use it in GitHub Desktop.
Decrypt Signal databases
from Crypto.Cipher import AES
from struct import unpack
import os
import plistlib
from base64 import b64encode
signal_enc_db_name = 'signal.sqlite'
keychain_plist_path = 'keychain_decrypted.plist'
out_path = signal
#Extract key from keychain
#Accept plist file GK format or ios_keychain_decrypter plist file
with open(keychain_plist_path,'rb') as f :
plist = plistlib.load(f)
def getSignalKeyFromKeychain():
for d in plist:
for dd in plist[d]:
if type(dd) == dict:
if 'acct' in dd:
if 'GRDBDatabaseCipherKeySpec' in str(dd['acct']) or str(b64encode(b'GRDBDatabaseCipherKeySpec')) in str(dd['acct']):
return dd['v_Data']
return None
key = getSignalKeyFromKeychain()
os.makedirs(out_path, exist_ok = True)
#Decrypt Main DB
enc_db_size = os.path.getsize(signal_enc_db_name)
with open('signal.sqlite','rb') as tmp:
signal_header_size = 0x20
default_page_size=0x1000
page_size = default_page_size
header_size = signal_header_size
header = tmp.read(header_size)
salt_sz = 0x10
hmac_sz = 0x40
reserved_sz = salt_sz + hmac_sz
max_page = int(enc_db_size / default_page_size)
def decrypt_page(page_offset):
if page_offset == 0:
page_data = tmp.read(page_size - header_size)
else:
page_data = tmp.read(page_size)
iv = page_data[-reserved_sz:-reserved_sz+salt_sz]
decryption_suite = AES.new(key[:32], AES.MODE_CBC, iv)
plain_text = decryption_suite.decrypt(page_data[:-reserved_sz])
return plain_text
with open(os.path.join(out_path,'signal-decrypted.sqlite'),'wb') as decrypted:
decrypted.write(header)
for page in range(0,max_page):
decrypted.write(decrypt_page(page))
decrypted.write(b'\x00'*reserved_sz)
# Decrypt WAL File
enc_db_size = os.path.getsize(signal_enc_db_name+'-'+'wal')
with open(signal_enc_db_name+'-'+'wal','rb') as tmp:
wal_header_size = 32
wal_page_header_size = 24
signal_header_size = 0x20
wal_header = tmp.read(wal_header_size)
page_size = unpack('>I',wal_header[8:12])[0]
salt_sz = 0x10
hmac_sz = 0x40
reserved_sz = salt_sz + hmac_sz
max_page = int((enc_db_size - wal_header_size) / (page_size + wal_page_header_size))
def decrypt_page(page_offset):
page_header = tmp.read(wal_page_header_size)
page_number = unpack('>I',page_header[:4])[0]
plain_text = b''
if page_number == 0:
plain_text += tmp.read(signal_header_size)
page_data = tmp.read(page_size - signal_header_size)
else:
page_data = tmp.read(page_size)
iv = page_data[-reserved_sz:-reserved_sz+salt_sz]
decryption_suite = AES.new(key[:32], AES.MODE_CBC, iv)
plain_text += decryption_suite.decrypt(page_data[:-reserved_sz])
return plain_text
with open(os.path.join(out_path,'signal-decrypted-wal.sqlite'),'wb') as decrypted:
decrypted.write(header)
for page in range(0,max_page):
decrypted.write(decrypt_page(page))
decrypted.write(b'\x00'*reserved_sz)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment