{"id":19336739,"url":"https://github.com/samrocketman/software_password_design","last_synced_at":"2025-09-10T21:33:30.096Z","repository":{"id":145442342,"uuid":"17930836","full_name":"samrocketman/software_password_design","owner":"samrocketman","description":"An exploration in best practices for storing passwords in a database.","archived":false,"fork":false,"pushed_at":"2020-10-18T07:11:53.000Z","size":10,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-06T10:13:21.790Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samrocketman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-03-20T04:36:20.000Z","updated_at":"2017-06-05T22:52:35.000Z","dependencies_parsed_at":"2023-06-03T18:31:25.930Z","dependency_job_id":null,"html_url":"https://github.com/samrocketman/software_password_design","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samrocketman%2Fsoftware_password_design","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samrocketman%2Fsoftware_password_design/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samrocketman%2Fsoftware_password_design/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samrocketman%2Fsoftware_password_design/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samrocketman","download_url":"https://codeload.github.com/samrocketman/software_password_design/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240441953,"owners_count":19801793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T03:12:17.607Z","updated_at":"2025-02-24T08:13:18.795Z","avatar_url":"https://github.com/samrocketman.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Purpose\n\n\nThe purpose of this little project is to show how easy it is to generate many iterations of hashes.  It is also proving a point that if you're say doing \"thousands\" of iterations of rehashing a password (as I've heard a few times in PR blog posts for companies who get compromised) with a salt it simply is not enough.  And if you're not using hashing or using hashing without a salt that's just plain irresponsible.  If you catch someone doing that you should email [Plain Text Offenders][1] to disclose this bad practice.  If you want to learn more about password hashing in applications I recommend reading this [Crack Station article][2].\n\nThat being said here I'll show you some simple benchmarks as well as make a proposal for a better password hashing system which will dynamically evolve over time as websites are upgraded over time.\n\n# Simple benchmarks\n\nThese benchmarks are the output of the `pword_hash_benchmark.py` script.\n\n```\nAlgorithm: md5\nBenchmark run time (seconds): 4.07\nIterations: 427237\nEnd user calcuted time for iterations (microseconds): 503287\nHex Digest: 49f5a9ea663cc188befdb629a101342c\nBase64 Digest: SfWp6mY8wYi+/bYpoQE0LA==\nAES Encrypted Base64 Digest: Ytht5Njgz5vJ0m9feiG7Ml7KK87bU9n2fKRbMelEThs=\nAES Decrypted Base64 Digest: SfWp6mY8wYi+/bYpoQE0LA==\n\nAlgorithm: sha1\nBenchmark run time (seconds): 5.06\nIterations: 397563\nEnd user calcuted time for iterations (microseconds): 502847\nHex Digest: 31ce5993abcc72fdad00f7e9a32647e5457cb7fe\nBase64 Digest: Mc5Zk6vMcv2tAPfpoyZH5UV8t/4=\nAES Encrypted Base64 Digest: KCxuOEXk/p0AaUAb9XL7nsfHEIupYnpzCOgYi1bMheU=\nAES Decrypted Base64 Digest: Mc5Zk6vMcv2tAPfpoyZH5UV8t/4=\n\nAlgorithm: sha224\nBenchmark run time (seconds): 1.65\nIterations: 228805\nEnd user calcuted time for iterations (microseconds): 502781\nHex Digest: b9e023977d9bed335090fc4bad1a900c6db3dfca3e18fb2b2e3b8cfc\nBase64 Digest: ueAjl32b7TNQkPxLrRqQDG2z38o+GPsrLjuM/A==\nAES Encrypted Base64 Digest: aXX2NXY2dCfX6KnGaJioLJ3DBOX+STLeTvYv6eo0swZlEOC8yEhtSBttvufv6zQMTYaL556alI1nQKzZMmW2WA==\nAES Decrypted Base64 Digest: ueAjl32b7TNQkPxLrRqQDG2z38o+GPsrLjuM/A==\n\nAlgorithm: sha256\nBenchmark run time (seconds): 1.67\nIterations: 223934\nEnd user calcuted time for iterations (microseconds): 509683\nHex Digest: dd1cc4871d6251fd9ba95cce346b623941e3ca855a8099e36f0eb0d728614de0\nBase64 Digest: 3RzEhx1iUf2bqVzONGtiOUHjyoVagJnjbw6w1yhhTeA=\nAES Encrypted Base64 Digest: Oz5PfUwcmPXaRjAHgOkDlPtlPxVGfFV24gyqVZDWy3v88sXV7zvkhIZ5JY1g9PuBTYaL556alI1nQKzZMmW2WA==\nAES Decrypted Base64 Digest: 3RzEhx1iUf2bqVzONGtiOUHjyoVagJnjbw6w1yhhTeA=\n\nAlgorithm: sha384\nBenchmark run time (seconds): 2.68\nIterations: 226513\nEnd user calcuted time for iterations (microseconds): 501515\nHex Digest: 6090e804a325fef6f687f999400a13f800147488bd31ebb99a3dfe68157ad07a903ee22f7fd5a80c397ea12e641421f6\nBase64 Digest: YJDoBKMl/vb2h/mZQAoT+AAUdIi9Meu5mj3+aBV60HqQPuIvf9WoDDl+oS5kFCH2\nAES Encrypted Base64 Digest: mMRNHCw5XjWi5Z9xkATv+5Zcb9vHWC32b6U317PslAtGDQgSfeKDN0DRwS+fOmaS1Kz6RssZUd2AuntEoxv24k2Gi+eempSNZ0Cs2TJltlhNhovnnpqUjWdArNkyZbZY\nAES Decrypted Base64 Digest: YJDoBKMl/vb2h/mZQAoT+AAUdIi9Meu5mj3+aBV60HqQPuIvf9WoDDl+oS5kFCH2\n\nAlgorithm: sha512\nBenchmark run time (seconds): 2.75\nIterations: 202731\nEnd user calcuted time for iterations (microseconds): 505061\nHex Digest: 025520812d7a4f08b4d0e54787b8fd7e8ed25c8ae90780c58de3293635f8c52cda9051c93a342d1e739cd9cfd06b59608fd5a27e635a9af1dd47d02d623a8c35\nBase64 Digest: AlUggS16Twi00OVHh7j9fo7SXIrpB4DFjeMpNjX4xSzakFHJOjQtHnOc2c/Qa1lgj9WifmNamvHdR9AtYjqMNQ==\nAES Encrypted Base64 Digest: U+uqI8RJT1awLS2lYVEqDA7jJL8LxAmbF87lg87J90gSei0ntUHJLHw3isb/x4cASCpbUfKdKeOhDyrtgbwd+OZcTgOuA1abBLMjmO57If3D15db7G7rdP1ToOFTJcll\nAES Decrypted Base64 Digest: AlUggS16Twi00OVHh7j9fo7SXIrpB4DFjeMpNjX4xSzakFHJOjQtHnOc2c/Qa1lgj9WifmNamvHdR9AtYjqMNQ==\n\n```\n\n# A proposal for key stretching\n\nSo normally a decent password hashing function in an application will have:\n\n* Use a well known cryptographic hash algorithm that doesn't have any known or proven collisions and has been adequately peer reviewed by the security community.\n* Hash multiple iterations of the hash it is using.\n* The function will use a salt to generate unique hashes.  A salt is basically just a random string of characters to append to the input password which will then be hashed.\n* The salt will be different for each password hash in the database.\n* The salt is stored in a field with the password hash so that calculating the hash is repeatable.\n* Encrypted hashes in the database with the key to the encryption stored on the local filesystem (not in the database) with local filesystem access disabled for the database if it supports it (in case of an SQL injection attack only).\n\nSo a pretend database entry for a user will look something like the following.\n\n```\nUsername | Password (hashed+encrypted) | Salt\n```\n\nI propose updating said pretend database entry.\n\n```\nUsername | Password (hashed+encrypted) | Salt | Iterations\n```\n\nThe reason why the iterations are stored in the database is because it should be updated as performance in your service is upgraded.  This means if you get more robust hardware (i.e. replace it every few years) you'll get stronger password storage out of the box with little effort.\n\nSo I propose doing the following.  When your application first starts up it runs a quick benchmark on the number of password hash iterations it can perform in say `500ms` (or some arbitrary time you're willing to wait for the hash to be computed on your hosted system).  This will be the estimated performance metric for the number of iterations the password should be hashed.  All new passwords will be be created with this number of hashes.\n\nIf your application restarts then the benchmark calculation for number of iterations is run during startup.  This will be the new value.  If an existing user is logging in and the number of iterations their stored password is hashed is different beyond a threshold then their hash will be recalculated with the new system benchmark for number of hash iterations.  In addition to the user login hash recalculation there should be an external job that periodically runs to adjust the hashes of the users based on the benchmarked number of iterations stored in the database for the current runtime.\n\nWith this method the strength of your password hashing should increase as the performance of the systems your service lies on increases at no extra cost to you.\n\n[1]: http://plaintextoffenders.com/\n[2]: https://crackstation.net/hashing-security.htm\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamrocketman%2Fsoftware_password_design","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamrocketman%2Fsoftware_password_design","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamrocketman%2Fsoftware_password_design/lists"}