tMerge pull request #4937 from SomberNight/revert_password_v2 - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit fa389a4e06efa2bd369ad1d9c879d54efe2816af DIR parent c59ac49feadeb6905aef17990b0f93414fd93574 HTML Author: ThomasV <thomasv@electrum.org> Date: Tue, 18 Dec 2018 19:35:57 +0100 Merge pull request #4937 from SomberNight/revert_password_v2 keystore: revert KDF change Diffstat: M electrum/crypto.py | 53 ++++++++++++++----------------- M electrum/tests/test_bitcoin.py | 10 +++++----- 2 files changed, 29 insertions(+), 34 deletions(-) --- DIR diff --git a/electrum/crypto.py b/electrum/crypto.py t@@ -116,9 +116,11 @@ def DecodeAES_bytes(secret: bytes, ciphertext: bytes) -> bytes: return s -PW_HASH_VERSION_LATEST = 2 -KNOWN_PW_HASH_VERSIONS = (1, 2) +PW_HASH_VERSION_LATEST = 1 +KNOWN_PW_HASH_VERSIONS = (1, 2, ) +SUPPORTED_PW_HASH_VERSIONS = (1, ) assert PW_HASH_VERSION_LATEST in KNOWN_PW_HASH_VERSIONS +assert PW_HASH_VERSION_LATEST in SUPPORTED_PW_HASH_VERSIONS class UnexpectedPasswordHashVersion(InvalidPassword): t@@ -126,23 +128,30 @@ class UnexpectedPasswordHashVersion(InvalidPassword): self.version = version def __str__(self): - return "{unexpected}: {version}\n{please_update}".format( + return "{unexpected}: {version}\n{instruction}".format( unexpected=_("Unexpected password hash version"), version=self.version, - please_update=_('You are most likely using an outdated version of Electrum. Please update.')) + instruction=_('You are most likely using an outdated version of Electrum. Please update.')) -def _hash_password(password: Union[bytes, str], *, version: int, salt: bytes) -> bytes: +class UnsupportedPasswordHashVersion(InvalidPassword): + def __init__(self, version): + self.version = version + + def __str__(self): + return "{unsupported}: {version}\n{instruction}".format( + unsupported=_("Unsupported password hash version"), + version=self.version, + instruction=f"To open this wallet, try 'git checkout password_v{self.version}'.\n" + "Alternatively, restore from seed.") + + +def _hash_password(password: Union[bytes, str], *, version: int) -> bytes: pw = to_bytes(password, 'utf8') + if version not in SUPPORTED_PW_HASH_VERSIONS: + raise UnsupportedPasswordHashVersion(version) if version == 1: return sha256d(pw) - elif version == 2: - if not isinstance(salt, bytes) or len(salt) < 16: - raise Exception('too weak salt', salt) - return hashlib.pbkdf2_hmac(hash_name='sha256', - password=pw, - salt=b'ELECTRUM_PW_HASH_V2'+salt, - iterations=50_000) else: assert version not in KNOWN_PW_HASH_VERSIONS raise UnexpectedPasswordHashVersion(version) t@@ -154,17 +163,9 @@ def pw_encode(data: str, password: Union[bytes, str, None], *, version: int) -> if version not in KNOWN_PW_HASH_VERSIONS: raise UnexpectedPasswordHashVersion(version) # derive key from password - if version == 1: - salt = b'' - elif version == 2: - salt = bytes(os.urandom(16)) - else: - assert False, version - secret = _hash_password(password, version=version, salt=salt) + secret = _hash_password(password, version=version) # encrypt given data - e = EncodeAES_bytes(secret, to_bytes(data, "utf8")) - # return base64(salt + encrypted data) - ciphertext = salt + e + ciphertext = EncodeAES_bytes(secret, to_bytes(data, "utf8")) ciphertext_b64 = base64.b64encode(ciphertext) return ciphertext_b64.decode('utf8') t@@ -176,13 +177,7 @@ def pw_decode(data: str, password: Union[bytes, str, None], *, version: int) -> raise UnexpectedPasswordHashVersion(version) data_bytes = bytes(base64.b64decode(data)) # derive key from password - if version == 1: - salt = b'' - elif version == 2: - salt, data_bytes = data_bytes[:16], data_bytes[16:] - else: - assert False, version - secret = _hash_password(password, version=version, salt=salt) + secret = _hash_password(password, version=version) # decrypt given data try: d = to_string(DecodeAES_bytes(secret, data_bytes), "utf8") DIR diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py t@@ -11,7 +11,7 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key, from electrum.bip32 import (bip32_root, bip32_public_derivation, bip32_private_derivation, xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation, is_xpub, convert_bip32_path_to_list_of_uint32) -from electrum.crypto import sha256d, KNOWN_PW_HASH_VERSIONS +from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS from electrum import ecc, crypto, constants from electrum.ecc import number_to_string, string_to_number from electrum.transaction import opcodes t@@ -219,7 +219,7 @@ class Test_bitcoin(SequentialTestCase): """Make sure AES is homomorphic.""" payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0' password = u'secret' - for version in KNOWN_PW_HASH_VERSIONS: + for version in SUPPORTED_PW_HASH_VERSIONS: enc = crypto.pw_encode(payload, password, version=version) dec = crypto.pw_decode(enc, password, version=version) self.assertEqual(dec, payload) t@@ -228,7 +228,7 @@ class Test_bitcoin(SequentialTestCase): def test_aes_encode_without_password(self): """When not passed a password, pw_encode is noop on the payload.""" payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0' - for version in KNOWN_PW_HASH_VERSIONS: + for version in SUPPORTED_PW_HASH_VERSIONS: enc = crypto.pw_encode(payload, None, version=version) self.assertEqual(payload, enc) t@@ -236,7 +236,7 @@ class Test_bitcoin(SequentialTestCase): def test_aes_deencode_without_password(self): """When not passed a password, pw_decode is noop on the payload.""" payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0' - for version in KNOWN_PW_HASH_VERSIONS: + for version in SUPPORTED_PW_HASH_VERSIONS: enc = crypto.pw_decode(payload, None, version=version) self.assertEqual(payload, enc) t@@ -246,7 +246,7 @@ class Test_bitcoin(SequentialTestCase): payload = u"blah" password = u"uber secret" wrong_password = u"not the password" - for version in KNOWN_PW_HASH_VERSIONS: + for version in SUPPORTED_PW_HASH_VERSIONS: enc = crypto.pw_encode(payload, password, version=version) with self.assertRaises(InvalidPassword): crypto.pw_decode(enc, wrong_password, version=version)