Release of hshwd
05/02/2026 · 8 min
Summary: hshwd is an open-source offline tool that generates strong, unique passwords from weak ones in a deterministic way, using the properties of hash functions.
Table of contents
Some background
The Good, the Bad and the Ugly
Make it look random
Make it slow
Make it unbreakable
Usability concerns
FAQ
Downloads
Some background
Back in 2023, I made a small tool for generating many complex, unique passwords from simple ones. I called it pwgen, but in reality I could've called it SHA-1 because this is really all it did.
The intended use was that you'd type your usual password (you know, the one you use everywhere even though you shouldn't) and then you would append something unique (e.g. the name of the website you're creating a password for), and then the program would return the SHA-1 hash of your input and ta-da! You get a long, unique password that you can generate back every time you need it because hashing is deterministic.
We're now in 2026 and I have just completed an online course on cryptography after realising that this topic has been fascinating me for years (thank you Soatok's blog!).
I am now the most knowledgeable I have ever been (and less than I'll be tomorrow), so now is the perfect time to aknowledge that pwgen had problems and to redo it properly*.
*that is, to the best of my current knowledge
The Good, the Bad and the Ugly
While pwgen is good at creating many unique passwords, it is by no means good at creating strong passwords.
- If you already know that my password creation method is simply
sha1(password+website), all you have to do is try a dictionary attack (or any attack you want on the 'password' bit, really) and apply this algorithm to each attempt until you find the right password. This will be virtually as fast as a basic dictionary attack because hashing is very fast. - Assuming you didn't already know I'm creating passwords this way, if just one of my passwords got leaked in plaintext you would know by the look of it that it is merely a SHA-1 hash. From that you could probably assume that what's being hashed is not very strong, and you would attempt the attack described above.
- A hash has only a very limited set of characters (0-9, a-f), which makes it easier to brute force when used as a password since there are only 16 possibilities for each character (although I'm not sure this is realistic given there are 16^40 ≈ 10^48 possibilities here).
- The output cannot be used on websites which enforce the usual password creation rules (an uppercase and a lowercase letter, a number, a special character and an obsolete kana like ゐ or ゑ).
- While using SHA-1 is not technically a problem because we don't care about collisions here, we'll need a longer output for what's next.
But before proceeding, let's fix the worst mistake of all: the name. What could be a good name?
Oh, I know! Something that is not exactly the name of a way more popular tool that every linux user knows! We'll go with a mix of 'hash' and 'password': hshwd.
Make it look random
The problems #3 and #4 can be fixed by using a larger portion of the ASCII range instead of keeping the hash as-is. While we're at it, we'll replace SHA-1 with SHA-256 to address #5.
First, let's hash something using SHA-256, say 'a':
ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb
Now, if we split this hash into pairs of characters ['ca', '97', ... 'bb'], we get hex values going from 00 to FF (0-255), which is the full ASCII range. We don't want the full range though (because of the non-printable characters), so we map our values from (0-255) to (33-126), which is the range of ASCII characters frequently used in random password generators (like this one), keep only the integer part, and convert the results to their corresponding ASCII character. Here we end up with:
jXP'j*ej|g2bY-q=]Qx{(N=JdOLQ`w;e
(this is NOT the name of Elon Musk's latest child)
A quick example in case my explanation is unclear: the pair 'ca' is 202 in decimal, so to map it from (0-255) to (33-126) we do 202/255*(126-33)+33 and keep only the integer part which is 106, which corresponds to the ASCII character 'j'.
Since SHA-256 outputs follow a uniform distribution, an attacker cannot distinguish between passwords generated by this program and randomly generated passwords. This solves problem #2 because the use of this program is not obvious from the look of the password alone (except for its length). This is also great against brute-force attacks because no combination of characters is more likely than another (and now there are (126-33)^32 ≈ 10^63 possibilities, which is better).
Problems #3 and #4 are also solved since the set of characters is much bigger and our generated passwords are very likely to comply with all the password rules (except for the obsolete kana one, sorry for lying to you).
Now all that's left is solving problem #1. Should be easy... right?
Make it slow
For now, even though we added a couple steps beyond hashing, this is still a very fast algorithm, therefore the attack described in problem #1 is likely to succeed in a very short time.
My first instinct was to slow it down by iterating the whole thing 10.000 times. And it kind of worked: the program would run in 0.2 seconds with a Python implementation, which is barely noticeable as a user but devastating when you need to try millions of possibilities.
Now I didn't want the program to rely on users having Python installed, so I re-implemented it in Rust to export binaries, and... you guessed it, the thing ran almost instantly – and that's just on my 7 years-old hardware. What if an attacker had better hardware and made a faster implementation? It became obvious that iterating the program 10.000 times was not the solution.
Make it unbreakable (like Kimmy Schmidt)
If instead of fixing the number of iterations we let the user choose, then even given your base password and attacker wouldn't know how many times the program needs to be looped. They would need to try an unknown number of iterations and try each possibility against whatever they are trying to break. Now imagine they don't know your password and they need to do that for each password attempt... now that sounds exciting!
But we can go even further by adding a 3rd secret (the first two being the password and the number of iterations).
If we salt each iteration like so (let's call hshwd() the function that maps the hash to ASCII characters):
for i in range(NUMBER_OF_ITERATIONS):
password = hshwd(sha256(SALT+password))
Then an attacker would need to try un unknown number of passwords, iterate the program an unknown number of times, each time adding an unknown salt.
Usability concerns
The way I used pwgen was that I assigned it to a keyboard shortcut, and after launching it I'd just have to type my password+website combination and hit enter to copy it to my clipboard.
Now that we have added iterations and salt, our options are:
- Typing those everytime before typing the password+website combination
- Typing those once, then storing them in a file and using those settings all subsequent times
- Cloning the hshwd repo, hardcoding your own values and compiling your own binary for everyday use
Options #2 and #3 are obviously less secure because one would need to make sure their binary or files cannot be accessed by an adversary.
Expanding on option #2, the file containing the values could be encrypted using a 'master password', making this whole program a password manager but the passwords are stored in your brain and you still have to type them... which is kinda stupid, but I said it first so I have the copyright on the idea now.
For now, I'll go with the lazy option: hardcoding my own values and having my own binary offline on hardware I trust, and having an option #1 available online (see downloads section) for hardware that I use occasionally and/or that is at risk of being stolen/taken.
FAQ
(nobody asked me anything) (I'll add real questions if I get any)
Why use SHA-256 instead of a slower hashing function?
Because I want to use hshwd on my Android phone, and the only way I know how to do an Android app right now is using Godot Engine, which only has SHA-1 and SHA-256 implemented.
Why not make the password longer?
- Some websites enforce a length limit on passwords, so longer passwords might be rejected (looking at you, France Travail)
- Modern browsers (like Firefox) generally suggest 'strong' passwords and those are only 15-characters long, so I assume 32 characters is more than enough
- The use case for this program is generating unique passwords for websites. If you need something longer, this is probably not the right tool.
Is hshwd safe to use?
I think it is! But I might be wrong, so do not hesitate to correct me if you see any mistakes in this blog post.
What does it NOT protect me against?
This. And also if your computer or smartphone is infected with malware, keyloggers, a Magic Trojan Horse™, or a thing that takes screenshots of your desktop every few seconds.
Why does the logo look like this?
I am only following the latest trends!
Downloads
Downloads are available on the project page.
:^)