Skip to content

Instantly share code, notes, and snippets.

@elibroftw
Last active March 13, 2024 10:08
Show Gist options
  • Save elibroftw/dc169add51b3093f8ebf35e276ae131d to your computer and use it in GitHub Desktop.
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 ).
"""
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')
@elibroftw
Copy link
Author

elibroftw commented Aug 15, 2021

Case 1: rename the following "image" to .zip and try decrypting these files. (I didn't create these files or password)
Case 1
PW: ThisisShit90!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment