Securing Your Dart Backend: Bcrypt Benchmarking
— Flutter, Security, bcrypt — 5 min read
At its heart, bcrypt is a password-hashing function. Hashing is a one-way operation. It takes your data, like a password, into a jumbled string of characters of the same length. That string is the hash. You can't turn that hash back into the original password. It's designed to be irreversible, and that's what makes it so secure.
One thing that makes bcrypt special is that it's slow on purpose. This might sound weird, but it's a good thing. Older ways of hashing, like MD5 or SHA1, are fast. But today, they're unsafe for password use because that speed helps attackers. Modern computers can go through MD5 hashes so quickly that trying out every password imaginable (a brute-force attack) becomes too easy. Bcrypt is designed to make this guessing way too complicated and time-consuming.
The Cost / Work Factor
The "cost factor," sometimes called the "work factor" or rounds, is a mandatory setting in bcrypt. This number decides how much computer power and time the hashing takes. The cost factor works on a logarithmic scale. For example, a cost factor of 10 means 210 (or 1024) rounds of the internal hashing steps. A cost factor of 12 means 212 (or 4096) rounds. So, every time you bump up the cost factor by one, the work needed to make the hash goes up quite a bit.
This ability to adjust the cost makes bcrypt an "adaptive" function. For instance, while MD5 might hash a password in tiny fractions of a second, bcrypt with a work factor of 12 might take around 0.4 seconds. That's a world of difference in cracking time. Seconds versus potentially years (the exact time depends on the computer, as my M1 Pro tests will show later).
Benchmarking Playground
Developers who want to use bcrypt on their server, the hashlib package is one of the fastest written in pure Dart. It offers several cryptographic hashing functions, including bcrypt, making it easy to use in apps for password hashing.
The speed tests discussed in this post were done on an Apple M1 Pro machine running macOS. It's important to say what system was used for testing because bcrypt's speed is tied to the CPU. That means the processor's speed directly affects how long hashing takes. Other things, like which version of the Dart SDK we're using, can also make a difference, but the CPU type and speed are usually the biggest things for bcrypt tests.
The testing process involved carefully measuring how long it took to hash a sample password using hashlib's bcrypt across different cost factors. The main idea behind how I did this is shown in the Dart code snippet below.
Duration timeSingleHash(String password, int costFactor) { final stopwatch = Stopwatch()..start(); try { final salt = bcryptSalt(nb: costFactor); bcrypt(utf8.encode(password), salt); } catch (e) { print('Error during hashing: $e'); // Return a very large duration to indicate failure return const Duration(days: 1); } stopwatch.stop(); return stopwatch.elapsed;}
Each password hashing for a specific cost factor was done many times to get steady and trustworthy numbers. The average time these runs took was then printed out. This method helps soften the effect of system hiccups and other temporary background stuff, giving a better picture of typical performance. The cost factors tested ranged from a low one (like 8) to a much higher one (like 14).
The Results
Testing hashlib's bcrypt on the M1 Pro (macOS) gave me specific hashing times for different cost factors. The table below shows these results. It lists the average time to hash a sample password for each cost factor I tried.
Cost | Avg Time | Min Time | Max Time | StdDev (ms) | Iterations---- | -------- | -------- | -------- | ----------- | ---------- 8 | 28 ms | 26 ms | 38 ms | 3.45 | 10 10 | 107 ms | 107 ms | 108 ms | 0.37 | 10 12 | 429 ms | 428 ms | 432 ms | 1.26 | 10 14 | 1.72 s | 1.71 s | 1.75 s | 10.09 | 10
This table clearly shows how the cost factor directly changes the amount of computer work needed. As the cost factor increases, the average hashing time gets much longer. This information is basic for choosing the right cost factor and balancing security needs with performance.
While the table gives you exact numbers, a picture can often provide a quicker feel for how the cost factor and hashing time are related. A graph plotting "Average Hashing Time" against "Cost Factor" shows this relationship is exponential. It curves up sharply.

This observed sharp rise in hashing time matches the 2cost relationship built into bcrypt's design. It confirms the theory works out in practice on the M1 Pro with the hashlib library.
- A cost factor of 10 (about 107 ms) is fast and still offers good security. This could be a good choice if speed is critical and the data isn't extremely sensitive.
- A cost factor of 12 (around 429 ms) gives a strong level of security while keeping the hashing time in a range that's probably okay for most interactive things like logging in.
- Cost factors of 14 and above (1.7s and above) start to cause delays that users will likely notice and might find annoying, even on fast server CPUs.
bcrypt's purposeful slowness is an important security feature. It makes big, offline brute-force attacks too costly and time-consuming for most attackers, unless they have massive resources, especially when trying to crack individually salted hashes.
Want to Master Flutter Security?
This post has focused on bcrypt performance with hashlib, but it skims the surface of complete application security.
If you want to learn more about advanced crypto methods beyond bcrypt, safe ways to store data on different platforms, network security for mobile and web apps, specific security things to watch out for with Flutter (on iOS, Android and Web), and big-picture security plans made for the Flutter framework, these topics need a lot more exploring.
My upcoming "Flutter Security Guidelines" plans to dive deep into these topics.
Stay tuned!