Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates for modified wallet format #536

Merged
merged 4 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions btcrecover/btcrpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2893,7 +2893,12 @@ def is_wallet_file(wallet_file):
try:
walletdata = wallet_file.read()
except: return False
return (b"email" in walletdata and b"two_fa_method" in walletdata) # Dogechain.info wallets have email and 2fa fields that are fairly unique
isWallet = False
if (b"email" in walletdata and b"two_fa_method" in walletdata): # Older Dogechain.info wallets have email and 2fa fields that are fairly unique
isWallet = True
elif (b"salt" in walletdata and b"cipher" in walletdata and b"payload" in walletdata): # Newer Dogechain.info wallets have cipher, salt and payload fields
isWallet = True
return isWallet

def __init__(self, iter_count, loading=False):
assert loading, 'use load_from_* to create a ' + self.__class__.__name__
Expand Down Expand Up @@ -2922,8 +2927,24 @@ def load_from_filename(cls, wallet_filename):
wallet_json = json.loads(wallet_data)
self = cls(wallet_json["pbkdf2_iterations"], loading=True)
self.salt = base64.b64decode(wallet_json["salt"])
self._encrypted_wallet = base64.b64decode(wallet_json["payload"])
self._encrypted_block = base64.b64decode(wallet_json["payload"])[:32]
try:
self.aes_cipher = wallet_json["cipher"]
except:
self.aes_cipher = "AES-CBC"

if self.aes_cipher == "AES-CBC":
self.iv = base64.b64decode(wallet_json["payload"])[:16]
self._encrypted_wallet = base64.b64decode(wallet_json["payload"])[16:]
else: # AES GCM
self.iv = base64.b64decode(wallet_json["payload"])[:12]
self.aes_auth_tag = base64.b64decode(wallet_json["payload"])[12:12+16]
self._encrypted_wallet = base64.b64decode(wallet_json["payload"])[12+16:]

self._encrypted_block = self._encrypted_wallet[:32]

if ";" in wallet_json["payload"]:
exit("\n**ERROR**\nFound RSA-encrypted Dogechain wallet payload, this means it wasn't downloaded in a way that supports password recovery or decryption... You cannot decrypt this wallet and will need to download it correctly or request your encrypted wallet from dogechain)")

return self

@classmethod
Expand Down Expand Up @@ -3028,12 +3049,21 @@ def _return_verified_password_or_false_cpu(self, arg_passwords): # dogechain.in
passwordbase64 = base64.b64encode(passwordSHA256)
key = hashlib.pbkdf2_hmac('sha256', passwordbase64, self.salt, self._iter_count, 32)

decrypted_block = AES.new(key, AES.MODE_CBC).decrypt(self._encrypted_block)[16:]
if self.aes_cipher == "AES-CBC":
decrypted_block = AES.new(key, AES.MODE_CBC, self.iv).decrypt(self._encrypted_block)

if self.check_decrypted_block(decrypted_block, password):
if self.check_decrypted_block(decrypted_block, password):
# Decrypt and dump the wallet if required
self.decrypt_wallet(password)
return password.decode("utf_8", "replace"), count
else:
try:
# For AES-GCM we need to decrypt the whole wallet, not just a block,
# also don't need to manually check the file contents as verification is part of the decryption
decrypted_block = AES.new(key, AES.MODE_GCM, self.iv).decrypt_and_verify(self._encrypted_wallet, self.aes_auth_tag)
return password.decode("utf_8", "replace"), count
except ValueError:
continue

return False, count

Expand All @@ -3050,11 +3080,19 @@ def _return_verified_password_or_false_opencl(self, arg_passwords): # dogechain
results = zip(passwords, clResult)

for count, (password, key) in enumerate(results, 1):
decrypted_block = AES.new(key, AES.MODE_CBC).decrypt(self._encrypted_block)[16:]
if self.check_decrypted_block(decrypted_block, password):
if self.aes_cipher == "AES-CBC":
decrypted_block = AES.new(key, AES.MODE_CBC, self.iv).decrypt(self._encrypted_block)
if self.check_decrypted_block(decrypted_block, password):
# Decrypt and dump the wallet if required
self.decrypt_wallet(password)
return password.decode("utf_8", "replace"), count
else:
try:
decrypted_block = AES.new(key, AES.MODE_GCM, self.iv).decrypt_and_verify(self._encrypted_wallet,
self.aes_auth_tag)
return password.decode("utf_8", "replace"), count
except ValueError:
continue

return False, count

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"guid": "52500558-b3fa-4318-b6a3-3c55835c6575",
"payload": "jJzIUd6i9DMEgCFG9JQ1/z4xSamItXAiQnV4AeJ0BwdC5Vbc3WuZNx3suAZ/J4tncwIlwsOYcTFc4bHKsG9fZBLiDGN80Yb7g4z88+mbqraUT/ZhnE1ClCmXgL8quBEn/LUaEWyXoV+qELd8IRRpbaHx4fgRIUjzqii+5ERsXiFWxg/DxLDLxYvOF1y0FBDep4HtuSGjb1ionh87+DkxFyxV9G8f4a/dwRz/ldSMJ3z2ZlKlLRzk4UceNzGW4k+TH4GCd3qXJoozYD6+WERjtVIW3WIQy8ZQHBE2jprSjARC5kdMLsslf1DEAOqJMVd1",
"salt": "Gc55/tevRG/ODxUN0ENiRg==",
"pbkdf2_iterations": 5000,
"cipher": "AES-CBC",
"version": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"guid": "52500558-b3fa-4318-b6a3-3c55835c6575","salt": "uTE5zPjTEpb6S23GbUJgwA==","payload": "LYp4+q9Qc2ixp2c49mrCfzfE+gnJgxNBF0YMmSmvELES9aQPwqp02O5Lo7p3npTHtbkjQeub8iGIddvvEdpoXqYi4ZFHSJ5/qM7lQKTzIXqLgJ8+3l3o/kqpgecXsrQKqmJEzzhzNCDnBhUeBpPiJuz39B5m1laehynsIZekAimJrkuEJtUHLfO54Mzzb3v2s6qAXUBuB9wjOBNCXgFPl1qDg+PMJdVhomyQWYoLr7425/+peoJ7IQ7BgH3sIUVua41zFIlkcHqjkYlBOuXOpb5tlZNRbgNRGiYISrYSRwBhuGO+U2R67ePTGtNq62VZhzs=","cipher": "AES-GCM","pbkdf2_iterations": 5000}
6 changes: 6 additions & 0 deletions btcrecover/test/test_passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,12 @@ def test_blockchain_secondpass_unencrypted(self): # this wallet has no second-p
def test_dogechain_info_cpu(self):
self.wallet_tester("dogechain.wallet.aes.json")

def test_dogechain_info_cpu_2024_CBC(self):
self.wallet_tester("dogechain.wallet.aes.json.2024-cbc")

def test_dogechain_info_cpu_2024_GCM(self):
self.wallet_tester("dogechain.wallet.aes.json.2024-gcm")

@skipUnless(can_load_ecdsa, "requires ECDSA")
@skipUnless(can_load_bitcoinutils, "requires Bitcoin-Utils")
def test_block_io_privkeyrequest_data_legacy_cpu(self):
Expand Down
Binary file modified docs/Images/download_dogechain_wallet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 0 additions & 16 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,22 +322,6 @@ Downloading these kinds of wallet files id done via your browser, through the "D

Basically you need to attempt to log in to your wallet (even with the wrong password) and save the wallet file that is downloaded as part of this process.

Once you are at the dogechain.info wallet login page, with the developer tools open in your browser, you will need to do the following steps:

1) Select the Network tab

2) Enter your Wallet ID

3) Enter a placeholder password (you can enter anything)

4) Click Log In (It will say that it failed to decrypt the wallet, but this is normal)

5) Select "Responses"

6) Select the API items. (This may look slightly different if you have 2fa enabled, you may need to complete the 2fa at this step too)

7) Once you have a response that looks like wallet data, copy it and paste it in to a text file. This is your wallet file...

![Download Dodgechain Wallet](download_dogechain_wallet.png)

### Downloading block.io wallet files ###
Expand Down
Loading