diff --git a/btcrecover/btcrpass.py b/btcrecover/btcrpass.py index a5aff322..c85f447e 100644 --- a/btcrecover/btcrpass.py +++ b/btcrecover/btcrpass.py @@ -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__ @@ -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 @@ -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 @@ -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 diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-cbc b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-cbc new file mode 100644 index 00000000..4ddb06b1 --- /dev/null +++ b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-cbc @@ -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 +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2022-01 b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-encrypted.2022-01 similarity index 100% rename from btcrecover/test/test-wallets/dogechain.wallet.aes.json.2022-01 rename to btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-encrypted.2022-01 diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-gcm b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-gcm new file mode 100644 index 00000000..8794232c --- /dev/null +++ b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-gcm @@ -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} \ No newline at end of file diff --git a/btcrecover/test/test_passwords.py b/btcrecover/test/test_passwords.py index 4a14cc85..e2753141 100644 --- a/btcrecover/test/test_passwords.py +++ b/btcrecover/test/test_passwords.py @@ -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): diff --git a/docs/Images/download_dogechain_wallet.png b/docs/Images/download_dogechain_wallet.png index 91fcc386..f5024356 100644 Binary files a/docs/Images/download_dogechain_wallet.png and b/docs/Images/download_dogechain_wallet.png differ diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index 7eaf92e7..3f984ed6 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -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 ###