Skip to content

Commit

Permalink
Damaged Private Key Repair (#326)
Browse files Browse the repository at this point in the history
*Added support to recover damaged raw private keys. (Bitcoin, Eth, etc) For Bitcoin (and likely clones) this works fine for both Hex and WIF private keys. (Works both with address and via addressDB)
*Added some additional wildcards to support the new feature above (Hex and Base58 wildcard)
*Added documentation, tests
  • Loading branch information
3rdIteration authored Jan 31, 2022
1 parent 4e87aee commit 0613341
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 29 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ If you need help, [your best bet is to look at my BTCRecover playlist on YouTube
* [Bitcoin Wallet for Android/BlackBerry](https://play.google.com/store/apps/details?id=de.schildbach.wallet) spending PINs and encrypted backups
* [KnC Wallet for Android](https://github.com/kncgroup/bitcoin-wallet) encrypted backups
* [Bither](https://bither.net/)
* [Encrypted (BIP-38) Paper Wallet Support (Eg: From Bitaddress.org)](https://bitaddress.org) Also works with altcoin forks like liteaddress.org, paper.dash.org, etc...
* Brainwallets
* Sha256(Passphrase) brainwallets (eg: Bitaddress.org, liteaddress.org, paper.dash.org)
* sCrypt Secured Brainwallets (Eg: Warpwallet, Memwallet)
* Altcoin password recovery support for most wallets derived from one of those above, including:
* [Coinomi](https://www.coinomi.com/en/) (Only supports password protected wallets)
* [Metamask](https://metamask.io/) (And Metamask clones like Binance Chain Wallet, Ronin Wallet, etc.)
Expand All @@ -95,11 +99,8 @@ If you need help, [your best bet is to look at my BTCRecover playlist on YouTube
* [Dogechain.info](https://dogechain.info/)
* [Dogecoin Wallet for Android](http://dogecoin.com/) encrypted backups
* [Yoroi Wallet for Cardano](https://yoroi-wallet.com/#/) Master_Passwords extracted from the wallet data (In browser or on rooted/jailbroken phones)
* [Encrypted (BIP-38) Paper Wallet Support (Eg: From Bitaddress.org)](https://bitaddress.org) Also works with altcoin forks like liteaddress.org, paper.dash.org, etc...
* Brainwallets
* Sha256(Passphrase) brainwallets (eg: Bitaddress.org, liteaddress.org, paper.dash.org)
* sCrypt Secured Brainwallets (Eg: Warpwallet, Memwallet)
* [Ethereum UTC Keystore Files](https://myetherwallet.com) Ethereum Keystore files, typically used by wallets like MyEtherWallet, MyCrypto, etc. (Also often used by Eth clones like Theta, etc)
* [Damaged Raw Eth Private Keys]() Individual Private keys that are missing characters.
* [Free and Open Source](http://en.wikipedia.org/wiki/Free_and_open-source_software) - anyone can download, inspect, use, and redistribute this software
* Supported on Windows, Linux, and OS X
* Support for Unicode passwords and seeds
Expand Down
213 changes: 201 additions & 12 deletions btcrecover/btcrpass.py

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions btcrecover/test/test_passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -2788,6 +2788,107 @@ def test_brainwallet_warpwallet_litecoin_opencl(self):
crypto = "litecoin",
check_compressed=False)

# Raw Private Key Wallets
class Test13RawPrivateKeyRecovery(unittest.TestCase):
def test_rawprivatekey_Eth(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['0xB9644424F9E639D1D0F27C4897e696CC324948BB'],
check_compressed=True,
check_uncompressed=True,
force_check_p2sh=False,
crypto='ethereum')

correct_pw = tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d33")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))

def test_rawprivatekey_Btc_legacy_Hex_Compressed(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['1KoHUH6vf9MGRvooN7bHqrWghDqKc566tB'],
check_compressed=True,
check_uncompressed=True,
force_check_p2sh=False,
crypto='bitcoin')

correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))

def test_rawprivatekey_Btc_legacy_Hex_Uncompressed(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['1N8pQZkmrKjzSwuYFzThiMr8Ceg2mX4tAo'],
check_compressed=True,
check_uncompressed=True,
force_check_p2sh=False,
crypto='bitcoin')

correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))

def test_rawprivatekey_Btc_p2sh_Hex_Compressed(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['3AZyE1Dobb71DWjvYtSNqwNELPGQhdjqp4'],
check_compressed=True,
check_uncompressed=False,
force_check_p2sh=True,
crypto='bitcoin')

correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))

def test_rawprivatekey_Btc_nativesegwit_Hex_Compressed(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['bc1qecejtf3csl8gmjxvjmh0mwtqah8z7eetrcay67'],
check_compressed=True,
check_uncompressed=True,
force_check_p2sh=False,
crypto='bitcoin')

correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))

def test_rawprivatekey_Btc_legacy_WIF_Uncompressed(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['1EDrqbJMVwjQ2K5avN3627NcAXyWbkpGBL'],
check_compressed=False,
check_uncompressed=True,
force_check_p2sh=False,
crypto='bitcoin')

correct_pw = tstr("5JYsdUthE1KzGAUXwfomeocw6vwzoTNXzcbJq9e7LcAyt1Svoo8")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))

def test_rawprivatekey_Btc_legacy_WIF_Compressed(self):
wallet = btcrpass.WalletRawPrivateKey(addresses=['1NMnWwgsaLaV97m3Z3PAkCvAyJqdVKHnEE'],
check_compressed=True,
check_uncompressed=False,
force_check_p2sh=False,
crypto='bitcoin')

correct_pw = tstr("KzTgU27AYQSojvpXaJHS5rmboB7k83Q3oCDGAeoD3Rz2BbLFoP19")

self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2))
self.assertEqual(wallet.return_verified_password_or_false(
(tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2))


# QuickTests: all of Test01Basics, Test02Anchors, Test03WildCards, and Test04Typos,
# all of Test05CommandLine except the "large" tests, and select quick tests from
# Test08KeyDecryption
Expand Down
68 changes: 59 additions & 9 deletions docs/Usage_Examples/basic_password_recoveries.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ python btcrecover.py --wallet-type bch --addrs bitcoincash:qqv8669jcauslc88ty5v0
```
<br>
Basic Cardano, so need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) For Cardano recovers, [see the notes here as well.](bip39-accounts-and-altcoins.md) This will accept either base or stake addresses... (Byron-Era addresses are not supported))

```
python btcrecover.py --wallet-type cardano --addrs addr1q90kk6lsmk3fdy54mqfr50hy025ymnmn5hhj8ztthcv3qlzh5aynphrad3d26hzxg7xzzf8hnmdpxwtwums4nmryj3jqk8kvak --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "ocean hidden kidney famous rich season gloom husband spring convince attitude boy"
```
Expand Down Expand Up @@ -195,45 +196,45 @@ Brainwallets are a very old (**and very unsafe**) type of wallet. Given this, mo

Basic Bitcoin Command (Will check both compressed and uncompressed address types, even though in this example this is a compressed address)
```
python btcrecover.py --brainwallet --addresses 1BBRWFHjFhEQc1iS6WTQCtPu2GtZvrRcwy --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs 1BBRWFHjFhEQc1iS6WTQCtPu2GtZvrRcwy --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>

Bitcoin Wallet, but set to only check uncompressed addresses. (Only use this for VERY old wallets that you are sure aren't a compressed address, though also consider that uncompressed is the default... Only gives a small speed boost)

```
python btcrecover.py --brainwallet --addresses 1MHoPPuGJyunUB5LZQF5dXTrLboEdxTmUm --skip-compressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs 1MHoPPuGJyunUB5LZQF5dXTrLboEdxTmUm --skip-compressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>

P2SH Bitcoin Wallet (Like the kind you would get of something like segwitaddress.org, as of 2021, these are all compressed type addresses, so can skip checking uncomrpessed ones...)
```
python btcrecover.py --brainwallet --addresses 3C4dEdngg4wnmwDYSwiDLCweYawMGg8dVN --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs 3C4dEdngg4wnmwDYSwiDLCweYawMGg8dVN --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>

Bech32 Bitcoin Wallet. (From segwitaddress.org)
```
python btcrecover.py --brainwallet --addresses bc1qth4w90jmh0a6ug6pwsuyuk045fmtwzreg03gvj --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs bc1qth4w90jmh0a6ug6pwsuyuk045fmtwzreg03gvj --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>

Litecoin Wallet (From liteaddress.org - These are all uncompressed with no option to use compressed) No extra arguments are needed for these types of wallets.
```
python btcrecover.py --brainwallet --addresses LfWkecD6Pe9qiymVjYENuYXcYpAWjU3mXw --skip-compressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs LfWkecD6Pe9qiymVjYENuYXcYpAWjU3mXw --skip-compressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>

Dash Wallet (From paper.dash.org) - No compression parameters specificed, so it will just check both
```
python btcrecover.py --brainwallet --addresses XvyeDeZAGh8Nd7fvRHZJV49eAwNvfCubvB --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs XvyeDeZAGh8Nd7fvRHZJV49eAwNvfCubvB --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>


Dash Wallet (From paper.dash.org - Or if you know you used a compressed one... (Though Uncompressed is the default)
```
python btcrecover.py --brainwallet --addresses XksGLVwdDQSzkxK1xPmd4R5grcUFyB3ouY --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --brainwallet --addrs XksGLVwdDQSzkxK1xPmd4R5grcUFyB3ouY --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>

Expand All @@ -244,13 +245,13 @@ Note: At this time, only Bitcoin and Litecoin are supported... (Eth could be eas

Basic Bitcoin Wallet with "btcr-test-password" as the salt. (Warpwallet suggested using your email address) These wallets are all "uncompressed" type, but the performance gain for this is so small compared to how long the sCrypt operation takes, it isn't worth not checking both types...
```
python btcrecover.py --warpwallet --warpwallet-salt btcr-test-password --addresses 1FThrDFjhSf8s1Aw2ed5U2sTrMz7HicZun --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --warpwallet --warpwallet-salt btcr-test-password --addrs 1FThrDFjhSf8s1Aw2ed5U2sTrMz7HicZun --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```
<br>
Basic Litecoin Wallet with "btcr-test-password" as the salt. (Like what memwallet or mindwallet produces, so you need to add the --crypto argment and specify litecoin) These wallets are all "uncompressed" type, but the performance gain for this is so small compared to how long the sCrypt operation takes, it isn't worth not checking both types...

```
python btcrecover.py --warpwallet --warpwallet-salt btcr-test-password --crypto litecoin --addresses LeBzGzZFxRUzzRAtm8EB2Dw74jRfQqUZeq --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
python btcrecover.py --warpwallet --warpwallet-salt btcr-test-password --crypto litecoin --addrs LeBzGzZFxRUzzRAtm8EB2Dw74jRfQqUZeq --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```

## Block.io Wallets
Expand Down Expand Up @@ -293,3 +294,52 @@ Basic Ethereum Command, so need to specifcy the `--wallet-type` (But can leave o
python btcrecover.py --slip39 --wallet-type ethereum --addrs 0x0Ef61684B1E671dcBee4D51646cA6247487Ef91a --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --slip39-shares "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing" "hearing echo academic agency deliver join grant laden index depart deadline starting duration loud crystal bulge gasoline injury tofu together"
```
<br>

## Raw Private Keys##
BTCRecover an also be used to recover from situations where you have a damaged private key.

This is handled in a similar way to a password recovery, so your private key guesses go in a tokenlist, using the %h wildcard to substitute hexidecimal characters or %b to substitute base58 characters. You can use either a tokenlist or a passwordlist, depending on your situation, as well as the standard typos. If you are using a tokenlist, you will just need to ensure that the private keys being produced match the length and characters required for a private key...

If you know the address that the private key corresponds to, you can supply that, alternatively you can use an AddressDB.

### Raw Eth Private Keys ###
You will also notice that the leading "0x" needs to be removed from the private key.

**Example tokenlist**
``` linenums="1"
{% include "eth_privkey_tokenlist.txt" %}
```

The tokenlist above is an example is a standard Eth private key (with the leading 0x removed) where there are three damanged parts. One single character (%h), one two-character (%2h) and one three-character (%3h) It will take about 20 mintes to run...

```
python btcrecover.py --rawprivatekey --addrs 0xB9644424F9E639D1D0F27C4897e696CC324948BB --wallet-type ethereum --tokenlist ./docs/Usage_Examples/eth_privkey_tokenlist.txt
```

## Raw Bitcoin Private Keys ##
Bitcoin private keys are supported in both Compressed and Uncompressed formats in Base58 and also as raw Hexidecimal keys.

If you are using a tokenlist (as in the examples below) with multiple private keys, one per line, you will also want to specify the "--max-tokens 1" argument.

**Example tokenlist**
``` linenums="1"
{% include "eth_privkey_tokenlist.txt" %}
```

The command below will attempt a recovery for an old-style, uncompressed private key with one missing character, using a tokenlist containing three possible private keys.

```
python btcrecover.py --rawprivatekey --addrs 1EDrqbJMVwjQ2K5avN3627NcAXyWbkpGBL --wallet-type bitcoin --max-tokens 1 --tokenlist ./docs/Usage_Examples/btc_privkey_tokenlist.txt
```

The command below will attempt a recovery for a more modern (compresseed, native-segwit address) private key with one missing character, using a tokenlist containing three possible private keys.

```
python btcrecover.py --rawprivatekey --addrs bc1qafy0ftpk5teeayjaqukyd244un8gxvdk8hl5j6 --wallet-type bitcoin --max-tokens 1 --tokenlist ./docs/Usage_Examples/btc_privkey_tokenlist.txt
```

You can also do raw private key repair, even if you don't have a record of the corresponding address, through using an AddressDB. (Also works for Eth, BCH, etc...)

```
python btcrecover.py --rawprivatekey --addressdb ./btcrecover/test/test-addressdbs/addresses-BTC-Test.db --wallet-type bitcoin --max-tokens 1 --tokenlist ./docs/Usage_Examples/btc_privkey_tokenlist.txt
```
4 changes: 4 additions & 0 deletions docs/Usage_Examples/btc_privkey_tokenlist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7
KzTgU27AYQSojvpXaJHS5rm%*oB7k83Q3oCDGAeoD3Rz2BbLFoP19
5JYsdUthE1KzGA%*Xwfomeocw6vwzoTNXzcbJq9e7LcAyt1Svoo8
KzPyZwq13F8tyXJ9si1ovGYo%*btQADi6qD9XbDHRWtfWud7PriHV
1 change: 1 addition & 0 deletions docs/Usage_Examples/eth_privkey_tokenlist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5db77aa7aea5%2h7d6b4c64dab21%h972cf4763d4937d3e6e17f580436dcb10%3h
Loading

0 comments on commit 0613341

Please sign in to comment.