Skip to content

Commit

Permalink
Updates for dogechain.info download and new format (#536)
Browse files Browse the repository at this point in the history
* Updates for modified wallet format
* Add upport for new AES-GCM wallets
* docco bump
  • Loading branch information
3rdIteration authored Dec 24, 2024
1 parent 8f70ce0 commit 223bdd3
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 23 deletions.
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

0 comments on commit 223bdd3

Please sign in to comment.