#!/opt/local/bin/python3.13

# -*- coding: utf-8 -*-

# Author: Gabriele 'matrix' Gristina
# Version: 2.1
# Date: Thu 28 Aug 2023 05:12:40 PM CEST
# License: MIT

# Extract metamask vault from browser and save to file, then you can use this tool
# reference: https://metamask.zendesk.com/hc/en-us/articles/360018766351-How-to-use-the-Vault-Decryptor-with-the-MetaMask-Vault-Data
# From version 2.0 works also for Metamask Mobile
# reference: https://github.com/3rdIteration/btcrecover/pull/346

import json
import argparse
import base64

def metamask_parser(file, shortdata):
  try:
    f = open(file)

    j = json.load(f)

    isMobile = False

    if 'engine' not in j:
      if 'salt' not in j or 'iv' not in j or 'data' not in j:
        print("! Invalid vault format ...")
        parser.print_help()
        exit(1)
    else:
      f.close()
      wallet_data = open(file, "rb").read().decode("utf-8","ignore").replace("\\","")

      # taken from https://github.com/3rdIteration/btcrecover/blob/master/btcrecover/btcrpass.py#L3096-L3103
      walletStartText = "vault"
      wallet_data_start = wallet_data.lower().find(walletStartText)
      wallet_data_trimmed = wallet_data[wallet_data_start:]
      wallet_data_start = wallet_data_trimmed.find("cipher")
      wallet_data_trimmed = wallet_data_trimmed[wallet_data_start - 2:]
      wallet_data_end = wallet_data_trimmed.find("}")
      wallet_data = wallet_data_trimmed[:wallet_data_end + 1]
      wallet_json = json.loads(wallet_data)

      j = json.loads(wallet_data)

      if 'lib' in j and 'original' in j['lib']:
        isMobile = True
      else:
        print("! Invalid vault format ...")
        parser.print_help()
        exit(1)


    if isMobile is False:

      try:
        iter_count = j['keyMetadata']['params']['iterations']
      except KeyError:
        iter_count = 10_000

      if((len(j['data']) > 3000) or shortdata):
        data_bin = base64.b64decode(j['data'])
        # TODO limit data to 16 bytes, we only check the first block of data, so we don't need more data.
        #  The use of smaller buffers should speedup the attack.
        #  Still the pbkdf 10k iter will be taking the most time by far probably.
        j['data'] = base64.b64encode(data_bin[0:64]).decode("ascii")

        if iter_count != 10000:
          print('$metamask-short$rounds=' + str(iter_count) + '$' + j['salt'] + '$' + j['iv'] + '$' + j['data'])
        else:
          print('$metamask-short$' + j['salt'] + '$' + j['iv'] + '$' + j['data'])
      else:
        if iter_count != 10000:
          print('$metamask$rounds=' + str(iter_count) + '$' + j['salt'] + '$' + j['iv'] + '$' + j['data'])
        else:
          print('$metamask$' + j['salt'] + '$' + j['iv'] + '$' + j['data'])

    else:

      # extract first 32 bytes of ciphertext for enhanced resistance to false-positives

      cipher_bin = base64.b64decode(j['cipher'])
      j['cipher'] = base64.b64encode(cipher_bin[:32]).decode("ascii")

      print('$metamaskMobile$' + j['salt'] + '$' + j['iv'] + '$' + j['cipher'])

  except ValueError as e:
    parser.print_help()
    exit(1)

  exit(0)

if __name__ == "__main__":
  parser = argparse.ArgumentParser(description="metamask2hashcat.py extraction tool")
  parser.add_argument('--vault', required=True, help='set metamask vault (json) file from path', type=str)
  parser.add_argument('--shortdata', help='force short data, can only be used with m26610, ', action='store_true')

  args = parser.parse_args()

  if args.vault:
      metamask_parser(args.vault, args.shortdata)
  else:
      parser.print_help()
      exit(1)
