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

Initial implementation of C++ password checker #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rtobar
Copy link
Contributor

@rtobar rtobar commented Mar 12, 2022

This is a first working version of a password checker written in C++. It uses the Boost Asio and Beast libraries for asynchronous networking and HTTP, and both threads and the Asio support for Boost Coroutines for concurrency. Like the python tool, it has command line toggles to specify the number of threads and workers per thread (or concurrent requests per thread) to use.

This tool implements an in-memory cache, but for the time being there's no loading or dumping of the cache -- it only works for the current session.

In terms of speed, when not using cache I've been able to check passwords at rates of ~25k passwords a second, which amounts to downloading data at ~750 MB/s. When using cache, and while the cache is cold, filling it is rather slow as all threads create contingency to write into it, and thus the maximum initial download speeds go only up to ~400 MB/s. Once the cache is hot the tool can check ~200k passwords per second, if not more. All in all, when using 16 threads and 100 workers per thread, I've been able to check the RockYou database in 1:50 minutes when caching, and 10 minutes without (so caching helps). On the other hand the top 100k passwords can be checked in 9 seconds with caching, and 5 seconds without (so caching pushes down):

$> ppsc rockyou.txt -w 100 -t 16 -o output.csv -T 40000
Read 14344391 passwords in 1.805 [s]
[■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ] [01m:51s<00m:00s]  28.13 MB/s, 92105.00 req/s, E:0/T:3200/R:3200 - 14344391/14344391    
Processed 14344391 passwords in 112.735 [s] at 127239.91 req/s, 283.47 MB/s
Cache hits: prefix=12676711 (88.37%), full=12698202 (88.52%)
Writing results to output.csv
Results written to output.csv in 1.514 [s]

For convenience I've also added a Dockerfile that will build an image containing an optimised build of the tool ready to use. This is based on the latest ubuntu:21.10 image, and it adds only a couple of megabytes on top of that, being as lightweight as possible.

Other notes of things I found while testing this tool:

  • Some requests time out for a very long time when hitting the service hard. This can be even in the orders of > 10 minutes or so. The tool thus adds an option to specify a request timeout, defaults to no timeout.
  • Sometimes Cloudflare sends back a 'Connection: close' header, which I wasn't expecting, so the tool has to handle the re-connection. This and the previous are reported in the progress bar as "T" and "R". There's also an "E" for any other unexpected errors, but nothing really shows up (yet).
  • As noted, caching indeed plays a great effect when checking > 1M passwords, as expected; otherwise it has an impact, but not as big, and using cache can be detrimental (speed-wise) depending on network speed and CPU count. The tool reports on cache hits (prefix- and full-hits). With the RockYou dataset there are some prefix-only hits, meaning that some values in the dataset are not in PwnedPasswords (but we already saw some of that earlier on with in Initial version of python async code #4).

Some things to improve in the future:

  • The cache is based on the hex digest rather than the binary digest, which is one of the points I previously discussed could be used to save space. Changing it to use the binary digest should provide some improvements.
  • Save/restore the cache for future runs
  • Report on Cloudflare hits
  • Report on request timing
  • Works only on Linux (and maybe even some distros only, Ubuntu for sure) because it hardcodes the path where the TLS root certificates are loaded from.

This tool uses Boost Asio and multiple threads to max out network and
CPU usage. It also uses Boost's coroutines for slightly more readable
code. SHA1 calculations are done with OpenSSL's implementation.

The tool has the option to specify a timeout to be applied to requests,
in milliseconds, and whether to use a cache or not (which is never
persisted anyway). Using a cache can put the bottleneck on
multithreaded synchronisation instead of networking, so *not* using it
could be worth in some situations.

The tool does support a "dry-run" mode where no network communication
happens. This was useful to test the rest of the logic, but is also
useful to show what the maximum limit is for checks per second on your
machine.

For convenience there is a Dockerfile to build a Docker image with a
Release build of the tool installed. This is based on Ubuntu 21.10, and
adds only a couple of megabytes on top of the original image, making it
fairly lightweight.
@stebet
Copy link
Collaborator

stebet commented Mar 24, 2022

This is awesome! My C++ foo isn't exactly up to par though so my review is somewhat lacking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants