diff --git a/code/decode.py b/code/decode.py index 05b113d..f30cffb 100644 --- a/code/decode.py +++ b/code/decode.py @@ -5,6 +5,8 @@ import logging from dataclasses import dataclass from collections import defaultdict +from utils import decrypt_encrypted_key + import pandas as pd from pandas import DataFrame from sklearn.feature_extraction import DictVectorizer @@ -28,20 +30,22 @@ class Message: body: str -def get_decryption_key(path): +def get_decryption_key(path, password=None): with open(path) as fh: config = json.load(fh) - key = None if 'key' in config and 'encryptedKey' in config: - raise Exception('config has both key and encryptedKey in it') + raise Exception('config has both `key` and `encryptedKey` in it') if 'key' in config: - key = 'key' + if password is not None: + raise Exception('you cannot use the -p option with your signal config') + return config['key'] elif 'encryptedKey' in config: - key = 'encryptedKey' + if password is None: + raise Exception('you must use the -p option to pass the password in for your signal db') + return decrypt_encrypted_key(password, config['encryptedKey']) else: dict_keys = ', '.join(str(x) for x in config.keys()) - raise Exception(f'no key found for db in config.json, only saw: {dict_keys}') - return config[key] + raise Exception(f'no `key` or `encryptedKey` found for db in config.json, only saw: {dict_keys}') def query_signal_db(db, key, query): diff --git a/code/main.py b/code/main.py index fac544b..43f0be8 100644 --- a/code/main.py +++ b/code/main.py @@ -8,6 +8,7 @@ table = subparsers.add_parser('table') table.set_defaults(func=dump_message_count_table) table.add_argument('--filter_file', '-f') table.add_argument('--output', '-o', default='table.csv') +table.add_argument('--password', '-p') user = subparsers.add_parser('user') user.add_argument('regex') @@ -21,10 +22,12 @@ parser.add_argument('--db', default=expanduser('~/.config/Signal/sql/db.sqlite')) parser.add_argument('--config', default=expanduser('~/.config/Signal/config.json')) + if __name__ == "__main__": args = parser.parse_args() - key = get_decryption_key(args.config) args = vars(args) + password = args.pop('password') + key = get_decryption_key(args['config'], password) args.pop('config') func = args.pop('func') func(**args, key=key) diff --git a/code/requirements.txt b/code/requirements.txt index fda4dd6..23c7794 100644 --- a/code/requirements.txt +++ b/code/requirements.txt @@ -1,2 +1,3 @@ pandas +pycryptodome scikit-learn diff --git a/code/utils.py b/code/utils.py new file mode 100644 index 0000000..34801db --- /dev/null +++ b/code/utils.py @@ -0,0 +1,28 @@ +# from https://gist.github.com/flatz/3f242ab3c550d361f8c6d031b07fb6b1 + +import os +import json + +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Hash import SHA1 +from Crypto.Cipher import AES +from Crypto.Util.Padding import unpad + +def aes_decrypt_cbc(key, iv, data): + cipher = AES.new(key, AES.MODE_CBC, iv) + return cipher.decrypt(data) + +prefix = b'v11' +salt = 'saltysalt' +derived_key_len = 128 // 8 +num_iterations = 1003 +iv = b' ' * 16 + +def decrypt_encrypted_key(password, encrypted_key): + encrypted_key = bytes.fromhex(encrypted_key) + assert encrypted_key.startswith(prefix) + encrypted_key = encrypted_key[len(prefix):] + + kek = PBKDF2(password, salt, dkLen = derived_key_len, count = num_iterations, hmac_hash_module = SHA1) + decrypted_key = unpad(aes_decrypt_cbc(kek, iv, encrypted_key), block_size = 16).decode('ascii') + print('0x' + decrypted_key) diff --git a/run.sh b/run.sh index 94c0389..498aac4 100755 --- a/run.sh +++ b/run.sh @@ -1,9 +1,10 @@ #/usr/bin/sh FILTER=0 -while getopts "c:f" flag; do +while getopts "c:p:f" flag; do case "$flag" in c) SIGNAL_DIR=$OPTARG;; + p) PASSWORD=$OPTARG;; f) FILTER=1; esac done @@ -16,8 +17,17 @@ fi OUTDIR="$(pwd)/output" -if [ $FILTER -eq 1 ]; then - docker run -it -v "$SIGNAL_DIR":/root/.config/Signal -v "$OUTDIR":/output -v "$(pwd)/config":/config sigint -f /config/chat_list.txt +# TODO: learn how to use getopts and rewrite this. this is dumb. +if [ -z "${PASSWORD+x}" ]; then + if [ $FILTER -eq 1 ]; then + docker run -it -v "$SIGNAL_DIR":/root/.config/Signal -v "$OUTDIR":/output -v "$(pwd)/config":/config sigint -f /config/chat_list.txt + else + docker run -it -v "$SIGNAL_DIR":/root/.config/Signal -v "$OUTDIR":/output sigint + fi else - docker run -it -v "$SIGNAL_DIR":/root/.config/Signal -v "$OUTDIR":/output sigint + if [ $FILTER -eq 1 ]; then + docker run -it -v "$SIGNAL_DIR":/root/.config/Signal -v "$OUTDIR":/output -v "$(pwd)/config":/config sigint -f /config/chat_list.txt --password "$PASSWORD" + else + docker run -it -v "$SIGNAL_DIR":/root/.config/Signal -v "$OUTDIR":/output sigint --password "$PASSWORD" + fi fi