From fa62526b477e872cf395d92fb7dafa241da6ee13 Mon Sep 17 00:00:00 2001 From: Joel Coutinho Date: Thu, 25 Jul 2024 19:38:23 +0530 Subject: [PATCH 1/2] updates password hashing blog --- .../password-hashing-salting/hashing-flow.png | Bin 0 -> 35126 bytes content/password-hashing-salting/index.md | 380 +++++++----------- 2 files changed, 153 insertions(+), 227 deletions(-) create mode 100644 content/password-hashing-salting/hashing-flow.png diff --git a/content/password-hashing-salting/hashing-flow.png b/content/password-hashing-salting/hashing-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..01814615ab147308cb40a966f09d42bb13afa85d GIT binary patch literal 35126 zcmeFZhgVbG_AYEe>0Lm&i1e!Tt{}bl-lYiy5Ro1f=~a3M>AiO;kz*j~ybA9=_BW%Ato+ukN4Hd8zA*5!`jv;DYh-vm1ka-tz4!8g&O4kzi(70nZ|>gC znAKA^=hdI(^;Ms#xp6V;OwD#JdvDg3nU%eJhZA8|6k1foiHD%)dB%Hs0-e_%Y)uvM2+0PA0zs^M+ z!?i8^I$Omuk_63$+U^!LMjNJH2pWF{n=D=;XHk&)3^zL>|_v}o>`2yJTllem^aPxmo!7RnU3QR%j9Ni8`WgttxI^fYVUNx2!DV+| z;bBap>g9G0aRw%12^AkJTD7UHs3TkMg+caM6e0{DSwc>xT*wCVe-NeS9X=Mfn>3st z;=UiTM9Lsm>QC=>&xdoDLt**9vJ;QncE&O=M-VYs5J#5H%WMxJjSJ@$z=S`9OClB% zGH4OyTk5mW?g7&>iR(?gq9Lz|2)`n0dN*v1YR=u?zZa_)0X?@vd>50zJVH<{5QOE` z4IH?w9H?F7c)k|BGQe3DB6G{Yk&8Zfl5oB)+kJqA2k8zb1%*-Tw5=Nvc!P;%0H$9P zpK;|$`dt1@W>c5l?ofn7pU75rod1|AK9=0C@uonc>AdI$_}?Xd{eiJL33^;R#gNmm z1gff1^A12fgu{t|+8hv4_LbKc=f?dEIoqnt=)g2@yFv-RNt=U-8|rbyAV}XDh}uDI z++?nbMbDgz8!=66Ul5>wOdOPME5OQXUg+cHz*v9r99~Ip`g8Bt+;Yho#zH#kYf z*3CQyIoz|NW_0dh^rH_l4wZLq{L$TA-tY-friknO(JRSlxG@UT^^0}Uc>RWDJ?a3{4$g|0Hk7Mwq2&Q1DN5QYmYFcDv za`1QYFOEJ#@0J91xwd>M-@4JDR1)C%&ZQB$cdVzKJkoljeY9EZ?^SKWij!>!WsrFD zs>^rHUs)c?j`t*oMY<=-xd=tb!4jiFh7toYd%K$heQiRByk13qdV`ZXBXO>N6Y>$} z(y`BOn}m^ibKo47>1;JAjKDhwPf#d_*53gjM zt}6Ss(JkDo!_CZKfBg5OLijlfcI{gz$Pk>3x_~yU^}^z*ZFAqI-UScRjn^nNXH4xA z`bWIA-T6S1!`M60{!1^8+dA@}!+sjJK~pi?BYsCFJExQh?V(fMVS{D7;fd_4W>007 z)VL@+(qLwJ=*MVru8S8P@RYig#l#Y{MSPdLC&$JytOWkm2A=^Qjk8Y5_CpOPo-f1$ zzIH|S05ovS5^FuEJ~=EWzCAr^IAuI&mvVL0XssP|8mBmOcYP9HP2tEidXgDj&nt!b z5NMhcAoRC`_y;47hAOb5J$W!?lB4g->~eeZ?dM?`q+~=6FS3@&G$(cnm{}a-pO0sg zn+F2Ho%e|1nIwZRPpBI0O&t*S;uhix(BvGKNTCdN*c0aq`YVu%?Z?IlTYnhT>K;zc5y$gwfDch-Pj-z zCB3hZ-$S+X#!6vYgy>ortmB$=Mlkde2e8TL95MJ!eMy z;Yy!dM^ys&=l5DuRR==Z|?CAwp4{Cq;8 zj$0Gc<3*i;ln=SgeL$kXg-33qeDb537hQ6Zla1=PzA#r0D<6laV6|Z_T)uTh!PyCK zzBso&3nm*Rfg6WTU5;OOFnLYuoXV*~w=gVd%ww1C2QQ8~&?{dHLOc&{n^3$azeM-4N`P^5**_5Imi_1EO z@03L>joE1{6taDmdG1FSktg6dV%MDQnjIA$|(8vQ|46^dV(~i zqte?(m?1J;D{g3d&-EYVzVb)zPbkD5hdS4R*u^3$NME8eqZA;CCL{d&{E&Vw*Um|f zaERYjfxdXXzeKA|Vw*OZE@1ckL6{Psq(Ra=W9Z^bgp9$l+cQjY4Y|N0e)`82uYX)9 z{wmlBunwa~F%x$V9awhC#ve}w<)HA1`sZo2WK(V70ywsm`$KijPLoy)E5p?9uGWpq z1q-!C^C8zYQa?c8I;+4egG!+NYD z!dT{Cd?S)K@AY9Rn%lX!3P!_lHk<&{DPayaDVsDmV*!RpE%(Mwx|P;DXKf|cvHjnc z-s%Gd6xQQ;3APG<#72$18#xhfZ@}*gp+ErfruTQv_wV<0;KV}cHkVWV90^ zK`5ZbwW-fuWLXY{N)>!MNEz|vyJPT{l`or{rTE!mW>@BQ^MI@6rpO~0-%SPUdbTi_ z9TgbO`x05bR`p{A19)cbUnjbr^*z+O6YasB&1!123qhDhfU3FhT;ukyKUWV6)cRjDvL4c)(Hwd@8xuJz#`L>TK{OZC#aGC0i~!! z$m0xw)Q;$fr&rx4VS~nIFQL5QQb-162Hy|A=U%)uvat6uNRK2`b$unXYzAiEczIl( zjN6rjd(IAZ$v42?>M_@cT7*9<s+a?d#+P1IGr+*=nhEBNI94mX z<+bjk>5F^lj$7>>>qbU{)^@NM^!M4@oqTN?vb@$%1E)I<+TK39qUb5LGbu4Pw;@z5 z7v1hI;o=wSNy_DLS2^v=1N*<9Hahd&;p1~t;>l+1*fx5Uc#o2gshDas(97LCEQf>K z;z_;>+pnvvb1yd&X-?`iVPXG$e#C^dM*;< zk0%mm9%?H$;cEC6qQ9sIc6^P9rs|$#(KM@JUm^>?8c45B^a8ev*QzPRn!62oo4qEP zR<><-GU1bM{4xEyHil_B6JtJZC!`g)_be$2UVl6~mvvD}wGA3WRdYJoOU!SoU_wt{ zyucYO^7Ed)#(~57^TN(0dHK#iORtR}dwA7ON*g$JyDw70Fnf>O-T=sU4DO(1R^-@q z>&|yfcC4spab5hTA1iQD{$ lc!J9 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%0D$r%IvW!o`BrT0Tg)~Cnn+iWe|YY9zsGu&ybn0S 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 literal 0 HcmV?d00001 diff --git a/content/password-hashing-salting/index.md b/content/password-hashing-salting/index.md index 67ec082d..31a7da45 100644 --- a/content/password-hashing-salting/index.md +++ b/content/password-hashing-salting/index.md @@ -1,305 +1,231 @@ --- title: "What is Password Hashing and why is it important" -date: "2022-03-02" +date: "2024-06-25" description: "A guide on password hashing and salting in different languages and why it's important to do so" cover: "password_hashing_and_salting.png" category: "programming" author: "Rishabh Poddar" --- -Storing passwords can be a nuance due to the liability of them being compromised. To make matters worse, users tend to reuse passwords across services which makes storing them securely even more important. +## Table of Contents -The aim behind storing passwords securely is that even if the database containing them is compromised, the attacker can’t decipher any user’s actual password. This rules out storing passwords in [plain text](https://en.wikipedia.org/wiki/Plaintext). - -Using encryption may seem to be a good choice since the attacker would not know the actual passwords (because they are encrypted). However, if the database is compromised, then the encryption keys would probably[^1] be compromised as well. Using these keys, the attacker would be able to decrypt the encrypted passwords - making this method of storage weak. - -This is where password hashing comes into play: +- [What is Password Hashing?](#what-is-password-hashing) +- [5 Reasons to Use Password Hashing](#5-reasons-to-use-password-hashing) +- [Password Hashing vs. Encryption: What's the Difference?](#password-hashing-vs-encryption-whats-the-difference) +- [How Does Password Hashing Work?](#how-does-password-hashing-work) +- [Which Hash Function to Choose?](#which-hash-function-to-choose) +- [Choosing the Right Hash Function](#choosing-the-right-hash-function) +- [Footnotes](#footnotes) ## What is Password Hashing? -Password hashing involves taking the plaintext password and passing it through a hashing function. Hashing is a one way function which generates a bit string of a fixed size. This is unlike encryption in which given the output and the encryption key, you can know the input. Lets take a look at hash functions in more detail -## What are hash functions? -They are functions that have these properties: +Password hashing is a crucial aspect of password management in the digital world, particularly for developers managing user authentication systems. It involves transforming a plaintext password into a fixed-length string of characters using a hashing algorithm. This one-way function ensures that even if someone gains unauthorized access to the hashed password, they cannot easily revert it to the original password. -1) **Preimage resistance**: Given the output of a hash function `Out`, it should be hard to find any input `In`, which when hashed, results in the same output (`hash(In) = Out`). For example, if I take a random SHA256 hash output (`string` data type) like `"401357cf18542b4117ca59800657b64cce2a36d8ad4c56b6102a1e0b03049e97"`, it should be very hard to know what the input to the hash function was that resulted in this output. Try googling your way into finding it! +Common algorithms used for password hashing include the [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm, [Argon2](https://en.wikipedia.org/wiki/Argon2), and the [Secure Hash Algorithm 2 (SHA-2)](https://en.wikipedia.org/wiki/SHA-2) family, which includes SHA-256. These algorithms are designed to handle inputs of any length and produce a consistent hash. They are built to resist attacks by being computationally intensive, requiring significant computation time and resources to break. -2) **Second preimage resistance**: If an input to a hash function is known, it should be hard to find another input that has the same hashed output. +When a user creates an account and sets a password, the system applies a hashing algorithm to convert the plaintext password into a hash. This hash is stored in the database, not the plaintext password. When the user attempts to log in, the system hashes the entered password and compares it with the stored hash. If they match, the user is authenticated. -3) **Collision resistance**: This says that it is hard to find any two inputs such that their hashed output is the same. This is slightly different from (2) since in (2), you are given one input, and in this case, you can cook up any input. +A key characteristic of password hashing is that the same input will always produce the same output. This is vital for comparison purposes, ensuring that the correct password is matched every time. However, this also means that two identical passwords will produce the same hash, which could be exploited if not mitigated by techniques like salting passwords. -4) **Predictability**: The hash function should always return the same output given the same input. +### 5 Reasons to Use Password Hashing -5) **Fixed Length Output**: The output of the hash function always has the same length (number of chars), regardless of the input’s length. +- 1. **Enhanced Security**: Password hashing significantly enhances the security of stored passwords. Even if an attacker gains access to the hashed passwords, it is extremely difficult to reverse-engineer the original passwords, especially if strong, modern hashing algorithms like the bcrypt algorithm or Argon2 are used. Using a salted hash adds an additional layer of security, making it even harder for hackers to crack the passwords. +- 2. **Regulatory Compliance**: Many industries have stringent data protection regulations that mandate the secure storage of user passwords. Password hashing helps organizations comply with these regulations, such as GDPR, HIPAA, and PCI DSS, by ensuring that user passwords are not stored in plaintext. +- 3. **Protection Against Plaintext Storage**: Storing passwords in plaintext is a major security risk. If a database is compromised, hackers can immediately access all user passwords. Hashing ensures that passwords are stored in a secure, non-human-readable format, reducing the risk of data breaches. +- 4. **Resistance to Brute Force and Rainbow Table Attacks**: Hashing algorithms are designed to be computationally intensive, making it difficult for hackers to use brute force methods to guess passwords. Additionally, hashing combined with salting protects against rainbow table attacks, where precomputed tables of hash values are used to crack passwords. +- 5. **Building User Trust**: Implementing robust security measures like password hashing, is a crucial demonstrator of commitment to protecting user data. This builds trust with users, who are more likely to use services that prioritize their security and privacy. -6) **Input sensitivity**: A small change in the input (even just one character), should have a large change in the output string. For example, the SHA256 hash of `"hello"` is `"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"`. But the hash of `"hella"` is `"70de66401b1399d79b843521ee726dcec1e9a8cb5708ec1520f1f3bb4b1dd984"`. As you can see, the outputs are verify different. +## Password Hashing vs. Encryption: What's the Difference? +Understanding the difference between password hashing and encryption is essential for developers working on secure authentication systems. While both techniques are used to protect data, they serve different purposes and function in distinct ways. -Due to this one-way property, storing a hashed value of a password is a good idea since if their hash is compromised (via a database leak), the attacker would not know the original password (which is the input to the hash function). In fact, the only “entity” which would know the input to the hash function would be the end user who generated the password in the first place. This is exactly what we want from a security point of view. +### Hashing -> Due to property number (5), there are an infinite number of inputs that can yield the same hash output. But it’s also very difficult to find any one of those infinite numbers of inputs given a specific output! +- One-way Function: Password hashing is a one-way process, meaning once data is hashed, it cannot be reverted to its original form. +- Fixed Output Length: Regardless of the input size, a hashing algorithm produces a fixed-length string. For instance, SHA-256 always generates a 256-bit message digest, ensuring uniformity in storage and comparison. +- Primary Use Case: Hashing is primarily used for verifying data integrity and securely storing sensitive information like passwords. When a password is hashed and stored, subsequent password attempts are hashed and compared against the stored hash for verification. -## What is salting and why hashing alone is not good enough - Problems with humans -Many people tend to use [common passwords](https://nordpass.com/most-common-passwords-list/) like "password", "12345" etc. Since the hash of the same input never changes (see property (4) above), we can precompute the hash of common passwords and then check the leaked database data against those precomputed hashes. +### Encryption -For example, the SHA256 hash of `"12345"` is `"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"`. If this hash is seen in a database, we know that the user’s password is `"12345"`[^2]. In fact, there is an entire database of precomputed hashes which can be checked against. These are called [rainbow tables](https://www.geeksforgeeks.org/understanding-rainbow-table-attack/). +- **Two-way Function**: Encryption involves converting plaintext data into ciphertext using an encryption key. The original data can be recovered by decrypting the ciphertext with the appropriate decryption key. This two-way functionality is crucial for protecting data that needs to be accessed and read later. +- **Variable Output Length**: The length of the encrypted data (ciphertext) can vary based on the encryption algorithm and the length of the input data. This means encrypted data may not have a consistent size. +- **Primary Use Case**: Encryption is used for protecting data in transit and at rest, ensuring that only authorized parties with the correct key can access the original information. Common use cases include secure communication channels (like HTTPS) and encrypted file storage. -The way to solve this problem is to add some random string, known as “salt”, to a password before hashing it (during the sign up process), and then we append that random string to the computed hash before storing it in the database. Let’s take an example: +In summary, password hashing is used for secure, irreversible storage of data like passwords, while encryption is used for reversible protection of data that needs to be read or accessed later. -- Alice’s password: `"12345"` -- Bob’s password: `"12345"` -- Alice’s random string (salt): `"ab$45"` -- Bob’s random string (salt): `"ih&g3"` -- Alice’s modified password: `"12345ab$45"` -- Bob’s modified password: `"12345ih&g3"` -- Alice’s SHA256 hash: `"2bb12bb768eb669f0e4b9df29e22a00467eb513c275ccfff1013288facac7889"` -- Bob’s SHA256 hash: `"b63400702c6f012aeaa57b5dc7eefaaaf3207cc6b68917911c410015ac0659b2"` +## How Does Password Hashing Work? -As you can see, their computed hashes are completely different, even though their passwords are the same. Most importantly, **these hashes won’t be in any rainbow table** since they are generated from fairly random strings (`"12345ab$45"` and `"12345ih&g3"`). +Password hashing is a multi-step process designed to transform plaintext passwords into secure, fixed-length hashes that are difficult to reverse-engineer. Here’s a detailed look at how this process works: -Before we store these hashes in the database, we must append the salt to them: -- Alice’s hash + salt: `"2bb12bb768eb669f0e4b9df29e22a00467eb513c275ccfff1013288facac7889.ab$45"` -- Bob’s hash + salt: `"b63400702c6f012aeaa57b5dc7eefaaaf3207cc6b68917911c410015ac0659b2.ih&g3"` +1. **User Creates a Password**: When a user registers or updates their password, they provide a plaintext password. This password is the initial input for the hashing process. +2. **Generating a Salt**: To enhance security, a unique salt (a random string of characters) is generated for each password. This random salt ensures that even identical passwords result in different hashes, preventing hackers from using precomputed tables (rainbow tables) to crack hashes. +3. **Hashing the Password**: The plaintext password, combined with the salt, is passed through a hashing algorithm such as the bcrypt algorithm, Argon2, or SHA-256. These algorithms perform complex mathematical operations on the input data to produce a fixed-length hash. The computational intensity of these algorithms makes it challenging for hackers to use brute force methods to guess passwords. +4. **Storing the Hash**: The resulting hash, along with the salt, is stored in the database. The salt is typically stored alongside the hash, as it is needed for password verification. +5. **User Login and Password Verification**: When a user attempts to log in, they provide their plaintext password. The system retrieves the stored salt associated with the user’s hash and combines it with the entered password. This combination is then hashed using the same algorithm. If the resulting hash matches the stored hash, the user is authenticated. This process ensures that the correct password can be verified without ever needing to store or transmit it in plaintext. -The reason we append the salt to the hash is so that during the verification process, we have to use the same salt as we did originally. So we must store this somewhere. Even if the salt is compromised, it’s not a security issue since the attacker would still need to know / guess the user’s password to be able to generate the same hash. +### Example -Let’s have a look at how the verification process happens: +![Password Hashing Procedure](./hashing-flow.png) -- Alice’s password: `"abcdef"` (Incorrect password) -- Alice’s salt: `"ab$45"` (fetched from the db) -- Alice’s modified password: `"abcdefab$45"` -- Alice’s SHA256 hash: `"c5110931a3ae4762c1c0334d8eeba8c9c555962cf7d2750fdd732936319a058c"` -- Alice’s hash + salt: `"c5110931a3ae4762c1c0334d8eeba8c9c555962cf7d2750fdd732936319a058c.ab$45"` +Consider a user with the password "password123": +1. A salt, say "s@1tValue!", is generated. +2. The password "password123" is combined with the salt to form "password123s@1tValue!". +3. This combined string is hashed using a secure algorithm, producing a hash like "5d41402abc4b2a76b9719d911017c592". +4. Both the salt and the hash are stored in the database. +5. During login, the system combines the entered password with the stored salt and hashes it. If the result matches the stored hash, the login is successful. -Since the computed hash + salt doesn’t match the one in the database, we reject this password. If Alice was to enter her correct password (`"12345"`), it would indeed generate the same hash + salt as the one in the database, verifying her identity. +By following these steps, developers can ensure that user passwords are stored securely and remain protected even if the database is compromised. +#### What is Salting and Why Hashing Alone is Not Good Enough - Problems with Humans -## Which hash function to choose? -Even after salting, the issue of brute force attacks still remain. An attacker could repeatedly guess different passwords (very quickly) to see which one matches the leaked hash. There are two dimensions that determine how quickly an attacker can find a match: -1) The randomness and length of the user’s password. -2) The time it takes for the hash function to compute the hash +**Salting**: Salting is a technique used to enhance the security of password hashing. It involves adding a unique, random string of characters (salt) to each password before hashing it. This ensures that even identical passwords result in different hashes, preventing hackers from using precomputed tables (rainbow tables) to crack hashes. -If a user uses a random and long enough password, the chances of the attacker guessing that exact string reduces. This means they have to crunch through more guesses which will take more time. [Here is a really cool tool](https://www.passwordmonster.com/) which estimates how much time it would take to guess a given password. +**Problems with Humans**: Humans are prone to using common passwords that are easily guessable. Without salting, identical passwords would produce identical hashes, making it easier for hackers to crack them using precomputed tables known as rainbow tables. Salting mitigates this risk by making each hash unique, even for identical passwords. -The slower and more computationally expensive the hashing function, the more time it would take to validate each guess. As of this writing (2nd March, 2022), the recommended hashing technique is to use [Argon2id](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id) with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism[^3]. +#### Example -As computational power increases, recommended hashing techniques change as well. Even if the algorithm remains the same, the recommended number of “rounds” / amount of “work” that should be done per password hash may increase. +Without salt: +- Password: "12345" +- Hash: "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5" +With salt: +- Password: "12345" +- Salt: "ab$45" +- Hash: "2bb12bb768eb669f0e4b9df29e22a00467eb513c275ccfff1013288facac7889.ab$45" -## Example code +## Which Hash Function to Choose? -### NodeJS -```js -import * as argon2 from "argon2"; -import * as crypto from "crypto"; - -const hashingConfig = { // based on OWASP cheat sheet recommendations (as of March, 2022) - parallelism: 1, - memoryCost: 64000, // 64 mb - timeCost: 3 // number of itetations -} - -async function hashPassword(password: string) { - let salt = crypto.randomBytes(16); - return await argon2.hash(password, { - ...hashingConfig, - salt, - }) -} - -async function verifyPasswordWithHash(password: string, hash: string) { - return await argon2.verify(hash, password, hashingConfig); -} - -hashPassword("somePassword").then(async (hash) => { - console.log("Hash + salt of the password:", hash) - console.log("Password verification success:", await verifyPasswordWithHash("somePassword", hash)); -}); -``` +Choosing the right hash function is crucial for ensuring the security and performance of your application. Different hash functions offer varying levels of security, speed, and resistance to attacks. Here’s a detailed look at some popular hash functions and considerations for selecting the best one for your needs: -The above produces the following output: -```bash -Hash + salt of the password: $argon2i$v=19$m=15000,t=3,p=1$tgSmiYOCjQ0im5U6NXEvPg$xKC4V31JqIK2XO91fnMCfevATq1rVDjIRX0cf/dnbKY - -Password verification success: true -``` +### SHA-256 -> If you run the above program, it will produce a different hash each time since the salt is regenerated each time. - -### GoLang -```go -package main - -import ( - "crypto/rand" - "crypto/subtle" - "encoding/base64" - "errors" - "fmt" - "log" - "strings" - - "golang.org/x/crypto/argon2" -) - -type params struct { - memory uint32 - iterations uint32 - parallelism uint8 - saltLength uint32 - keyLength uint32 -} - -func main() { - p := ¶ms{ - memory: 64 * 1024, // 64 MB - iterations: 3, - parallelism: 1, - saltLength: 16, - keyLength: 32, - } +- **Overview**: SHA-256 is part of the SHA-2 family of cryptographic hash functions, designed by the United States’ National Security Agency (NSA). It generates a 256-bit message digest and is widely used across many applications, including SSL/TLS certificates, digital signatures, and blockchain. +- **Pros**: + - Security: SHA-256 is currently considered secure and has not been compromised. + - Performance: It is relatively fast, making it suitable for applications where speed is a priority. +- **Cons**: + - Speed: Its speed can be a disadvantage for password hashing because faster hashes are easier to brute-force. + - Lack of Built-in Salt: SHA-256 does not include a built-in mechanism for salting, requiring developers to implement this separately. +- **Use Cases**: + - Verifying data integrity + - SSL/TLS certificates + - Blockchain applications - encodedHash, err := generateHashFromPassword("somePassword", p) - if err != nil { - log.Fatal(err) - } +### bcrypt - fmt.Println("Hash + salt of the password:") - fmt.Println(encodedHash) +- Overview: bcrypt is a password hashing function designed specifically for securing passwords. It incorporates a salt to protect against rainbow table attacks and is intentionally slow to make brute-force attacks more difficult. +- Pros: + - Security: bcrypt includes a salt and is designed to be computationally intensive. + - Adjustable Work Factor: The work factor (or cost factor) can be increased over time to maintain security as hardware improves. +- Cons: + - Performance: While its slowness enhances security, it can be a drawback for systems requiring high-speed password verification, though in a modern stack this shouldn’t be an issue at all (~100ms for a hash verification could be slow for let’s say a close-to-real-time API). +- Use Cases: + - Password hashing + - User authentication systems - match, err := verifyPassword("somePassword", encodedHash) - if err != nil { - log.Fatal(err) - } +### PBKDF2 +- **Overview**: PBKDF2 (Password-Based Key Derivation Function 2) is a key derivation function that applies a pseudorandom function (e.g., HMAC) to the input password along with a salt value and repeats the process many times. +- **Pros**: + - **Security**: PBKDF2 is well-established and widely used. + - **Configurability**: The iteration count can be increased to improve security. +- **Cons**: + - **Performance**: Higher iteration counts can significantly impact performance. + - **Complexity**: Properly configuring PBKDF2 for optimal security and performance can be challenging. +- **Use Cases**: + - Password hashing + - Key derivation for cryptographic keys - fmt.Printf("\nPassword verification success: %v\n", match) -} - -func generateHashFromPassword(password string, p *params) (encodedHash string, err error) { - salt, err := generateRandomBytes(p.saltLength) - if err != nil { - return "", err - } +## Choosing the Right Hash Function - hash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength) +**Security Considerations** - // Base64 encode the salt and hashed password. - b64Salt := base64.RawStdEncoding.EncodeToString(salt) - b64Hash := base64.RawStdEncoding.EncodeToString(hash) +- **Algorithm Strength**: Choose an algorithm with no known vulnerabilities. bcrypt and Argon2 are specifically designed for password hashing and offer strong security features. +- **Resistance to Attacks**: Ensure the hash function is resistant to known attacks, including brute-force, rainbow table, and side-channel attacks. - // Return a string using the standard encoded hash representation. - encodedHash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, b64Salt, b64Hash) +P**erformance Considerations** +- Computational Cost: The hash function should be computationally expensive to thwart brute-force attacks but not so slow that it impacts user experience. +- Scalability: Consider the impact on system performance, especially if your application needs to handle a large number of authentication requests. - return encodedHash, nil -} - -func generateRandomBytes(n uint32) ([]byte, error) { - b := make([]byte, n) - _, err := rand.Read(b) - if err != nil { - return nil, err - } +**Future-Proofing** - return b, nil -} - -func verifyPassword(password, encodedHash string) (match bool, err error) { - // Extract the parameters, salt and derived key from the encoded password - // hash. - p, salt, hash, err := decodeHash(encodedHash) - if err != nil { - return false, err - } +- **Adjustable Parameters**: Opt for algorithms like bcrypt and Argon2, which allow you to adjust parameters (such as the work factor or memory usage) to maintain security as hardware capabilities improve. +- **Community and Industry Adoption**: Use widely adopted algorithms with strong community support and regular updates. Argon2, as the latest and most secure, is highly recommended for new applications. - // Derive the key from the other password using the same parameters. - otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength) - // Check that the contents of the hashed passwords are identical. Note - // that we are using the subtle.ConstantTimeCompare() function for this - // to help prevent timing attacks. - if subtle.ConstantTimeCompare(hash, otherHash) == 1 { - return true, nil - } - return false, nil -} - -func decodeHash(encodedHash string) (p *params, salt, hash []byte, err error) { - vals := strings.Split(encodedHash, "$") - if len(vals) != 6 { - return nil, nil, nil, errors.New("the encoded hash is not in the correct format") - } +**Recommended Configurations** +- **Argon2id** + - **Memory**: At least 15 MiB + - **Time**: An iteration count of 2 + - **Parallelism**: 1 degree of parallelism +- **bcrypt** + - **Cost Factor**: At least 12 (can be adjusted based on system performance and security requirements) +- **PBKDF2** + - **Iterations**: At least 100,000 (can be increased for higher security) - var version int - _, err = fmt.Sscanf(vals[2], "v=%d", &version) - if err != nil { - return nil, nil, nil, err - } - if version != argon2.Version { - return nil, nil, nil, errors.New("incompatible version of argon2") - } +Example Codes - p = ¶ms{} - _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism) - if err != nil { - return nil, nil, nil, err - } +**Node.js** - salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4]) - if err != nil { - return nil, nil, nil, err - } - p.saltLength = uint32(len(salt)) +```typescript +const hashingConfig = { + parallelism: 1, + memoryCost: 64000, // 64 MB + timeCost: 3 // number of iterations +} - hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5]) - if err != nil { - return nil, nil, nil, err - } - p.keyLength = uint32(len(hash)) +async function hashPassword(password) { + let salt = crypto.randomBytes(16); + return await argon2.hash(password, { + ...hashingConfig, + salt, + }); +} - return p, salt, hash, nil +async function verifyPassword(password, hash) { + return await argon2.verify(hash, password); } + +hashPassword("examplePassword").then(async (hash) => { + console.log("Hash + salt of the password:", hash); + console.log("Password verification success:", await verifyPassword("examplePassword", hash)); +}); ``` -### Python ```python import argon2 argon2Hasher = argon2.PasswordHasher( - time_cost=3, # number of iterations - memory_cost=64 * 1024, # 64mb - parallelism=1, # how many parallel threads to use - hash_len=32, # the size of the derived key - salt_len=16 # the size of the random generated salt in bytes + time_cost=3, + memory_cost=64 * 1024, + parallelism=1, + hash_len=32, + salt_len=16 ) - -password = "somePassword" - +password = "examplePassword" hash = argon2Hasher.hash(password) - + print("Hash + salt of password", hash) - -verifyValid = argon2Hasher.verify(hash, password) -print("Password verification success:", verifyValid) + +verify_valid = argon2Hasher.verify(hash, password) +print("Password verification success:", verify_valid) ``` -### Java ```java import de.mkammerer.argon2.Argon2; import de.mkammerer.argon2.Argon2Factory; - + public class PasswordHashing { public static void main(String[] args) { - // salt 32 bytes - // Hash length 64 bytes Argon2 argon2 = Argon2Factory.create( Argon2Factory.Argon2Types.ARGON2id, 16, 32); - char[] password = "somePassword".toCharArray(); - String hash = argon2.hash(3, // Number of iterations - 64 * 1024, // 64mb - 1, // how many parallel threads to use + char[] password = "examplePassword".toCharArray(); + String hash = argon2.hash(3, + 64 * 1024, + 1, password); System.out.println("Hash + salt of the password: "+hash); System.out.println("Password verification success: "+ argon2.verify(hash, password)); @@ -307,9 +233,9 @@ public class PasswordHashing { } ``` +## Footnotes -[^1]: Unless they are stored in a "secure vault" like this one. But then too, it’s still possible that they get leaked. - -[^2]: Technically, the users password could be anything that, when hashed, results in `"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"`. But if we try and login with the password as `"12345"`, it would work since the algorithm is just matching the computed hash against the one in the database. - -[^3]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html +- Secure vaults can help protect encryption keys, but there is always a risk of compromise. +- The same password can produce the same hash, making it vulnerable to attacks without proper salting. +- Hashing, salting, and usually anything related to complex cryptography isn’t something you should make yourself. To ensure maximum security, please use battle-tested, and scientifically proven methods, like algorithms mentioned above. +- Refer to the OWASP Password Storage Cheat Sheet for the latest recommendations. \ No newline at end of file From fc9f4b64ea9265730b85a137369f07b31e4e8e2a Mon Sep 17 00:00:00 2001 From: Joel Coutinho Date: Thu, 25 Jul 2024 19:44:36 +0530 Subject: [PATCH 2/2] updates author creds --- content/password-hashing-salting/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/password-hashing-salting/index.md b/content/password-hashing-salting/index.md index 31a7da45..987dafe3 100644 --- a/content/password-hashing-salting/index.md +++ b/content/password-hashing-salting/index.md @@ -4,7 +4,7 @@ date: "2024-06-25" description: "A guide on password hashing and salting in different languages and why it's important to do so" cover: "password_hashing_and_salting.png" category: "programming" -author: "Rishabh Poddar" +author: "Dejan Lukic" --- ## Table of Contents