├── src ├── glossary.md ├── signing │ └── README.md ├── hashing-data │ └── README.md ├── encryption-asymmetric │ └── README.md ├── encryption-symmetric │ └── README.md ├── message-authentication │ └── README.md ├── README.md ├── SUMMARY.md └── key-derivation │ ├── README.md │ └── hashing-password.md ├── test-book ├── src │ └── main.rs ├── tests │ ├── skeptic.rs │ └── check_glossary_sort.rs ├── build.rs ├── Cargo.toml └── Cargo.lock ├── pre-commit ├── book.toml ├── .gitignore ├── README.md ├── .github └── workflows │ └── ci.yaml └── LICENSE /src/glossary.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | -------------------------------------------------------------------------------- /src/signing/README.md: -------------------------------------------------------------------------------- 1 | # Digital signing 2 | -------------------------------------------------------------------------------- /src/hashing-data/README.md: -------------------------------------------------------------------------------- 1 | # Data Hashing 2 | -------------------------------------------------------------------------------- /src/encryption-asymmetric/README.md: -------------------------------------------------------------------------------- 1 | # Asymmetric Encryption 2 | -------------------------------------------------------------------------------- /src/encryption-symmetric/README.md: -------------------------------------------------------------------------------- 1 | # Symmetric Encryption 2 | -------------------------------------------------------------------------------- /test-book/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /src/message-authentication/README.md: -------------------------------------------------------------------------------- 1 | # Hash-Based Message Authentication (HMAC) 2 | -------------------------------------------------------------------------------- /test-book/tests/skeptic.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs")); 2 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The [RustCrypto] organization provides pure Rust implementations of various cryptographic 4 | algorithms. 5 | 6 | This book is a work in progress. 7 | 8 | [rustcrypto]: https://github.com/RustCrypto/ 9 | -------------------------------------------------------------------------------- /pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # check mdbook 4 | mdbook build 5 | 6 | # apply formatting 7 | mdformat -h > /dev/null || echo 'You may need to install mdformat: `pip install mdformat-gfm`' 8 | mdformat --wrap 100 . 9 | 10 | cargo fmt --manifest-path=test-book/Cargo.toml 11 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["RustCrypto Organization"] 3 | language = "en" 4 | multilingual = false 5 | title = "The RustCrypto Book" 6 | description = "Reference manual for the RustCrypto project" 7 | 8 | [rust] 9 | edition = "2021" 10 | 11 | # [output.html.playground] 12 | # editable = true 13 | -------------------------------------------------------------------------------- /test-book/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Generate tests for all markdown files 3 | let mdbook_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/"); 4 | let readme_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../README.md"); 5 | let mut mdbook_files = skeptic::markdown_files_of_directory(mdbook_path); 6 | mdbook_files.push(readme_path.into()); 7 | skeptic::generate_doc_tests(&mdbook_files); 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf 17 | 18 | # Book build output 19 | book/ 20 | 21 | # Cargo output 22 | **/target/ 23 | -------------------------------------------------------------------------------- /test-book/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-book" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Test runner for mdbook" 6 | 7 | [dev-dependencies] 8 | skeptic = "0.13.7" 9 | 10 | [build-dependencies] 11 | skeptic = "0.13.7" 12 | 13 | [dependencies] 14 | argon2 = "0.5.2" 15 | data-encoding = "2.4.0" 16 | hmac = "0.12.1" 17 | password-hash = "0.5.0" 18 | pbkdf2 = { version = "0.12.2", features = ["simple"] } 19 | scrypt = { version = "0.11.0", features = ["simple"] } 20 | sha2 = "0.10.8" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The RustCrypto Book 2 | 3 | Reference manual for the RustCrypto project, implemented as a MDBook \[WIP\]. 4 | 5 | You can read the book at . 6 | 7 | ## Contributing 8 | 9 | Contributions are welcome! You will need [`mdbook`] to build the book and \[`mdformat-gfn`\] to 10 | check formatting. 11 | 12 | If you would like to use the pre-commit hook, just simlink it: 13 | `ln -s "$(pwd)/pre-commit" .git/hooks/`. 14 | 15 | [`mdbook`]: https://rust-lang.github.io/mdBook/guide/installation.html 16 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | 5 | - [Data Hashing](hashing-data/README.md) 6 | - [Key Derivation](key-derivation/README.md) 7 | - [Password Hashing](key-derivation/hashing-password.md) 8 | - [Message Authentication](message-authentication/README.md) 9 | - [Digital Signing](signing/README.md) 10 | - [Asymmetric Encryption](encryption-asymmetric/README.md) 11 | - [Symmetric Encryption](encryption-symmetric/README.md) 12 | 13 | ______________________________________________________________________ 14 | 15 | - [Glossary](glossary.md) 16 | -------------------------------------------------------------------------------- /src/key-derivation/README.md: -------------------------------------------------------------------------------- 1 | # Key Derivation Functions 2 | 3 | [Key derivation functions] (KDFs) provide a way to take an input such as a password, and create an 4 | output of specified length. This output can then be used, for example, as the key for an encryption 5 | algorithm. 6 | 7 | Password hashing functions are a related concept to key derivation functions; see 8 | [Password Hashing](hashing-password.md) for more information on those. 9 | 10 | 11 | 12 | [key derivation functions]: https://en.wikipedia.org/wiki/Key_derivation_function 13 | -------------------------------------------------------------------------------- /test-book/tests/check_glossary_sort.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | #[test] 4 | fn check_glossary_sort() { 5 | // Make sure glossary headings are in order 6 | let glossary_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/glossary.md"); 7 | let text = std::fs::read_to_string(glossary_path).unwrap(); 8 | 9 | let mut linenums = vec![]; 10 | let mut headings = vec![]; 11 | text.lines() 12 | .enumerate() 13 | .filter(|(_, line)| line.starts_with("## ")) 14 | .for_each(|(idx, line)| { 15 | linenums.push(idx); 16 | headings.push(line.strip_prefix("## ").unwrap()); 17 | }); 18 | 19 | let unsorted = headings.clone(); 20 | headings.sort_unstable(); 21 | let longest = headings.iter().max_by_key(|h| h.len()).unwrap_or(&"").len(); 22 | 23 | if !(unsorted == headings) { 24 | let mut msg = format!( 25 | "glossary headings are not currently sorted\n\n\ 26 | line {:17, block size of 8, and 1 degree of 22 | parallelism 23 | 1. Bcrypt with a minimum work factor of 10 and a password limit of 72 bytes 24 | 1. If FIPS-140 compliance is required, PBKDF2 with a minimum work factor of 600,000 and a SHA-256 25 | internal hash function 26 | 27 | You don't need to worry about setting these parameters; the default implementations match the 28 | recommendations. 29 | 30 | ```rust,noplaypen 31 | # fn main() { 32 | use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::Salt}; 33 | 34 | let password = "password"; 35 | 36 | // This is the b64 hash of "bad salt!" for demo only: don't do this! Instead use: 37 | // let salt = SaltString::generate(&mut OsRng); 38 | let salt_str = "YmFkIHNhbHQh"; 39 | let salt: Salt = salt_str.try_into().unwrap(); 40 | 41 | let argon2 = Argon2::default(); 42 | let hash = argon2.hash_password(password.as_bytes(), salt).unwrap(); 43 | 44 | // This is the hash we will store. Notice our salt string is included, as well as parameters: 45 | // version 0x13 (19), memory 19456KiB (19 MiB), 2 iterations (time), parallelism 1 46 | let expected = 47 | "$argon2id$v=19$m=19456,t=2,p=1$YmFkIHNhbHQh$DqHGwv6NQV0VcaJi7jeF1E8IpfMXmXcpq4r2kKyqpXk"; 48 | // ^ hash ^ parameters ^ salt ^ combined hash 49 | 50 | assert_eq!(expected, hash.to_string()); 51 | 52 | // The verifier reads the salt and the parameters from the hash and verifies the result is equal 53 | Argon2::default().verify_password(password.as_bytes(), &hash).expect("invalid password"); 54 | # } 55 | ``` 56 | 57 | Sometimes it may be necessary to support the verification of passwords that were hashed with 58 | different algorithms or parameters. This can be done using the traits in `password_hash`: 59 | 60 | ```rust,noplaypen 61 | # fn main() { 62 | use password_hash::{PasswordHash, PasswordVerifier}; 63 | 64 | use argon2::Argon2; 65 | use pbkdf2::Pbkdf2; 66 | use scrypt::Scrypt; 67 | 68 | // Can be: `$argon2`, `$pbkdf2`, or `$scrypt` 69 | let hash_string = 70 | "$argon2id$v=19$m=19456,t=2,p=1$YmFkIHNhbHQh$DqHGwv6NQV0VcaJi7jeF1E8IpfMXmXcpq4r2kKyqpXk"; 71 | let input_password = "password"; 72 | 73 | let password_hash = PasswordHash::new(&hash_string).expect("invalid password hash"); 74 | 75 | // Trait objects for algorithms to support 76 | let algs: &[&dyn PasswordVerifier] = &[&Argon2::default(), &Pbkdf2, &Scrypt]; 77 | 78 | password_hash.verify_password(algs, input_password).expect("invalid password"); 79 | # } 80 | ``` 81 | 82 | ## Differences with Standard Hash Functions 83 | 84 | Compared with [standard hash functions](hashing-data/README.md), password hashes have a few features 85 | that make them well suited to this purpose: 86 | 87 | ### Salt 88 | 89 | "Salt" is random data that gets concatenated with the password before going through the hash 90 | function. This data needs to be different for each password hashed, but gets stored with the output 91 | hash and reused during verification. 92 | 93 | This roughly looks like the following: 94 | 95 | ``` 96 | storage: 97 | 98 | per-entry random salt -\ 99 | \ 100 | plaintext password -> argon2 -> database entry 101 | 102 | 103 | verification: 104 | 105 | /-- hash ---------------------------\ 106 | database entry -| argon2 -> test hash -> compare 107 | \ / / 108 | \-- salt --/ / 109 | / 110 | test password ----------------/ 111 | 112 | ``` 113 | 114 | See [Why Salt?](#appendix-why-salt) in the appendix for an illustration of why this is necessary. 115 | 116 | ### Slowness 117 | 118 | While hashes like SHA-256 are meant to be fast, password hashes are meant to be slow. This is to 119 | help slow down brute forcing, where an attacker tries hashing a wide variety of common passwords 120 | with a known salt to try to compute a known hash. 121 | 122 | As mentioned in [What should I use?], password hashing algorithms have parameters that can control 123 | how slow or fast they are to solve. The defaults recommended by OWASP provide a good balance of high 124 | security without being nonperformant. 125 | 126 | ### A Note on Pepper 127 | 128 | Peppering is the process of encrypting hashed passwords with a secret key before storing in the 129 | database. For example: 130 | 131 | ``` 132 | storage: 133 | 134 | per-entry random salt -\ 135 | \ 136 | plaintext password -> argon2 -> aes256-gcm encrypt -> database entry 137 | / 138 | per-database secret key --------/ 139 | 140 | 141 | verification: 142 | 143 | per-database secret key ----\ 144 | \ /-- hash ---------------------------\ 145 | database entry -> aes256-gem decrypt -| argon2 -> test hash -> compare 146 | \ / / 147 | \-- salt --/ / 148 | / 149 | test password --------------------------------------/ 150 | 151 | ``` 152 | 153 | OWSAP now suggests this process since it means that even if an attacker has access to the database 154 | table, they cannot do anything with it unless they also have the encryption key (stored separately). 155 | 156 | Peppering is not considered part of password hashing functions. 157 | 158 | ## Appendix: Why Salt? 159 | 160 | What purpose does salting passwords solve? To start, imagine that we use SHA-256 to hash our 161 | password: 162 | 163 | > **WARNING**: DO NOT use SHA-256 as a password hashing algorithm. We use it in this example only 164 | > because it is easy to understand. See the [What should I use?] section at the top of this page for 165 | > better options. 166 | 167 | ```rust,editable 168 | # fn main() { 169 | # use sha2::{Sha256, Digest}; 170 | # use data_encoding::HEXLOWER; 171 | let input = "password"; 172 | 173 | let mut hash = Sha256::new(); 174 | hash.update(input.as_bytes()); 175 | let result = hash.finalize(); 176 | 177 | println!("Hash: {}", HEXLOWER.encode(&result)); 178 | # } 179 | ``` 180 | 181 | This prints `5e884898da...`, which we would store in our database. 182 | 183 | Now some security event happens, and our database with all password hashes gets leaked. Now, the 184 | public has this information: 185 | 186 | ``` 187 | +----------+------------+ 188 | | username | pw_sha256 | 189 | +----------+------------+ 190 | | ferris | 5e884898da | <- note this hash 191 | | curie | 437dd76609 | 192 | | turing | fc3a03a63b | 193 | +----------+------------+ 194 | ``` 195 | 196 | An attacker is prepared, and has precalculated a table of common password hashes: 197 | 198 | ``` 199 | +----------+------------+ 200 | | input | sha_256 | 201 | +----------+------------+ 202 | | 1234 | 03ac674216 | 203 | | abcd | 84e73dc50f | 204 | | password | 5e884898da | <- note this hash 205 | +----------+------------+ 206 | ``` 207 | 208 | Bingo! user `ferris` has password hash `5e884898da`, which means the password `password` will work 209 | to access ferris's account. The user is now compromised! 210 | 211 | This is a simple version of a rainbow attack. To prevent this, we do what is called "salting". This 212 | means that we pick a random salt that we store with the password in the database, and hash that salt 213 | with the input password when we want to check it: 214 | 215 | ``` 216 | +----------+------------------------+ 217 | | username | pw | 218 | +----------+------------------------+ 219 | | ferris | $sha2$salty$d878e396b9 | # user's password is 'password' 220 | | curie | $sha2$spice$036ecfef69 | 221 | | turing | $sha2$spice$701f72c284 | 222 | | lovelace | $sha2$sugar$a92ec3b280 | # user's password is also 'password' 223 | +----------+------------------------+ 224 | ``` 225 | 226 | Notice how no hashes from the attacker's list appear in our new password table, and that even though 227 | two users have the same password, their hashes are different. This means that an attacker can no 228 | longer look up a hash directly, and instead has to waste time calculating each possible input! 229 | 230 | A salt function with SHA-256 would roughly look like: 231 | 232 | ```rust,editable 233 | # fn main() { 234 | # use sha2::{Sha256, Digest}; 235 | # use data_encoding::HEXLOWER; 236 | // Input from the user 237 | let input = "password"; 238 | 239 | // That user's salt, stored in the database 240 | let salt = "salty"; 241 | let to_hash = format!("{salt}{input}"); 242 | 243 | // Compute salt+input hash 244 | let mut hash = Sha256::new(); 245 | hash.update(to_hash.as_bytes()); 246 | let result = hash.finalize(); 247 | let digest = HEXLOWER.encode(&result); 248 | 249 | println!("Hash: {digest}"); 250 | 251 | // Hash of salt+password in the database 252 | let stored_value = "d878e396b9"; 253 | assert_eq!(stored_value, &digest[..10]); 254 | # } 255 | ``` 256 | 257 | The [`PasswordHasher`] trait makes sure that salt is always used when hashing passwords. 258 | 259 | [password hash functions]: https://en.wikipedia.org/wiki/Key_derivation_function#Password_hashing 260 | [password storage cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html 261 | [what should i use?]: #what-should-i-use 262 | [`password-hash`]: https://docs.rs/password-hash/latest/password_hash/ 263 | [`passwordhasher`]: https://docs.rs/password-hash/latest/password_hash/trait.PasswordHasher.html 264 | [`passwordverifier`]: https://docs.rs/password-hash/latest/password_hash/trait.PasswordVerifier.html 265 | -------------------------------------------------------------------------------- /test-book/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "argon2" 7 | version = "0.5.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" 10 | dependencies = [ 11 | "base64ct", 12 | "blake2", 13 | "cpufeatures", 14 | "password-hash", 15 | ] 16 | 17 | [[package]] 18 | name = "base64ct" 19 | version = "1.6.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "1.3.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "2.4.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 34 | 35 | [[package]] 36 | name = "blake2" 37 | version = "0.10.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 40 | dependencies = [ 41 | "digest", 42 | ] 43 | 44 | [[package]] 45 | name = "block-buffer" 46 | version = "0.10.4" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 49 | dependencies = [ 50 | "generic-array", 51 | ] 52 | 53 | [[package]] 54 | name = "bytecount" 55 | version = "0.6.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" 58 | 59 | [[package]] 60 | name = "camino" 61 | version = "1.1.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" 64 | dependencies = [ 65 | "serde", 66 | ] 67 | 68 | [[package]] 69 | name = "cargo-platform" 70 | version = "0.1.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" 73 | dependencies = [ 74 | "serde", 75 | ] 76 | 77 | [[package]] 78 | name = "cargo_metadata" 79 | version = "0.14.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" 82 | dependencies = [ 83 | "camino", 84 | "cargo-platform", 85 | "semver", 86 | "serde", 87 | "serde_json", 88 | ] 89 | 90 | [[package]] 91 | name = "cc" 92 | version = "1.0.83" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 95 | dependencies = [ 96 | "libc", 97 | ] 98 | 99 | [[package]] 100 | name = "cfg-if" 101 | version = "1.0.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 104 | 105 | [[package]] 106 | name = "cipher" 107 | version = "0.4.4" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 110 | dependencies = [ 111 | "crypto-common", 112 | "inout", 113 | ] 114 | 115 | [[package]] 116 | name = "cpufeatures" 117 | version = "0.2.9" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 120 | dependencies = [ 121 | "libc", 122 | ] 123 | 124 | [[package]] 125 | name = "crypto-common" 126 | version = "0.1.6" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 129 | dependencies = [ 130 | "generic-array", 131 | "typenum", 132 | ] 133 | 134 | [[package]] 135 | name = "data-encoding" 136 | version = "2.4.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" 139 | 140 | [[package]] 141 | name = "digest" 142 | version = "0.10.7" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 145 | dependencies = [ 146 | "block-buffer", 147 | "crypto-common", 148 | "subtle", 149 | ] 150 | 151 | [[package]] 152 | name = "errno" 153 | version = "0.3.4" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" 156 | dependencies = [ 157 | "errno-dragonfly", 158 | "libc", 159 | "windows-sys", 160 | ] 161 | 162 | [[package]] 163 | name = "errno-dragonfly" 164 | version = "0.1.2" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 167 | dependencies = [ 168 | "cc", 169 | "libc", 170 | ] 171 | 172 | [[package]] 173 | name = "error-chain" 174 | version = "0.12.4" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 177 | dependencies = [ 178 | "version_check", 179 | ] 180 | 181 | [[package]] 182 | name = "fastrand" 183 | version = "2.0.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 186 | 187 | [[package]] 188 | name = "generic-array" 189 | version = "0.14.7" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 192 | dependencies = [ 193 | "typenum", 194 | "version_check", 195 | ] 196 | 197 | [[package]] 198 | name = "getrandom" 199 | version = "0.2.10" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 202 | dependencies = [ 203 | "cfg-if", 204 | "libc", 205 | "wasi", 206 | ] 207 | 208 | [[package]] 209 | name = "glob" 210 | version = "0.3.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 213 | 214 | [[package]] 215 | name = "hmac" 216 | version = "0.12.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 219 | dependencies = [ 220 | "digest", 221 | ] 222 | 223 | [[package]] 224 | name = "inout" 225 | version = "0.1.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 228 | dependencies = [ 229 | "generic-array", 230 | ] 231 | 232 | [[package]] 233 | name = "itoa" 234 | version = "1.0.9" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 237 | 238 | [[package]] 239 | name = "libc" 240 | version = "0.2.148" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" 243 | 244 | [[package]] 245 | name = "linux-raw-sys" 246 | version = "0.4.8" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" 249 | 250 | [[package]] 251 | name = "memchr" 252 | version = "2.6.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 255 | 256 | [[package]] 257 | name = "password-hash" 258 | version = "0.5.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" 261 | dependencies = [ 262 | "base64ct", 263 | "rand_core", 264 | "subtle", 265 | ] 266 | 267 | [[package]] 268 | name = "pbkdf2" 269 | version = "0.12.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 272 | dependencies = [ 273 | "digest", 274 | "hmac", 275 | "password-hash", 276 | "sha2", 277 | ] 278 | 279 | [[package]] 280 | name = "proc-macro2" 281 | version = "1.0.67" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 284 | dependencies = [ 285 | "unicode-ident", 286 | ] 287 | 288 | [[package]] 289 | name = "pulldown-cmark" 290 | version = "0.9.3" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" 293 | dependencies = [ 294 | "bitflags 1.3.2", 295 | "memchr", 296 | "unicase", 297 | ] 298 | 299 | [[package]] 300 | name = "quote" 301 | version = "1.0.33" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 304 | dependencies = [ 305 | "proc-macro2", 306 | ] 307 | 308 | [[package]] 309 | name = "rand_core" 310 | version = "0.6.4" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 313 | dependencies = [ 314 | "getrandom", 315 | ] 316 | 317 | [[package]] 318 | name = "redox_syscall" 319 | version = "0.3.5" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 322 | dependencies = [ 323 | "bitflags 1.3.2", 324 | ] 325 | 326 | [[package]] 327 | name = "rustix" 328 | version = "0.38.15" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" 331 | dependencies = [ 332 | "bitflags 2.4.0", 333 | "errno", 334 | "libc", 335 | "linux-raw-sys", 336 | "windows-sys", 337 | ] 338 | 339 | [[package]] 340 | name = "ryu" 341 | version = "1.0.15" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 344 | 345 | [[package]] 346 | name = "salsa20" 347 | version = "0.10.2" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" 350 | dependencies = [ 351 | "cipher", 352 | ] 353 | 354 | [[package]] 355 | name = "same-file" 356 | version = "1.0.6" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 359 | dependencies = [ 360 | "winapi-util", 361 | ] 362 | 363 | [[package]] 364 | name = "scrypt" 365 | version = "0.11.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" 368 | dependencies = [ 369 | "password-hash", 370 | "pbkdf2", 371 | "salsa20", 372 | "sha2", 373 | ] 374 | 375 | [[package]] 376 | name = "semver" 377 | version = "1.0.19" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" 380 | dependencies = [ 381 | "serde", 382 | ] 383 | 384 | [[package]] 385 | name = "serde" 386 | version = "1.0.188" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 389 | dependencies = [ 390 | "serde_derive", 391 | ] 392 | 393 | [[package]] 394 | name = "serde_derive" 395 | version = "1.0.188" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 398 | dependencies = [ 399 | "proc-macro2", 400 | "quote", 401 | "syn", 402 | ] 403 | 404 | [[package]] 405 | name = "serde_json" 406 | version = "1.0.107" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 409 | dependencies = [ 410 | "itoa", 411 | "ryu", 412 | "serde", 413 | ] 414 | 415 | [[package]] 416 | name = "sha2" 417 | version = "0.10.8" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 420 | dependencies = [ 421 | "cfg-if", 422 | "cpufeatures", 423 | "digest", 424 | ] 425 | 426 | [[package]] 427 | name = "skeptic" 428 | version = "0.13.7" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 431 | dependencies = [ 432 | "bytecount", 433 | "cargo_metadata", 434 | "error-chain", 435 | "glob", 436 | "pulldown-cmark", 437 | "tempfile", 438 | "walkdir", 439 | ] 440 | 441 | [[package]] 442 | name = "subtle" 443 | version = "2.5.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 446 | 447 | [[package]] 448 | name = "syn" 449 | version = "2.0.37" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 452 | dependencies = [ 453 | "proc-macro2", 454 | "quote", 455 | "unicode-ident", 456 | ] 457 | 458 | [[package]] 459 | name = "tempfile" 460 | version = "3.8.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 463 | dependencies = [ 464 | "cfg-if", 465 | "fastrand", 466 | "redox_syscall", 467 | "rustix", 468 | "windows-sys", 469 | ] 470 | 471 | [[package]] 472 | name = "test-book" 473 | version = "0.1.0" 474 | dependencies = [ 475 | "argon2", 476 | "data-encoding", 477 | "hmac", 478 | "password-hash", 479 | "pbkdf2", 480 | "scrypt", 481 | "sha2", 482 | "skeptic", 483 | ] 484 | 485 | [[package]] 486 | name = "typenum" 487 | version = "1.17.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 490 | 491 | [[package]] 492 | name = "unicase" 493 | version = "2.7.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 496 | dependencies = [ 497 | "version_check", 498 | ] 499 | 500 | [[package]] 501 | name = "unicode-ident" 502 | version = "1.0.12" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 505 | 506 | [[package]] 507 | name = "version_check" 508 | version = "0.9.4" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 511 | 512 | [[package]] 513 | name = "walkdir" 514 | version = "2.4.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" 517 | dependencies = [ 518 | "same-file", 519 | "winapi-util", 520 | ] 521 | 522 | [[package]] 523 | name = "wasi" 524 | version = "0.11.0+wasi-snapshot-preview1" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 527 | 528 | [[package]] 529 | name = "winapi" 530 | version = "0.3.9" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 533 | dependencies = [ 534 | "winapi-i686-pc-windows-gnu", 535 | "winapi-x86_64-pc-windows-gnu", 536 | ] 537 | 538 | [[package]] 539 | name = "winapi-i686-pc-windows-gnu" 540 | version = "0.4.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 543 | 544 | [[package]] 545 | name = "winapi-util" 546 | version = "0.1.6" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 549 | dependencies = [ 550 | "winapi", 551 | ] 552 | 553 | [[package]] 554 | name = "winapi-x86_64-pc-windows-gnu" 555 | version = "0.4.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 558 | 559 | [[package]] 560 | name = "windows-sys" 561 | version = "0.48.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 564 | dependencies = [ 565 | "windows-targets", 566 | ] 567 | 568 | [[package]] 569 | name = "windows-targets" 570 | version = "0.48.5" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 573 | dependencies = [ 574 | "windows_aarch64_gnullvm", 575 | "windows_aarch64_msvc", 576 | "windows_i686_gnu", 577 | "windows_i686_msvc", 578 | "windows_x86_64_gnu", 579 | "windows_x86_64_gnullvm", 580 | "windows_x86_64_msvc", 581 | ] 582 | 583 | [[package]] 584 | name = "windows_aarch64_gnullvm" 585 | version = "0.48.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 588 | 589 | [[package]] 590 | name = "windows_aarch64_msvc" 591 | version = "0.48.5" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 594 | 595 | [[package]] 596 | name = "windows_i686_gnu" 597 | version = "0.48.5" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 600 | 601 | [[package]] 602 | name = "windows_i686_msvc" 603 | version = "0.48.5" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 606 | 607 | [[package]] 608 | name = "windows_x86_64_gnu" 609 | version = "0.48.5" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 612 | 613 | [[package]] 614 | name = "windows_x86_64_gnullvm" 615 | version = "0.48.5" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 618 | 619 | [[package]] 620 | name = "windows_x86_64_msvc" 621 | version = "0.48.5" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 624 | --------------------------------------------------------------------------------