Last active
March 13, 2024 10:08
-
-
Save elibroftw/dc169add51b3093f8ebf35e276ae131d to your computer and use it in GitHub Desktop.
A script to decrypt MyMonero wallet files. Simply use decrypt_mymonero( WALLET_FOLDER_PATH ).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Supports: MyMonero | |
Instructions (terminal, assuming you have python installed): | |
pip install cryptography | |
python[3] monero_wallet_decryption.py [path_to_wallet_dir] | |
License: Public Domain | |
You are permitted to run/edit/use any or all of this code without attribution. | |
Free implies that there are no warranties. | |
""" | |
# TODO: Official GUI Wallet Decryption | |
import argparse | |
import base64 | |
from contextlib import suppress | |
import getpass | |
import glob | |
import json | |
import platform | |
import pprint | |
from os import path | |
from cryptography.hazmat.primitives import hashes, hmac | |
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | |
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes | |
default_data_loc = None | |
if platform.system() == 'Windows': | |
default_data_loc = path.expandvars(r'%APPDATA%\MyMonero') | |
elif platform.system() == 'Darwin': | |
default_data_loc = path.expanduser('~/Library/Application Support/MyMonero') | |
elif platform.system() == 'Linux': | |
# TODO: | |
pass | |
CRYPTOR_SETTINGS = { | |
# 'algorithm': 'aes-256-cbc', | |
# options: 1, // this gets inserted into the format. should probably be renamed to something more concretely descriptive | |
'salt_length': 8, | |
'iv_length': 16, | |
'pbkdf2': { | |
'iterations': 10000, | |
'key_length': 32 | |
}, | |
'hmac': { | |
'includes_header': True, | |
'algorithm': hashes.SHA256(), | |
'length': 32 | |
} | |
} | |
def parse_headers(encrypted_data): | |
version_char = encrypted_data[0:1] | |
# ignored: validate_schema_version(version_char.toString('binary').charCodeAt()); | |
options_char = encrypted_data[1:2] | |
salt_length = CRYPTOR_SETTINGS['salt_length'] | |
offset = 2 + salt_length | |
encryption_salt = encrypted_data[2:offset] | |
hmac_salt = encrypted_data[offset:offset + salt_length] | |
offset += salt_length | |
iv_length = CRYPTOR_SETTINGS['iv_length'] | |
iv = encrypted_data[offset:offset + iv_length] | |
offset += iv_length | |
return { | |
'version': version_char, | |
'options': options_char, | |
'encryption_salt': encryption_salt, | |
'hmac_salt': hmac_salt, | |
'iv': iv, | |
'length': offset | |
} | |
def create_pdkdf2_key(provided_password, salt): | |
password = provided_password.encode() # Convert to type bytes | |
kdf = PBKDF2HMAC( | |
algorithm=hashes.SHA1(), | |
length=CRYPTOR_SETTINGS['pbkdf2']['key_length'], | |
salt=salt, | |
iterations=CRYPTOR_SETTINGS['pbkdf2']['iterations']) | |
return kdf.derive(password) | |
def decrypt_mymonero_wallets(data_loc=default_data_loc, output=True, outfile=None, ignore_hmac_errors=False): | |
""" | |
Translated from | |
https://github.com/mymonero/mymonero-app-js/blob/master/local_modules/symmetric_cryptor/symmetric_string_cryptor.js | |
""" | |
if data_loc is None: | |
raise NotImplementedError('MyMonero wallet directory not provided. No default set on Linux.') | |
wallets = [] | |
files_found = 0 | |
for wallet in glob.iglob(glob.escape(data_loc) + '/Wallets__*'): | |
files_found += 1 | |
with open(wallet, 'r', encoding='utf-8') as f: | |
b64_encrypted = f.read() | |
encrypted_data = base64.b64decode(b64_encrypted) | |
hmac_length = CRYPTOR_SETTINGS['hmac']['length'] | |
headers = parse_headers(encrypted_data) | |
cipher_text = encrypted_data[headers['length']:-hmac_length] | |
# DO NOT SET ANY VARIABLE as hmac since it's imported | |
wallet_hmac = encrypted_data[-hmac_length:] | |
password = getpass.getpass(f'Enter wallet password ({wallet}): ') | |
hmac_key = create_pdkdf2_key(password, headers['hmac_salt']) | |
hmac_msg = b'' | |
if CRYPTOR_SETTINGS['hmac']['includes_header']: | |
hmac_msg = headers['version'] + headers['options'] + headers['encryption_salt'] + headers['hmac_salt'] + headers['iv'] | |
hmac_msg += cipher_text | |
generated_hmac = hmac.HMAC(hmac_key, CRYPTOR_SETTINGS['hmac']['algorithm']) | |
generated_hmac.update(hmac_msg) | |
generated_hmac = generated_hmac.finalize() | |
if generated_hmac != wallet_hmac and not ignore_hmac_errors: | |
raise Exception(f'HMAC mismatch (your password may be incorrect for wallet {wallet})') | |
cipher_key = create_pdkdf2_key(password, headers['encryption_salt']) | |
decryptor = Cipher(algorithms.AES(cipher_key), modes.CBC(headers['iv'])).decryptor() | |
d = decryptor.update(cipher_text) | |
try: | |
wallets.append(json.loads(d.decode())) | |
except (json.decoder.JSONDecodeError, UnicodeDecodeError): | |
try: | |
wallets.append(json.loads(d.decode()[:-1])) | |
except (json.decoder.JSONDecodeError, UnicodeDecodeError): | |
try: | |
wallets.append(json.loads(d.decode()[:-14])) | |
except (json.decoder.JSONDecodeError, UnicodeDecodeError): | |
print(f'Failed to json load wallet file: {wallet}') | |
print('---RAW DATA FOR DEBUGGING---') | |
i = 1 | |
while i < hmac_length: | |
try: | |
x = d.decode() | |
print(x) | |
if i > 0: | |
decoded_e = None | |
for j in range(20): | |
try: | |
wallets.append(json.loads(x[:-j])) | |
decoded = None | |
break | |
except json.decoder.JSONDecodeError as e: | |
decoded_e = e | |
if decoded_e is not None: | |
print(f'FAILED TO JSON DECODE: {decoded_e}') | |
print(f'offset that works: {i}') | |
break | |
except UnicodeDecodeError: | |
cipher_text = encrypted_data[headers['length']:-hmac_length + i] | |
d = decryptor.update(cipher_text) | |
i += 1 | |
if i >= hmac_length: | |
cipher_text = encrypted_data[headers['length']:-hmac_length] | |
if output: pprint.pprint(wallets) | |
if outfile: | |
with open(outfile, 'w') as f: | |
json.dump(wallets, f, indent=4) | |
if not files_found: | |
print('Warning: found 0 wallet files') | |
return wallets | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description='MyMonero Wallet Decryption Tool') | |
parser.add_argument('wallet_dir', default=default_data_loc, nargs='?', help='path to where wallet files are location') | |
args = parser.parse_args() | |
wallets = decrypt_mymonero_wallets(args.wallet_dir, outfile='mymonero_decrypted.json', ignore_hmac_errors=False) | |
if wallets: | |
print('Decoded wallets to mymonero_decrypted.json') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Case 1: rename the following "image" to .zip and try decrypting these files. (I didn't create these files or password)

PW: ThisisShit90!