Browse Source

keystore: revert KDF change from #4838

making the KDF expensive is blocked on #4909
3.3.3.1
SomberNight 6 years ago
parent
commit
0c9a03ac54
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 53
      electrum/crypto.py
  2. 10
      electrum/tests/test_bitcoin.py

53
electrum/crypto.py

@ -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):
@ -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)
@ -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')
@ -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")

10
electrum/tests/test_bitcoin.py

@ -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
@ -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)
@ -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)
@ -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)
@ -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)

Loading…
Cancel
Save