util.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. altcoin.util: various altcoin-related utilities
  12. """
  13. from ..util import die
  14. def decrypt_keystore(data, passwd, *, mac_algo=None, mac_params={}):
  15. """
  16. Decrypt the encrypted data in a cross-chain keystore
  17. Returns the decrypted data as a bytestring
  18. """
  19. cdata = data['crypto']
  20. parms = cdata['kdfparams']
  21. valid_kdfs = ['scrypt', 'pbkdf2']
  22. valid_ciphers = ['aes-128-ctr', 'aes-256-ctr']
  23. if (kdf := cdata['kdf']) not in valid_kdfs:
  24. die(1, f'unsupported key derivation function {kdf!r} (must be one of {valid_kdfs})')
  25. if (cipher := cdata['cipher']) not in valid_ciphers:
  26. die(1, f'unsupported cipher {cipher!r} (must be one of {valid_ciphers})')
  27. # Derive encryption key from password:
  28. if kdf == 'scrypt':
  29. if not mac_algo:
  30. die(1, 'the ‘mac_algo’ parameter is required for scrypt kdf')
  31. from hashlib import scrypt
  32. hashed_pw = scrypt(
  33. password = passwd,
  34. salt = bytes.fromhex(parms['salt']),
  35. n = parms['n'],
  36. r = parms['r'],
  37. p = parms['p'],
  38. maxmem = 0,
  39. dklen = parms['dklen'])
  40. elif kdf == 'pbkdf2':
  41. if (prf := parms.get('prf')) != 'hmac-sha256':
  42. die(1, f"unsupported hash function {prf!r} (must be 'hmac-sha256')")
  43. from cryptography.hazmat.primitives import hashes
  44. from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
  45. hashed_pw = PBKDF2HMAC(
  46. algorithm = hashes.SHA256(),
  47. length = parms['dklen'],
  48. salt = bytes.fromhex(parms['salt']),
  49. iterations = parms['c']
  50. ).derive(passwd)
  51. # see:
  52. # https://github.com/xchainjs/xchainjs-lib.git
  53. # https://github.com/xchainjs/foundry-primitives-js.git
  54. from hashlib import blake2b
  55. mac_algo = mac_algo or blake2b
  56. mac_params = mac_params or {'digest_size': 32}
  57. mac = mac_algo(
  58. hashed_pw[16:32] + bytes.fromhex(cdata['ciphertext']),
  59. **mac_params
  60. ).digest().hex()
  61. if mac != cdata['mac']:
  62. die(1, 'incorrect password')
  63. # Decrypt data:
  64. from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  65. from cryptography.hazmat.backends import default_backend
  66. cipher_len = int(cipher.split('-')[1]) // 8
  67. c = Cipher(
  68. algorithms.AES(hashed_pw[:cipher_len]),
  69. modes.CTR(bytes.fromhex(cdata['cipherparams']['iv'])),
  70. backend = default_backend())
  71. encryptor = c.encryptor()
  72. return encryptor.update(bytes.fromhex(cdata['ciphertext'])) + encryptor.finalize()