Searching for Bitcoins

June 6, 2022

A bitcoin wallet is a "place" to store Bitcoins. I say "place" because it is not actually a physical place but solely defined by a private key, which is used to sign transactions. So anyone who knows the key has complete access to the wallet.

So, is it possible to brute-force a private key for a wallet?

Spoiler: No, not really, that's part of the "mathematical security" Bitcoin offers.

There's this video about how big 2**256 really is:

The creation is sometimes also done via choosing a series of words, like for Electrum, where a "wallet seed" contains of 12 words from a list of words, which also can be written down on paper. It can then later be used to re-create the wallet and gain access to the Bitcoins, even if the original PC is not around anymore.

While reading some related articles I stumbled on a StackExchange post, and an answer on a similar question got me thinking:

It is possible to brute force some Bitcoin addresses, because some people generate their private keys in an insecure manner. Any (non-zero) 32 bytes can be a private key. So running sha256 over a passphrase gives an apparently random, but brute force-able private key.

So, why not just try that?

Setup

This is what we need:

  1. An updated list of all addresses which have some Bitcoins
  2. A list of commonly used passphrases
  3. A script to generate public addresses from sha256'ed passphrases

Getting all addresses with a non-zero balance

First, we'd need a list with all current addresses with a balance. Anyone with a up-to-date full node of the Bitcoin network already has it, and luckily, there are people sharing that data just in the format we need. For example, you can download an up-to-date list here, it will be around 1.2 GB currently (zipped).

The list contains entries like this:

address balance
34xp4vRoCGJym3xR7yCVPFHoCNxv4Twseo  26547968732321
bc1qgdjqv0av3q56jvd82tkdjpy7gdp9ut8tlqmgrpmv24sq90ecnvqqjwvw97  16800998536372
1P5ZEDWTKTFGxQjZphgWPQUpe554WKDfHQ  12084557276776
37XuVSEpWW4trkfmvWzegTHQt7BdktSKUs  9450511456719
38UmuUqPCrFmQo4khkomQwZ4VbY2nZMJ67  8406701772536
1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF  7995721697629
...

 Getting a list of passwords

Next up we need a list of common passphrases. As there are many such lists available I'll just go with one from the NCSC (UK), available via a web.archive.org-Link.

It contains entries such as:

123456
123456789
qwerty
password
111111
12345678

Calculating the public address

To get a private key from a passphrase, we need to calculate the SHA256 digest. This is easily done using hashlib:

private_hash = hashlib.sha256(bytes(word, 'ascii')).hexdigest()

Next, we want to create a Bitcoin wallet using that hash. Luckily we don't have to go through wallet generation ourselves, but there is also a library which does the heavy lifting, bitcoinaddress:

from bitcoinaddress import Wallet
wallet = Wallet(private_hash)

This will generate a wallet with the most basic information, and also some pre-generated public addresses, which we can use to check the list later:

Private Key HEX: 30caae2fcb7c34ecadfddc45e0a27e9103bd7cfc87730d7818cc096b1266a683
Private Key WIF: 5JBmuBc64pVrKLyDc8ktyXJmAeEwKQogn6jsk6taeq8zRMtGZrE
Private Key WIF compressed: KxrZBKosMreLfy98e5EkQUmPwZNWkG54a4LEyEzfXyUGSRvfnCve 

Public Key: 04a32ed011213146495f58d3ed83a6cc3fc0fd107d5fa2887bbc2fcea81e8bc84f650e81f4ddc84424daab546945f0d7d9dfd4dce39ce3776ee6b8ba78e6eddc7a 
Public Key compressed: 02a32ed011213146495f58d3ed83a6cc3fc0fd107d5fa2887bbc2fcea81e8bc84f

Public Address 1: 1TnnhMEgic5g4ttrCQyDopwqTs4hheuNZ   
Public Address 1 compressed: 17xYNJ5h9eW9CbhntR87YE1myvGtNkrpnK   
Public Address 3: 3QmjwSLdEbBP9qBdqoNuwLRr1AFX4aATrM  
Public Address bc1 P2WPKH: bc1qf3gazzex87xqdlusm4qa2qlpj6r63jgegx2nur    
Public Address bc1 P2WSH: bc1qeqmm7yxpyn7caxcd9gz8mytklnfevqtylrptt9lxdnfvtje2vxkq252l53

So, now that we a public address, we can just search the list of addresses, right? Unfortunately, searching 1.7 GiB of text (uncompressed) takes a really long time, so we need to speed that up.

Using bisect_left() from bisect on a sorted list of addresses, we can use a bisect search, meaning that we just keep cutting the list in half until we find (or don't find) the address we're looking for.

Putting it together

After letting the script run, there is currently no balance on any of the widely used passwords.

But when first preparing this blog post, there was actually a match: 777!

secret: 777
private_hash: eaf89db7108470dc3f6b23ea90618264b3e8f8b6145371667c4055e9c5ce9f52
wallet: 
Private Key HEX: eaf89db7108470dc3f6b23ea90618264b3e8f8b6145371667c4055e9c5ce9f52
Private Key WIF: 5Kbmc1FLfTFTXy9sCZsmxgUjZfP4hLtMPkxpgKjDeV954bQVSQn

Public Key: 042661ad780e66d678af5acc9b6259c93a16001ea261914844a413af90757f25fc6cc2cc571016903625702cf2e60601224cefdd7a99a42a3f4ca1c21f2e06889c 
Public Key compressed: 022661ad780e66d678af5acc9b6259c93a16001ea261914844a413af90757f25fc

Public Address 1: 164694JJjkWpSJH3HufCEK5AdJvUiG9Ucd

You can see the balance here:

You can see that it had a balance of about $4 in April last year.

When I first saw that, I literally wrote:

The balance is so low though, that even if someone decided to steal it, it wouldn't be enough to pay for the transaction fees.

Apparently I was wrong! I had in mind that transaction costs for Bitcoin were about $20 back then, but somehow somebody was able to transfer it to another account.

Anyway, it was interesting to see that all it takes to access an account is really one long number - and if that is generated in an insecure manner, it doesn't take much to get to a private wallet key like shown above.

Thanks for reading!

All the code can be found here.