diff --git a/src/basics.md b/src/basics.md
index ebe4e68..a2e9692 100644
--- a/src/basics.md
+++ b/src/basics.md
@@ -20,6 +20,7 @@
| [Extract phone numbers from text][ex-phone] | [![regex-badge]][regex] | [![cat-text-processing-badge]][cat-text-processing] |
| [Calculate the SHA-256 digest of a file][ex-sha-digest] | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] | [![cat-cryptography-badge]][cat-cryptography] |
| [Sign and verify a message with an HMAC digest][ex-hmac] | [![ring-badge]][ring] | [![cat-cryptography-badge]][cat-cryptography] |
+| [Salt and hash a password with PBKDF2][ex-pbkdf2] | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] | [![cat-cryptography-badge]][cat-cryptography] |
| [Define and operate on a type represented as a bitfield][ex-bitflags] | [![bitflags-badge]][bitflags] | [![cat-no-std-badge]][cat-no-std] |
| [Access a file randomly using a memory map][ex-random-file-access] | [![memmap-badge]][memmap] | [![cat-filesystem-badge]][cat-filesystem] |
| [Check number of logical cpu cores][ex-check-cpu-cores] | [![num_cpus-badge]][num_cpus] | [![cat-hardware-support-badge]][cat-hardware-support] |
@@ -900,6 +901,82 @@ fn run() -> Result<()> {
# quick_main!(run);
```
+[ex-pbkdf2]: #ex-pbkdf2
+
+## Salt and hash a password with PBKDF2
+
+[![ring-badge]][ring] [![data-encoding-badge]][data-encoding] [![cat-cryptography-badge]][cat-cryptography]
+
+Uses [`ring::pbkdf2`] to hash a salted password using the PBKDF2 key derivation
+function [`pbkdf2::derive`] and then verifies the hash is correct with
+[`pbkdf2::verify`]. The salt is generated using
+[`SecureRandom::fill`], which fills the salt byte array with
+securely generated random numbers.
+
+```rust
+# #[macro_use]
+# extern crate error_chain;
+extern crate data_encoding;
+extern crate ring;
+#
+# error_chain! {
+# foreign_links {
+# Ring(ring::error::Unspecified);
+# }
+# }
+
+use data_encoding::HEXUPPER;
+use ring::{digest, pbkdf2, rand};
+use ring::rand::SecureRandom;
+
+fn run() -> Result<()> {
+ const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
+ const N_ITER: u32 = 100_000;
+ let rng = rand::SystemRandom::new();
+
+ // Generate salt
+ let mut salt = [0u8; CREDENTIAL_LEN];
+ rng.fill(&mut salt)?;
+
+ // Hash password
+ let password = "Guess Me If You Can!";
+ let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN];
+ pbkdf2::derive(
+ &digest::SHA512,
+ N_ITER,
+ &salt,
+ password.as_bytes(),
+ &mut pbkdf2_hash,
+ );
+ println!("Salt: {}", HEXUPPER.encode(&salt));
+ println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash));
+
+ // Verify hash with correct password and wrong password
+ let should_succeed = pbkdf2::verify(
+ &digest::SHA512,
+ N_ITER,
+ &salt,
+ password.as_bytes(),
+ &pbkdf2_hash,
+ );
+ let wrong_password = "Definitely not the correct password";
+ let should_fail = pbkdf2::verify(
+ &digest::SHA512,
+ N_ITER,
+ &salt,
+ wrong_password.as_bytes(),
+ &pbkdf2_hash,
+ );
+
+ assert!(should_succeed.is_ok());
+ assert!(!should_fail.is_ok());
+
+ Ok(())
+}
+#
+# quick_main!(run);
+```
+
[ex-bitflags]: #ex-bitflags
## Define and operate on a type represented as a bitfield
@@ -1050,6 +1127,8 @@ fn main() {
[`Normal`]: https://doc.rust-lang.org/rand/rand/distributions/normal/struct.Normal.html
[`num_cpus::get`]: https://docs.rs/num_cpus/*/num_cpus/fn.get.html
[`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
+[`pbkdf2::derive`]: https://docs.rs/ring/*/ring/pbkdf2/fn.derive.html
+[`pbkdf2::verify`]: https://docs.rs/ring/*/ring/pbkdf2/fn.verify.html
[`rand::Rand`]: https://doc.rust-lang.org/rand/rand/trait.Rand.html
[`rand::Rand`]: https://doc.rust-lang.org/rand/rand/trait.Rand.html
[`rand::Rng`]: https://doc.rust-lang.org/rand/rand/trait.Rng.html
@@ -1061,8 +1140,10 @@ fn main() {
[`regex::RegexSetBuilder`]: https://doc.rust-lang.org/regex/regex/struct.RegexSetBuilder.html
[`Regex::replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all
[`Regex`]: https://doc.rust-lang.org/regex/regex/struct.Regex.html
+[`ring::pbkdf2`]: https://docs.rs/ring/*/ring/pbkdf2/index.html
[`Rng::gen_range`]: https://doc.rust-lang.org/rand/rand/trait.Rng.html#method.gen_range
[`RwLock`]: https://doc.rust-lang.org/std/sync/struct.RwLock.html
+[`SecureRandom::fill`]: https://docs.rs/ring/*/ring/rand/trait.SecureRandom.html#tymethod.fill
[`seek`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.seek
[`Stdio::piped`]: https://doc.rust-lang.org/std/process/struct.Stdio.html
[rand-distributions]: https://doc.rust-lang.org/rand/rand/distributions/index.html
diff --git a/src/intro.md b/src/intro.md
index 96e7e0c..5e8fecc 100644
--- a/src/intro.md
+++ b/src/intro.md
@@ -38,6 +38,7 @@ community. It needs and welcomes help. For details see
| [Extract phone numbers from text][ex-phone] | [![regex-badge]][regex] | [![cat-text-processing-badge]][cat-text-processing] |
| [Calculate the SHA-256 digest of a file][ex-sha-digest] | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] | [![cat-cryptography-badge]][cat-cryptography] |
| [Sign and verify a message with an HMAC digest][ex-hmac] | [![ring-badge]][ring] | [![cat-cryptography-badge]][cat-cryptography] |
+| [Salt and hash a password with PBKDF2][ex-pbkdf2] | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] | [![cat-cryptography-badge]][cat-cryptography] |
| [Define and operate on a type represented as a bitfield][ex-bitflags] | [![bitflags-badge]][bitflags] | [![cat-no-std-badge]][cat-no-std] |
| [Access a file randomly using a memory map][ex-random-file-access] | [![memmap-badge]][memmap] | [![cat-filesystem-badge]][cat-filesystem] |
| [Check number of logical cpu cores][ex-check-cpu-cores] | [![num_cpus-badge]][num_cpus] | [![cat-hardware-support-badge]][cat-hardware-support] |
@@ -182,6 +183,7 @@ community. It needs and welcomes help. For details see
[ex-paginated-api]: net.html#ex-paginated-api
[ex-parse-subprocess-output]: basics.html#ex-parse-subprocess-output
[ex-parse-subprocess-input]: basics.html#ex-parse-subprocess-input
+[ex-pbkdf2]: basics.html#ex-pbkdf2
[ex-run-piped-external-commands]: basics.html#ex-run-piped-external-commands
[ex-verify-extract-email]: basics.html#ex-verify-extract-email
[ex-percent-encode]: encoding.html#ex-percent-encode