From 4c0c710e5ee2359df904f135090a878779dffc64 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 00:33:49 +0000 Subject: [PATCH 01/13] feat(0.0.6): Added Password and Hash functions --- .vscode/settings.json | 3 + Cargo.toml | 4 +- README.md | 24 ++- examples/hash.rs | 51 +++++ examples/password.rs | 43 ++++ src/common/constant.rs | 26 +++ src/common/mod.rs | 4 + src/common/words.rs | 463 +++++++++++++++++++++++++++++++++++++++++ src/date.rs | 4 +- src/hash.rs | 110 ++++++++++ src/lib.rs | 13 +- src/password.rs | 128 ++++++++++++ src/random.rs | 173 ++++++++++++--- tests/hash.rs | 79 +++++++ tests/password.rs | 31 +++ 15 files changed, 1116 insertions(+), 40 deletions(-) create mode 100644 examples/hash.rs create mode 100644 examples/password.rs create mode 100644 src/common/constant.rs create mode 100644 src/common/mod.rs create mode 100644 src/common/words.rs create mode 100644 src/hash.rs create mode 100644 src/password.rs create mode 100644 tests/hash.rs create mode 100644 tests/password.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 2d036dc7..8f6b03e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,12 +2,15 @@ "cSpell.words": [ "aarch", "ABCDEFGHIJKLMNOP", + "ascii", "binutils", + "blake", "certutil", "congruential", "czvf", "datetime", "dylib", + "Funct", "github", "GITHUB", "gnubin", diff --git a/Cargo.toml b/Cargo.toml index 4cb9af3f..4fb68079 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,14 @@ readme = "README.md" repository = "https://github.com/sebastienrousseau/mini-functions.git" resolver = "2" # Enables the new Cargo resolution engine rust-version = "1.57.0" -version = "0.0.5" +version = "0.0.6" [badges] maintenance = { status = "actively-developed" } [dependencies] +blake3 = "1.3.3" +convert_case = "0.6.0" image = "0.24.5" qrcode = "0.12.0" time = "0.3.17" diff --git a/README.md b/README.md index 2644cb23..25a48034 100644 --- a/README.md +++ b/README.md @@ -59,20 +59,32 @@ integration into a variety of projects and applications. ## Installation đŸ“Ļ It takes just a few minutes to get up and running with `mini-functions`. -Check out our [documentation][0] for more information. +Please check out our [website][0] for more information. You can also +find our documentation on [docs.rs][9] and [lib.rs][10] and our +[crates.io][8] page. -Add the following to your `Cargo.toml` file: +Mini Functions comes with a set of examples that you can use to get +started. To run the examples, clone the repository and run the following +command: -```toml -[dependencies] -mini-functions = "0.0.5" +```shell +cargo run --example ``` ## Usage 📖 +To use `mini-functions` in your project, add the following to your +`Cargo.toml` file: + +```toml +[dependencies] +mini-functions = "0.0.6" +``` + Add the following to your `main.rs` file: ```rust +extern crate mini_functions; use mini_functions::*; ``` @@ -214,6 +226,6 @@ for their help and support. [crates-badge]: https://img.shields.io/crates/v/mini-functions.svg?style=for-the-badge 'Crates.io' [divider]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/elements/divider.svg "divider" [docs-badge]: https://img.shields.io/docsrs/mini-functions.svg?style=for-the-badge 'Docs.rs' -[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.5-orange.svg?style=for-the-badge 'Lib.rs' +[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.6-orange.svg?style=for-the-badge 'Lib.rs' [license-badge]: https://img.shields.io/crates/l/mini-functions.svg?style=for-the-badge 'License' [mwl]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/shields/made-with-love.svg "Made With Love" diff --git a/examples/hash.rs b/examples/hash.rs new file mode 100644 index 00000000..9747cdc1 --- /dev/null +++ b/examples/hash.rs @@ -0,0 +1,51 @@ +use mini_functions::hash::Hash; + +fn main() { + let mut hash = Hash::new(); + let mut default = Hash::default(); + let is_valid = hash.verify( + "7f2611ba158b6dcea4a69c229c303358c5e04493abeadee106a4bfa464d55787", + "password", + ); + hash.set_password("password"); + default.set_password("password"); + hash.set_hash("1.61803398874989484820"); + default.set_hash("1.61803398874989484820"); + + println!("đŸĻ€ Hash::new(): ✅ {}", hash); + println!("đŸĻ€ Hash::default(): ✅ {}", default); + println!("đŸĻ€ Hash::generate_hash(): ✅ {}", hash.generate_hash()); + if is_valid { + println!( + "đŸĻ€ Hash::verify(): ✅ {}", + hash.verify(hash.hash(), "password") + ); + } else { + println!("đŸĻ€ Hash::verify() : ❌ The password or hash is invalid."); + } + println!( + "đŸĻ€ Hash::set_password(): ✅ {}", + hash.password() == "password" + ); + println!( + "đŸĻ€ Hash::set_hash(): ✅ {}", + hash.hash() == "1.61803398874989484820" + ); + println!( + "đŸĻ€ Hash::password(): ✅ {}", + hash.password() == "password" + ); + println!( + "đŸĻ€ Hash::password_length(): ✅ {}", + hash.password_length() == 8 + ); + println!( + "đŸĻ€ Hash::hash(): ✅ {}", + hash.hash() == "1.61803398874989484820" + ); + println!( + "đŸĻ€ Hash::hash_length(): ✅ {}", + hash.hash_length() == 22 + ); + println!("đŸĻ€ Hash::entropy(): ✅ {}", hash.entropy() > 0.0); +} diff --git a/examples/password.rs b/examples/password.rs new file mode 100644 index 00000000..136e4ce8 --- /dev/null +++ b/examples/password.rs @@ -0,0 +1,43 @@ +use mini_functions::password::Password; + +fn main() { + let password = Password::new(3, "-"); + println!( + "đŸĻ€ Password::default(): ✅ {}", + Password::default() + ); + println!("đŸĻ€ Password::new(): ✅ {}", password); + println!( + "đŸĻ€ Password::passphrase(): ✅ {}", + password.passphrase() + ); + let mut password = Password::new(3, "-"); + println!("đŸĻ€ Password::set_passphrase"); + println!( + " 🔓 Original passphrase: ✅ {}", + password.passphrase() + ); + password.set_passphrase("M1n1Funct1()ns-N3wP@ssphr4s3-Ex@mpl3"); + println!( + " 🔐 Updated passphrase: ✅ {}", + password.passphrase() + ); + println!("đŸĻ€ Password::len(): ✅ {}", password.len()); + println!( + "đŸĻ€ Password::is_empty(): ✅ {}", + password.is_empty() + ); + println!("đŸĻ€ Password::hash(): ✅ {}", password.hash()); + println!( + "đŸĻ€ Password::password_length(): ✅ {}", + password.password_length() + ); + println!( + "đŸĻ€ Password::hash_length(): ✅ {}", + password.hash_length() + ); + println!( + "đŸĻ€ Password::entropy(): ✅ {}", + password.entropy() + ); +} diff --git a/src/common/constant.rs b/src/common/constant.rs new file mode 100644 index 00000000..438d3c0b --- /dev/null +++ b/src/common/constant.rs @@ -0,0 +1,26 @@ +// Copyright Š 2022-2023 Mini Functions. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/// Constant values used in the application. + +/// The CRATE constant contains the URL of the crate. +// pub const CRATE: &str = "https://crates.io/crates/mini-functions"; + +/// The DOCUMENTATION constant contains the URL of the documentation. +// pub const DOCUMENTATION: &str = "https://docs.rs/mini-functions"; + +/// The GITHUB constant contains the URL of the GitHub repository. +// pub const GITHUB: &str = "https://github.com/sebastienrousseau/mini-functions"; + +/// The HASH_COST constant contains the cost of the hash. +// pub const HASH_COST: u32 = 8; + +/// The HASH_LENGTH constant contains the length of the hash. +// pub const HASH_LENGTH: usize = 32; + +/// The HOMEPAGE constant contains the URL of the homepage. +// pub const HOMEPAGE: &str = "https://minifunctions.com/"; + +/// The SPECIAL constant contains the special characters. +pub const SPECIAL: &[u8] = b"!@#$%^&*()_+-=[]{};':,./<>?"; diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 00000000..dee51940 --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,4 @@ +pub use crate::common; + +pub(crate) mod constant; +pub(crate) mod words; diff --git a/src/common/words.rs b/src/common/words.rs new file mode 100644 index 00000000..aeaa19b8 --- /dev/null +++ b/src/common/words.rs @@ -0,0 +1,463 @@ +// Copyright Š 2022-2023 Mini Functions. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// * 4096 English words for generation of easy to memorize random passphrases. +// * This list comes from the MakePass passphrase generator developed by +// * Dianelos Georgoudis , which was announced on +// * sci.crypt on 1997/10/24. Here's a relevant excerpt from that posting: +// * +// * > The 4096 words in the word list were chosen according to the following +// * > criteria: +// * > - each word must contain between 3 and 6 characters +// * > - each word must be a common English word +// * > - each word should be clearly different from each other +// * > word, orthographically or semantically +// * > +// * > The MakePass word list has been placed in the public domain +// * +// * At least two other sci.crypt postings by Dianelos Georgoudis also state +// * that the word list is in the public domain, and so did the web page at: +// * +// * http://web.archive.org/web/%2a/http://www.tecapro.com/makepass.html +// * +// * which existed until 2006 and is available from the Wayback Machine as of +// * this writing (March 2010). Specifically, the web page said: +// * +// * > The MakePass word list has been placed in the public domain. To download +// * > a copy click here. You can use the MakePass word list for many other +// * > purposes. +// * +// * "To download a copy click here" was a link to free/makepass.lst, which is +// * currently available via the Wayback Machine: +// * +// * http://web.archive.org/web/%2a/http://www.tecapro.com/free/makepass.lst +// * +// * Even though the original description of the list stated that "each word +// * must contain between 3 and 6 characters", there were two 7-character words: +// * "England" and "Germany". For use in passwdqc, these have been replaced +// * with "erase" and "gag". +// * +// * The code in passwdqc_check.c and passwdqc_random.c makes the following +// * assumptions about this list: +// * +// * - there are exactly 4096 words; +// * - the words are of up to 6 characters long; +// * - although some words may contain capital letters, no two words differ by +// * the case of characters alone (e.g., converting the list to all-lowercase +// * would yield a list of 4096 unique words); +// * - the words contain alphabetical characters only; +// * - if an entire word on this list matches the initial substring of other +// * word(s) on the list, it is placed immediately before those words (e.g., +// * "bake", "baker", "bakery"). +// * +// * Additionally, the default minimum passphrase length of 11 characters +// * specified in passwdqc_parse.c has been chosen such that a passphrase +// * consisting of any three words from this list with two separator +// * characters will pass the minimum length check. In other words, this +// * default assumes that no word is shorter than 3 characters. +// * + +/// The list of words. +pub const WORD_LIST: &[&str] = &[ + "Adam", "Afghan", "Alaska", "Alice", "Allah", "Amazon", "Andrew", "Anglo", "Angola", "Antony", + "April", "Arab", "Arctic", "Athens", "Austin", "Bach", "Baltic", "Basque", "Berlin", "Bible", + "Bombay", "Bonn", "Boston", "Brazil", "Briton", "Buddha", "Burma", "Caesar", "Cairo", "Canada", + "Carl", "Carol", "Celtic", "Chile", "China", "Christ", "Congo", "Cuba", "Cyprus", "Czech", + "Dallas", "Danish", "Darwin", "David", "Delhi", "Derby", "Diana", "Dublin", "Dutch", "East", + "Eden", "Edward", "Eric", "Essex", "Europe", "Eve", "Exodus", "France", "French", "Friday", + "Gandhi", "Gaul", "Gemini", "Geneva", "George", "German", "Gloria", "God", "Gothic", "Greece", + "Greek", "Hague", "Haiti", "Hanoi", "Harry", "Havana", "Hawaii", "Hebrew", "Henry", "Hermes", + "Hindu", "Hitler", "Idaho", "Inca", "India", "Indian", "Iowa", "Iran", "Iraq", "Irish", + "Isaac", "Isabel", "Islam", "Israel", "Italy", "Ivan", "Jack", "Jacob", "James", "Japan", + "Java", "Jersey", "Jesus", "Jewish", "Jim", "John", "Jordan", "Joseph", "Judas", "Judy", + "July", "June", "Kansas", "Karl", "Kenya", "Koran", "Korea", "Kuwait", "Laos", "Latin", "Leo", + "Libya", "Lima", "Lisbon", "Liz", "London", "Louvre", "Lucy", "Luther", "Madame", "Madrid", + "Malta", "Maria", "Mars", "Mary", "Maya", "Mecca", "Mexico", "Miami", "Mickey", "Milan", + "Monaco", "Monday", "Moscow", "Moses", "Moslem", "Mrs", "Munich", "Muslim", "Naples", "Nazi", + "Nepal", "Newark", "Nile", "Nobel", "North", "Norway", "Ohio", "Oscar", "Oslo", "Oxford", + "Panama", "Paris", "Pascal", "Paul", "Peking", "Peru", "Peter", "Philip", "Poland", "Polish", + "Prague", "Quebec", "Rex", "Rhine", "Ritz", "Robert", "Roman", "Rome", "Rosa", "Russia", + "Sahara", "Sam", "Saturn", "Saudi", "Saxon", "Scot", "Seoul", "Somali", "Sony", "Soviet", + "Spain", "Stalin", "Sudan", "Suez", "Sunday", "Sweden", "Swiss", "Sydney", "Syria", "Taiwan", + "Tarzan", "Taurus", "Tehran", "Teresa", "Texas", "Thomas", "Tibet", "Tokyo", "Tom", "Turk", + "Turkey", "Uganda", "Venice", "Venus", "Vienna", "Viking", "Virgo", "Warsaw", "West", "Yale", + "Yemen", "York", "Zaire", "Zurich", "aback", "abbey", "abbot", "abide", "ablaze", "able", + "aboard", "abode", "abort", "abound", "about", "above", "abroad", "abrupt", "absent", "absorb", + "absurd", "abuse", "accent", "accept", "access", "accord", "accuse", "ace", "ache", "aching", + "acid", "acidic", "acorn", "acre", "across", "act", "action", "active", "actor", "actual", + "acute", "adapt", "add", "added", "addict", "adept", "adhere", "adjust", "admire", "admit", + "adobe", "adopt", "adrift", "adult", "adverb", "advert", "aerial", "afar", "affair", "affect", + "afford", "afield", "afloat", "afraid", "afresh", "after", "again", "age", "agency", "agenda", + "agent", "aghast", "agile", "ago", "agony", "agree", "agreed", "ahead", "aid", "aide", "aim", + "air", "airman", "airy", "akin", "alarm", "albeit", "album", "alert", "alibi", "alien", + "alight", "align", "alike", "alive", "alkali", "all", "alley", "allied", "allow", "alloy", + "ally", "almond", "almost", "aloft", "alone", "along", "aloof", "aloud", "alpha", "alpine", + "also", "altar", "alter", "always", "amaze", "amber", "ambush", "amen", "amend", "amid", + "amidst", "amiss", "among", "amount", "ample", "amuse", "anchor", "and", "anew", "angel", + "anger", "angle", "angry", "animal", "ankle", "annoy", "annual", "answer", "anthem", "anti", + "any", "anyhow", "anyway", "apart", "apathy", "apex", "apiece", "appeal", "appear", "apple", + "apply", "apron", "arcade", "arcane", "arch", "ardent", "are", "area", "argue", "arid", + "arise", "arm", "armful", "armpit", "army", "aroma", "around", "arouse", "array", "arrest", + "arrive", "arrow", "arson", "art", "artery", "artful", "artist", "ascent", "ashen", "ashore", + "aside", "ask", "asleep", "aspect", "assay", "assent", "assert", "assess", "asset", "assign", + "assist", "assume", "assure", "asthma", "astute", "asylum", "ate", "atlas", "atom", "atomic", + "attach", "attack", "attain", "attend", "attic", "auburn", "audio", "audit", "august", "aunt", + "auntie", "aura", "author", "auto", "autumn", "avail", "avenge", "avenue", "avert", "avid", + "avoid", "await", "awake", "awaken", "award", "aware", "awash", "away", "awful", "awhile", + "axes", "axiom", "axis", "axle", "aye", "babe", "baby", "back", "backup", "bacon", "bad", + "badge", "badly", "bag", "baggy", "bail", "bait", "bake", "baker", "bakery", "bald", "ball", + "ballad", "ballet", "ballot", "bamboo", "ban", "banal", "banana", "band", "bang", "bank", + "bar", "barber", "bare", "barely", "barge", "bark", "barley", "barn", "baron", "barrel", + "barren", "basalt", "base", "basic", "basil", "basin", "basis", "basket", "bass", "bat", + "batch", "bath", "baton", "battle", "bay", "beach", "beacon", "beak", "beam", "bean", "bear", + "beard", "beast", "beat", "beauty", "become", "bed", "beech", "beef", "beefy", "beep", "beer", + "beet", "beetle", "before", "beggar", "begin", "behalf", "behave", "behind", "beige", "being", + "belief", "bell", "belly", "belong", "below", "belt", "bench", "bend", "benign", "bent", + "berry", "berth", "beset", "beside", "best", "bestow", "bet", "beta", "betray", "better", + "beware", "beyond", "bias", "biceps", "bicker", "bid", "big", "bigger", "bike", "bile", "bill", + "binary", "bind", "biopsy", "birch", "bird", "birdie", "birth", "bishop", "bit", "bitch", + "bite", "bitter", "black", "blade", "blame", "bland", "blast", "blaze", "bleak", "blend", + "bless", "blew", "blind", "blink", "blip", "bliss", "blitz", "block", "blond", "blood", + "bloody", "bloom", "blot", "blouse", "blow", "blue", "bluff", "blunt", "blur", "blush", "boar", + "board", "boast", "boat", "bodily", "body", "bogus", "boil", "bold", "bolt", "bomb", "bond", + "bone", "bonnet", "bonus", "bony", "book", "boom", "boost", "boot", "booth", "booze", "border", + "bore", "borrow", "bosom", "boss", "both", "bother", "bottle", "bottom", "bought", "bounce", + "bound", "bounty", "bout", "bovine", "bow", "bowel", "bowl", "box", "boy", "boyish", "brace", + "brain", "brainy", "brake", "bran", "branch", "brand", "brandy", "brass", "brave", "bravo", + "breach", "bread", "break", "breast", "breath", "bred", "breed", "breeze", "brew", "brick", + "bride", "bridge", "brief", "bright", "brim", "brine", "bring", "brink", "brisk", "broad", + "broke", "broken", "bronze", "brook", "broom", "brown", "bruise", "brush", "brutal", "brute", + "bubble", "buck", "bucket", "buckle", "budget", "buffet", "buggy", "build", "bulb", "bulge", + "bulk", "bulky", "bull", "bullet", "bully", "bump", "bumpy", "bunch", "bundle", "bunk", + "bunny", "burden", "bureau", "burial", "buried", "burly", "burn", "burnt", "burrow", "burst", + "bury", "bus", "bush", "bust", "bustle", "busy", "but", "butler", "butt", "butter", "button", + "buy", "buyer", "buzz", "bye", "byte", "cab", "cabin", "cable", "cache", "cactus", "cage", + "cake", "calf", "call", "caller", "calm", "calmly", "came", "camel", "camera", "camp", + "campus", "can", "canal", "canary", "cancel", "cancer", "candid", "candle", "candy", "cane", + "canine", "canoe", "canopy", "canvas", "canyon", "cap", "cape", "car", "carbon", "card", + "care", "career", "caress", "cargo", "carnal", "carp", "carpet", "carrot", "carry", "cart", + "cartel", "case", "cash", "cask", "cast", "castle", "casual", "cat", "catch", "cater", + "cattle", "caught", "causal", "cause", "cave", "cease", "celery", "cell", "cellar", "cement", + "censor", "census", "cereal", "cervix", "chain", "chair", "chalk", "chalky", "champ", "chance", + "change", "chant", "chaos", "chap", "chapel", "charge", "charm", "chart", "chase", "chat", + "cheap", "cheat", "check", "cheek", "cheeky", "cheer", "cheery", "cheese", "chef", "cherry", + "chess", "chest", "chew", "chic", "chick", "chief", "child", "chill", "chilly", "chin", "chip", + "choice", "choir", "choose", "chop", "choppy", "chord", "chorus", "chose", "chosen", "chrome", + "chunk", "chunky", "church", "cider", "cigar", "cinema", "circa", "circle", "circus", "cite", + "city", "civic", "civil", "clad", "claim", "clammy", "clan", "clap", "clash", "clasp", "class", + "clause", "claw", "clay", "clean", "clear", "clergy", "clerk", "clever", "click", "client", + "cliff", "climax", "climb", "clinch", "cling", "clinic", "clip", "cloak", "clock", "clone", + "close", "closer", "closet", "cloth", "cloud", "cloudy", "clout", "clown", "club", "clue", + "clumsy", "clung", "clutch", "coach", "coal", "coarse", "coast", "coat", "coax", "cobalt", + "cobra", "coca", "cock", "cocoa", "code", "coffee", "coffin", "cohort", "coil", "coin", "coke", + "cold", "collar", "colon", "colony", "colt", "column", "comb", "combat", "come", "comedy", + "comic", "commit", "common", "compel", "comply", "concur", "cone", "confer", "consul", + "convex", "convey", "convoy", "cook", "cool", "cope", "copper", "copy", "coral", "cord", + "core", "cork", "corn", "corner", "corps", "corpse", "corpus", "cortex", "cosmic", "cosmos", + "cost", "costly", "cosy", "cotton", "couch", "cough", "could", "count", "county", "coup", + "couple", "coupon", "course", "court", "cousin", "cove", "cover", "covert", "cow", "coward", + "cowboy", "crab", "crack", "cradle", "craft", "crafty", "crag", "crane", "crap", "crash", + "crate", "crater", "crawl", "crazy", "creak", "cream", "creamy", "create", "credit", "creed", + "creek", "creep", "creepy", "crept", "crest", "crew", "cried", "crime", "crisis", "crisp", + "critic", "croft", "crook", "crop", "cross", "crow", "crowd", "crown", "crude", "cruel", + "cruise", "crunch", "crush", "crust", "crux", "cry", "crypt", "cube", "cubic", "cuckoo", + "cuff", "cult", "cup", "curb", "cure", "curfew", "curl", "curry", "curse", "cursor", "curve", + "custom", "cut", "cute", "cycle", "cyclic", "cynic", "dad", "daddy", "dagger", "daily", + "dairy", "daisy", "dale", "damage", "damn", "damp", "dampen", "dance", "danger", "dare", + "dark", "darken", "dash", "data", "date", "dawn", "day", "dead", "deadly", "deaf", "deal", + "dealer", "dean", "dear", "death", "debate", "debit", "debris", "debt", "debtor", "decade", + "decay", "decent", "decide", "deck", "decor", "decree", "deduce", "deed", "deep", "deeply", + "deer", "defeat", "defect", "defend", "defer", "define", "defy", "degree", "deity", "delay", + "delete", "delta", "demand", "demise", "demo", "demon", "demure", "denial", "denote", "dense", + "dental", "deny", "depart", "depend", "depict", "deploy", "depot", "depth", "deputy", "derive", + "desert", "design", "desire", "desist", "desk", "detail", "detect", "deter", "detest", + "detour", "device", "devil", "devise", "devoid", "devote", "devour", "dial", "diary", "dice", + "dictum", "did", "die", "diesel", "diet", "differ", "digest", "digit", "dine", "dinghy", + "dinner", "diode", "dire", "direct", "dirt", "dirty", "disc", "disco", "dish", "disk", + "dismal", "dispel", "ditch", "dive", "divert", "divide", "divine", "dizzy", "docile", "dock", + "doctor", "dog", "dogma", "dole", "doll", "dollar", "dolly", "domain", "dome", "domino", + "donate", "done", "donkey", "donor", "doom", "door", "dorsal", "dose", "double", "doubt", + "dough", "dour", "dove", "down", "dozen", "draft", "drag", "dragon", "drain", "drama", "drank", + "draw", "drawer", "dread", "dream", "dreary", "dress", "drew", "dried", "drift", "drill", + "drink", "drip", "drive", "driver", "drop", "drove", "drown", "drug", "drum", "drunk", "dry", + "dual", "duck", "duct", "due", "duel", "duet", "duke", "dull", "duly", "dumb", "dummy", "dump", + "dune", "dung", "duress", "during", "dusk", "dust", "dusty", "duty", "dwarf", "dwell", "dyer", + "dying", "dynamo", "each", "eager", "eagle", "ear", "earl", "early", "earn", "earth", "ease", + "easel", "easily", "easter", "easy", "eat", "eaten", "eater", "echo", "eddy", "edge", "edible", + "edict", "edit", "editor", "eerie", "eerily", "effect", "effort", "egg", "ego", "eight", + "eighth", "eighty", "either", "elbow", "elder", "eldest", "elect", "eleven", "elicit", "elite", + "else", "elude", "elves", "embark", "emblem", "embryo", "emerge", "emit", "empire", "employ", + "empty", "enable", "enamel", "end", "endure", "enemy", "energy", "engage", "engine", "enjoy", + "enlist", "enough", "ensure", "entail", "enter", "entire", "entry", "envoy", "envy", "enzyme", + "epic", "epoch", "equal", "equate", "equip", "equity", "era", "erase", "erect", "erode", + "erotic", "errant", "error", "escape", "escort", "essay", "estate", "esteem", "ethic", + "ethnic", "evade", "even", "event", "ever", "every", "evict", "evil", "evoke", "evolve", + "exact", "exam", "exceed", "excel", "except", "excess", "excise", "excite", "excuse", "exempt", + "exert", "exile", "exist", "exit", "exotic", "expand", "expect", "expert", "expire", "export", + "expose", "extend", "extra", "eye", "eyed", "fabric", "face", "facial", "fact", "factor", + "fade", "fail", "faint", "fair", "fairly", "fairy", "faith", "fake", "falcon", "fall", "false", + "falter", "fame", "family", "famine", "famous", "fan", "fancy", "far", "farce", "fare", "farm", + "farmer", "fast", "fasten", "faster", "fat", "fatal", "fate", "father", "fatty", "fault", + "faulty", "fauna", "fear", "feast", "feat", "fed", "fee", "feeble", "feed", "feel", "feet", + "fell", "fellow", "felt", "female", "fence", "fend", "ferry", "fetal", "fetch", "feudal", + "fever", "few", "fewer", "fiance", "fiasco", "fiddle", "field", "fiend", "fierce", "fiery", + "fifth", "fifty", "fig", "fight", "figure", "file", "fill", "filled", "filler", "film", + "filter", "filth", "filthy", "final", "finale", "find", "fine", "finger", "finish", "finite", + "fire", "firm", "firmly", "first", "fiscal", "fish", "fisher", "fist", "fit", "fitful", "five", + "fix", "flag", "flair", "flak", "flame", "flank", "flap", "flare", "flash", "flask", "flat", + "flaw", "fled", "flee", "fleece", "fleet", "flesh", "fleshy", "flew", "flick", "flight", + "flimsy", "flint", "flirt", "float", "flock", "flood", "floor", "floppy", "flora", "floral", + "flour", "flow", "flower", "fluent", "fluffy", "fluid", "flung", "flurry", "flush", "flute", + "flux", "fly", "flyer", "foal", "foam", "focal", "focus", "fog", "foil", "fold", "folk", + "follow", "folly", "fond", "fondly", "font", "food", "fool", "foot", "for", "forbid", "force", + "ford", "forest", "forge", "forget", "fork", "form", "formal", "format", "former", "fort", + "forth", "forty", "forum", "fossil", "foster", "foul", "found", "four", "fourth", "fox", + "foyer", "frail", "frame", "franc", "frank", "fraud", "free", "freed", "freely", "freer", + "freeze", "frenzy", "fresh", "friar", "fridge", "fried", "friend", "fright", "fringe", "frock", + "frog", "from", "front", "frost", "frosty", "frown", "frozen", "frugal", "fruit", "fudge", + "fuel", "fulfil", "full", "fully", "fun", "fund", "funny", "fur", "furry", "fury", "fuse", + "fusion", "fuss", "fussy", "futile", "future", "fuzzy", "gadget", "gag", "gain", "gala", + "galaxy", "gale", "gall", "galley", "gallon", "gallop", "gamble", "game", "gamma", "gang", + "gap", "garage", "garden", "garlic", "gas", "gasp", "gate", "gather", "gauge", "gaunt", "gave", + "gay", "gaze", "gear", "geese", "gender", "gene", "genial", "genius", "genre", "gentle", + "gently", "gentry", "genus", "get", "ghetto", "ghost", "giant", "gift", "giggle", "gill", + "gilt", "ginger", "girl", "give", "given", "glad", "glade", "glance", "gland", "glare", + "glass", "glassy", "gleam", "glee", "glide", "global", "globe", "gloom", "gloomy", "glory", + "gloss", "glossy", "glove", "glow", "glue", "goal", "goat", "gold", "golden", "golf", "gone", + "gong", "good", "goose", "gorge", "gory", "gosh", "gospel", "gossip", "got", "govern", "gown", + "grab", "grace", "grade", "grain", "grand", "grant", "grape", "graph", "grasp", "grass", + "grassy", "grate", "grave", "gravel", "gravy", "gray", "grease", "greasy", "great", "greed", + "greedy", "green", "greet", "grew", "grey", "grid", "grief", "grill", "grim", "grin", "grind", + "grip", "grit", "gritty", "groan", "groin", "groom", "groove", "gross", "ground", "group", + "grove", "grow", "grown", "growth", "grudge", "grunt", "guard", "guess", "guest", "guide", + "guild", "guilt", "guilty", "guise", "guitar", "gulf", "gully", "gun", "gunman", "guru", "gut", + "guy", "gypsy", "habit", "hack", "had", "hail", "hair", "hairy", "hale", "half", "hall", + "halt", "hamlet", "hammer", "hand", "handle", "handy", "hang", "hangar", "happen", "happy", + "harass", "hard", "harder", "hardly", "hare", "harem", "harm", "harp", "harsh", "has", "hash", + "hassle", "haste", "hasten", "hasty", "hat", "hatch", "hate", "haul", "haunt", "have", "haven", + "havoc", "hawk", "hazard", "haze", "hazel", "hazy", "head", "heal", "health", "heap", "hear", + "heard", "heart", "hearth", "hearty", "heat", "heater", "heaven", "heavy", "heck", "hectic", + "hedge", "heel", "hefty", "height", "heir", "held", "helium", "helix", "hell", "hello", "helm", + "helmet", "help", "hemp", "hence", "her", "herald", "herb", "herd", "here", "hereby", "hernia", + "hero", "heroic", "heroin", "hey", "heyday", "hick", "hidden", "hide", "high", "higher", + "highly", "hill", "him", "hind", "hint", "hippy", "hire", "his", "hiss", "hit", "hive", + "hoard", "hoarse", "hobby", "hockey", "hold", "holder", "hole", "hollow", "holly", "holy", + "home", "honest", "honey", "hood", "hook", "hope", "horn", "horny", "horrid", "horror", + "horse", "hose", "host", "hot", "hotel", "hound", "hour", "house", "hover", "how", "huge", + "hull", "human", "humane", "humble", "humid", "hung", "hunger", "hungry", "hunt", "hurdle", + "hurl", "hurry", "hurt", "hush", "hut", "hybrid", "hymn", "hyphen", "ice", "icing", "icon", + "idea", "ideal", "idiom", "idiot", "idle", "idly", "idol", "ignite", "ignore", "ill", "image", + "immune", "impact", "imply", "import", "impose", "incest", "inch", "income", "incur", "indeed", + "index", "indoor", "induce", "inept", "inert", "infant", "infect", "infer", "influx", "inform", + "inject", "injure", "injury", "inlaid", "inland", "inlet", "inmate", "inn", "innate", "inner", + "input", "insane", "insect", "insert", "inset", "inside", "insist", "insult", "insure", + "intact", "intake", "intend", "inter", "into", "invade", "invent", "invest", "invite", + "invoke", "inward", "iron", "ironic", "irony", "island", "isle", "issue", "itch", "item", + "itself", "ivory", "jacket", "jade", "jaguar", "jail", "jargon", "jaw", "jazz", "jeep", + "jelly", "jerky", "jest", "jet", "jewel", "job", "jock", "jockey", "join", "joint", "joke", + "jolly", "jolt", "joy", "joyful", "joyous", "judge", "juice", "juicy", "jumble", "jumbo", + "jump", "jungle", "junior", "junk", "junta", "jury", "just", "karate", "keel", "keen", "keep", + "keeper", "kept", "kernel", "kettle", "key", "khaki", "kick", "kid", "kidnap", "kidney", + "kill", "killer", "kin", "kind", "kindly", "king", "kiss", "kite", "kitten", "knack", "knee", + "knew", "knife", "knight", "knit", "knob", "knock", "knot", "know", "known", "label", "lace", + "lack", "lad", "ladder", "laden", "lady", "lagoon", "laity", "lake", "lamb", "lame", "lamp", + "lance", "land", "lane", "lap", "lapse", "large", "larval", "laser", "last", "latch", "late", + "lately", "latent", "later", "latest", "latter", "laugh", "launch", "lava", "lavish", "law", + "lawful", "lawn", "lawyer", "lay", "layer", "layman", "lazy", "lead", "leader", "leaf", + "leafy", "league", "leak", "leaky", "lean", "leap", "learn", "lease", "leash", "least", + "leave", "led", "ledge", "left", "leg", "legacy", "legal", "legend", "legion", "lemon", "lend", + "length", "lens", "lent", "leper", "lesion", "less", "lessen", "lesser", "lesson", "lest", + "let", "lethal", "letter", "level", "lever", "levy", "lewis", "liable", "liar", "libel", + "lice", "lick", "lid", "lie", "lied", "life", "lift", "light", "like", "likely", "limb", + "lime", "limit", "limp", "line", "linear", "linen", "linger", "link", "lion", "lip", "liquid", + "liquor", "list", "listen", "lit", "live", "lively", "liver", "lizard", "load", "loaf", "loan", + "lobby", "lobe", "local", "locate", "lock", "locus", "lodge", "loft", "lofty", "log", "logic", + "logo", "lone", "lonely", "long", "longer", "look", "loop", "loose", "loosen", "loot", "lord", + "lorry", "lose", "loss", "lost", "lot", "lotion", "lotus", "loud", "loudly", "lounge", "lousy", + "love", "lovely", "lover", "low", "lower", "lowest", "loyal", "lucid", "luck", "lucky", "lull", + "lump", "lumpy", "lunacy", "lunar", "lunch", "lung", "lure", "lurid", "lush", "lust", "lute", + "luxury", "lying", "lymph", "lynch", "lyric", "macho", "macro", "mad", "madam", "made", + "mafia", "magic", "magma", "magnet", "magnum", "maid", "maiden", "mail", "main", "mainly", + "major", "make", "maker", "male", "malice", "mall", "malt", "mammal", "manage", "mane", + "mania", "manic", "manner", "manor", "mantle", "manual", "manure", "many", "map", "maple", + "marble", "march", "mare", "margin", "marina", "mark", "market", "marry", "marsh", "martin", + "martyr", "mask", "mason", "mass", "mast", "master", "match", "mate", "matrix", "matter", + "mature", "maxim", "may", "maybe", "mayor", "maze", "mead", "meadow", "meal", "mean", "meant", + "meat", "medal", "media", "median", "medic", "medium", "meet", "mellow", "melody", "melon", + "melt", "member", "memo", "memory", "menace", "mend", "mental", "mentor", "menu", "mercy", + "mere", "merely", "merge", "merger", "merit", "merry", "mesh", "mess", "messy", "met", "metal", + "meter", "method", "methyl", "metric", "metro", "mid", "midday", "middle", "midst", "midway", + "might", "mighty", "mild", "mildew", "mile", "milk", "milky", "mill", "mimic", "mince", "mind", + "mine", "mini", "mink", "minor", "mint", "minus", "minute", "mirror", "mirth", "misery", + "miss", "mist", "misty", "mite", "mix", "moan", "moat", "mobile", "mock", "mode", "model", + "modem", "modern", "modest", "modify", "module", "moist", "molar", "mole", "molten", "moment", + "money", "monies", "monk", "monkey", "month", "mood", "moody", "moon", "moor", "moral", + "morale", "morbid", "more", "morgue", "mortal", "mortar", "mosaic", "mosque", "moss", "most", + "mostly", "moth", "mother", "motion", "motive", "motor", "mould", "mount", "mourn", "mouse", + "mouth", "move", "movie", "much", "muck", "mucus", "mud", "muddle", "muddy", "mule", "mummy", + "murder", "murky", "murmur", "muscle", "museum", "music", "mussel", "must", "mutant", "mute", + "mutiny", "mutter", "mutton", "mutual", "muzzle", "myopic", "myriad", "myself", "mystic", + "myth", "nadir", "nail", "naked", "name", "namely", "nape", "napkin", "narrow", "nasal", + "nasty", "nation", "native", "nature", "nausea", "naval", "nave", "navy", "near", "nearer", + "nearly", "neat", "neatly", "neck", "need", "needle", "needy", "negate", "neon", "nephew", + "nerve", "nest", "neural", "never", "newly", "next", "nice", "nicely", "niche", "nickel", + "niece", "night", "nimble", "nine", "ninety", "ninth", "noble", "nobody", "node", "noise", + "noisy", "non", "none", "noon", "nor", "norm", "normal", "nose", "nosy", "not", "note", + "notice", "notify", "notion", "nought", "noun", "novel", "novice", "now", "nozzle", "nude", + "null", "numb", "number", "nurse", "nylon", "nymph", "oak", "oasis", "oath", "obese", "obey", + "object", "oblige", "oboe", "obtain", "occult", "occupy", "occur", "ocean", "octave", "odd", + "off", "offend", "offer", "office", "offset", "often", "oil", "oily", "okay", "old", "older", + "oldest", "olive", "omega", "omen", "omit", "once", "one", "onion", "only", "onset", "onto", + "onus", "onward", "opaque", "open", "openly", "opera", "opium", "oppose", "optic", "option", + "oracle", "oral", "orange", "orbit", "orchid", "ordeal", "order", "organ", "orgasm", "orient", + "origin", "ornate", "orphan", "other", "otter", "ought", "ounce", "our", "out", "outer", + "output", "outset", "oval", "oven", "over", "overt", "owe", "owing", "owl", "own", "owner", + "oxide", "oxygen", "oyster", "ozone", "pace", "pack", "packet", "pact", "paddle", "paddy", + "pagan", "page", "paid", "pain", "paint", "pair", "palace", "pale", "palm", "panel", "panic", + "papa", "papal", "paper", "parade", "parcel", "pardon", "parent", "parish", "park", "parody", + "parrot", "part", "partly", "party", "pass", "past", "paste", "pastel", "pastor", "pastry", + "pat", "patch", "patent", "path", "patio", "patrol", "patron", "pause", "pave", "pawn", "pay", + "peace", "peach", "peak", "pear", "pearl", "pedal", "peel", "peer", "pelvic", "pelvis", "pen", + "penal", "pence", "pencil", "penis", "penny", "people", "pepper", "per", "perch", "peril", + "period", "perish", "permit", "person", "pest", "petite", "petrol", "petty", "phase", "phone", + "photo", "phrase", "piano", "pick", "picket", "picnic", "pie", "piece", "pier", "pierce", + "piety", "pig", "pigeon", "piggy", "pike", "pile", "pill", "pillar", "pillow", "pilot", "pin", + "pinch", "pine", "pink", "pint", "pious", "pipe", "pirate", "piss", "pistol", "piston", "pit", + "pitch", "pity", "pivot", "pixel", "pizza", "place", "placid", "plague", "plain", "plan", + "plane", "planet", "plank", "plant", "plasma", "plate", "play", "player", "plea", "plead", + "please", "pledge", "plenty", "plenum", "plight", "plot", "ploy", "plug", "plum", "plump", + "plunge", "plural", "plus", "plush", "pocket", "poem", "poet", "poetic", "poetry", "point", + "poison", "polar", "pole", "police", "policy", "polite", "poll", "pollen", "polo", "pond", + "ponder", "pony", "pool", "poor", "poorly", "pop", "pope", "poppy", "pore", "pork", "port", + "portal", "pose", "posh", "post", "postal", "pot", "potato", "potent", "pouch", "pound", + "pour", "powder", "power", "praise", "pray", "prayer", "preach", "prefer", "prefix", "press", + "pretty", "price", "pride", "priest", "primal", "prime", "prince", "print", "prior", "prism", + "prison", "privy", "prize", "probe", "profit", "prompt", "prone", "proof", "propel", "proper", + "prose", "proton", "proud", "prove", "proven", "proxy", "prune", "psalm", "pseudo", "psyche", + "pub", "public", "puff", "pull", "pulp", "pulpit", "pulsar", "pulse", "pump", "punch", + "punish", "punk", "pupil", "puppet", "puppy", "pure", "purely", "purge", "purify", "purple", + "purse", "pursue", "push", "pushy", "pussy", "put", "putt", "puzzle", "quaint", "quake", + "quarry", "quartz", "quay", "queen", "queer", "query", "quest", "queue", "quick", "quid", + "quiet", "quilt", "quirk", "quit", "quite", "quiver", "quiz", "quota", "quote", "rabbit", + "race", "racial", "racism", "rack", "racket", "radar", "radio", "radish", "radius", "raffle", + "raft", "rage", "raid", "rail", "rain", "rainy", "raise", "rally", "ramp", "random", "range", + "rank", "ransom", "rape", "rapid", "rare", "rarely", "rarity", "rash", "rat", "rate", "rather", + "ratify", "ratio", "rattle", "rave", "raven", "raw", "ray", "razor", "reach", "react", "read", + "reader", "ready", "real", "really", "realm", "reap", "rear", "reason", "rebel", "recall", + "recent", "recess", "recipe", "reckon", "record", "recoup", "rector", "red", "redeem", + "reduce", "reed", "reef", "refer", "reform", "refuge", "refuse", "regal", "regard", "regent", + "regime", "region", "regret", "reign", "reject", "relate", "relax", "relay", "relic", "relief", + "relish", "rely", "remain", "remark", "remedy", "remind", "remit", "remote", "remove", "renal", + "render", "rent", "rental", "repair", "repeal", "repeat", "repent", "reply", "report", + "rescue", "resent", "reside", "resign", "resin", "resist", "resort", "rest", "result", + "resume", "retail", "retain", "retina", "retire", "return", "reveal", "review", "revise", + "revive", "revolt", "reward", "rhino", "rhyme", "rhythm", "ribbon", "rice", "rich", "rick", + "rid", "ride", "rider", "ridge", "rife", "rifle", "rift", "right", "rigid", "ring", "rinse", + "riot", "ripe", "ripen", "ripple", "rise", "risk", "risky", "rite", "ritual", "rival", "river", + "road", "roar", "roast", "rob", "robe", "robin", "robot", "robust", "rock", "rocket", "rocky", + "rod", "rode", "rodent", "rogue", "role", "roll", "roof", "room", "root", "rope", "rose", + "rosy", "rotate", "rotor", "rotten", "rouge", "rough", "round", "route", "rover", "row", + "royal", "rubble", "ruby", "rudder", "rude", "rugby", "ruin", "rule", "ruler", "rumble", + "rump", "run", "rune", "rung", "runway", "rural", "rush", "rust", "rustic", "rusty", "sack", + "sacred", "sad", "saddle", "sadism", "sadly", "safari", "safe", "safely", "safer", "safety", + "saga", "sage", "said", "sail", "sailor", "saint", "sake", "salad", "salary", "sale", "saline", + "saliva", "salmon", "saloon", "salt", "salty", "salute", "same", "sample", "sand", "sandy", + "sane", "sash", "satan", "satin", "satire", "sauce", "sauna", "savage", "save", "say", "scale", + "scalp", "scan", "scant", "scar", "scarce", "scare", "scarf", "scary", "scene", "scenic", + "scent", "school", "scope", "score", "scorn", "scotch", "scout", "scrap", "scream", "screen", + "screw", "script", "scroll", "scrub", "scum", "sea", "seal", "seam", "seaman", "search", + "season", "seat", "second", "secret", "sect", "sector", "secure", "see", "seed", "seeing", + "seek", "seem", "seize", "seldom", "select", "self", "sell", "seller", "semi", "senate", + "send", "senile", "senior", "sense", "sensor", "sent", "sentry", "sequel", "serene", "serial", + "series", "sermon", "serum", "serve", "server", "set", "settle", "seven", "severe", "sewage", + "sex", "sexual", "sexy", "shabby", "shade", "shadow", "shady", "shaft", "shaggy", "shah", + "shake", "shaky", "shall", "sham", "shame", "shape", "share", "shark", "sharp", "shawl", "she", + "shear", "sheen", "sheep", "sheer", "sheet", "shelf", "shell", "sherry", "shield", "shift", + "shine", "shiny", "ship", "shire", "shirt", "shit", "shiver", "shock", "shoe", "shook", + "shoot", "shop", "shore", "short", "shot", "should", "shout", "show", "shower", "shrank", + "shrewd", "shrill", "shrimp", "shrine", "shrink", "shrub", "shrug", "shut", "shy", "shyly", + "sick", "side", "siege", "sigh", "sight", "sigma", "sign", "signal", "silent", "silk", + "silken", "silky", "sill", "silly", "silver", "simple", "simply", "since", "sinful", "sing", + "singer", "single", "sink", "sir", "siren", "sister", "sit", "site", "six", "sixth", "sixty", + "size", "sketch", "skill", "skin", "skinny", "skip", "skirt", "skull", "sky", "slab", "slack", + "slain", "slam", "slang", "slap", "slate", "slater", "slave", "sleek", "sleep", "sleepy", + "sleeve", "slice", "slick", "slid", "slide", "slight", "slim", "slimy", "sling", "slip", + "slit", "slogan", "slope", "sloppy", "slot", "slow", "slowly", "slug", "slum", "slump", + "smack", "small", "smart", "smash", "smear", "smell", "smelly", "smelt", "smile", "smoke", + "smoky", "smooth", "smug", "snack", "snail", "snake", "snap", "snatch", "sneak", "snow", + "snowy", "snug", "soak", "soap", "sober", "soccer", "social", "sock", "socket", "soda", + "sodden", "sodium", "sofa", "soft", "soften", "softly", "soggy", "soil", "solar", "sold", + "sole", "solely", "solemn", "solid", "solo", "solve", "some", "son", "sonar", "sonata", "song", + "sonic", "soon", "sooner", "soot", "soothe", "sordid", "sore", "sorrow", "sorry", "sort", + "soul", "sound", "soup", "sour", "source", "space", "spade", "span", "spare", "spark", + "sparse", "spasm", "spat", "spate", "speak", "spear", "speech", "speed", "speedy", "spell", + "spend", "sperm", "sphere", "spice", "spicy", "spider", "spiky", "spill", "spin", "spinal", + "spine", "spiral", "spirit", "spit", "spite", "splash", "split", "spoil", "spoke", "sponge", + "spoon", "sport", "spot", "spouse", "spray", "spread", "spree", "spring", "sprint", "spur", + "squad", "square", "squash", "squat", "squid", "stab", "stable", "stack", "staff", "stage", + "stain", "stair", "stake", "stale", "stall", "stamp", "stance", "stand", "staple", "star", + "starch", "stare", "stark", "start", "starve", "state", "static", "statue", "status", "stay", + "stead", "steady", "steak", "steal", "steam", "steel", "steep", "steer", "stem", "stench", + "step", "stereo", "stern", "stew", "stick", "sticky", "stiff", "stifle", "stigma", "still", + "sting", "stint", "stir", "stitch", "stock", "stocky", "stone", "stony", "stool", "stop", + "store", "storm", "stormy", "story", "stout", "stove", "strain", "strait", "strand", "strap", + "strata", "straw", "stray", "streak", "stream", "street", "stress", "strict", "stride", + "strife", "strike", "string", "strip", "strive", "stroke", "stroll", "strong", "stud", + "studio", "study", "stuff", "stuffy", "stunt", "stupid", "sturdy", "style", "submit", "subtle", + "subtly", "suburb", "such", "suck", "sudden", "sue", "suffer", "sugar", "suit", "suite", + "suitor", "sullen", "sultan", "sum", "summer", "summit", "summon", "sun", "sunny", "sunset", + "super", "superb", "supper", "supple", "supply", "sure", "surely", "surf", "surge", "survey", + "suture", "swamp", "swan", "swap", "swarm", "sway", "swear", "sweat", "sweaty", "sweep", + "sweet", "swell", "swift", "swim", "swine", "swing", "swirl", "switch", "sword", "swore", + "symbol", "synod", "syntax", "syrup", "system", "table", "tablet", "taboo", "tacit", "tackle", + "tact", "tactic", "tail", "tailor", "take", "tale", "talent", "talk", "tall", "tally", "tame", + "tandem", "tangle", "tank", "tap", "tape", "target", "tariff", "tart", "task", "taste", + "tasty", "tattoo", "taut", "tavern", "tax", "taxi", "tea", "teach", "teak", "team", "tear", + "tease", "tech", "teeth", "tell", "temper", "temple", "tempo", "tempt", "ten", "tenant", + "tend", "tender", "tendon", "tennis", "tenor", "tense", "tensor", "tent", "tenth", "tenure", + "term", "terror", "test", "text", "than", "thank", "that", "the", "their", "them", "theme", + "then", "thence", "theory", "there", "these", "thesis", "they", "thick", "thief", "thigh", + "thin", "thing", "think", "third", "thirst", "thirty", "this", "thorn", "those", "though", + "thread", "threat", "three", "thrill", "thrive", "throat", "throne", "throng", "throw", + "thrust", "thud", "thug", "thumb", "thus", "thyme", "tick", "ticket", "tidal", "tide", "tidy", + "tie", "tier", "tiger", "tight", "tile", "till", "tilt", "timber", "time", "timid", "tin", + "tiny", "tip", "tissue", "title", "toad", "toast", "today", "toilet", "token", "told", "toll", + "tomato", "tomb", "tonal", "tone", "tongue", "tonic", "too", "took", "tool", "tooth", "top", + "topaz", "topic", "torch", "torque", "torso", "tort", "toss", "total", "touch", "tough", + "tour", "toward", "towel", "tower", "town", "toxic", "toxin", "trace", "track", "tract", + "trade", "tragic", "trail", "train", "trait", "tram", "trance", "trap", "trauma", "travel", + "tray", "tread", "treat", "treaty", "treble", "tree", "trek", "tremor", "trench", "trend", + "trendy", "trial", "tribal", "tribe", "trick", "tricky", "tried", "trifle", "trim", "trio", + "trip", "triple", "troop", "trophy", "trot", "trough", "trout", "truce", "truck", "true", + "truly", "trunk", "trust", "truth", "try", "tsar", "tube", "tumble", "tuna", "tundra", "tune", + "tung", "tunic", "tunnel", "turban", "turf", "turn", "turtle", "tutor", "tweed", "twelve", + "twenty", "twice", "twin", "twist", "two", "tycoon", "tying", "type", "tyrant", "ugly", + "ulcer", "ultra", "umpire", "unable", "uncle", "under", "uneasy", "unfair", "unify", "union", + "unique", "unit", "unite", "unity", "unlike", "unrest", "unruly", "until", "update", "upheld", + "uphill", "uphold", "upon", "uproar", "upset", "upshot", "uptake", "upturn", "upward", "urban", + "urge", "urgent", "urging", "urine", "usable", "usage", "use", "useful", "user", "usual", + "uterus", "utmost", "utter", "vacant", "vacuum", "vagina", "vague", "vain", "valet", "valid", + "valley", "value", "valve", "van", "vanish", "vanity", "vary", "vase", "vast", "vat", "vault", + "vector", "veil", "vein", "velvet", "vendor", "veneer", "venom", "vent", "venue", "verb", + "verbal", "verge", "verify", "verity", "verse", "versus", "very", "vessel", "vest", "veto", + "via", "viable", "vicar", "vice", "victim", "victor", "video", "view", "vigil", "vile", + "villa", "vine", "vinyl", "viola", "violet", "violin", "viral", "virgin", "virtue", "virus", + "visa", "vision", "visit", "visual", "vital", "vivid", "vocal", "vodka", "vogue", "voice", + "void", "volley", "volume", "vomit", "vote", "vowel", "voyage", "vulgar", "wade", "wage", + "waist", "wait", "waiter", "wake", "walk", "walker", "wall", "wallet", "walnut", "wander", + "want", "war", "warden", "warm", "warmth", "warn", "warp", "wary", "was", "wash", "wasp", + "waste", "watch", "water", "watery", "wave", "way", "weak", "weaken", "wealth", "weapon", + "wear", "weary", "wedge", "wee", "weed", "week", "weekly", "weep", "weight", "weird", "well", + "were", "wet", "whale", "wharf", "what", "wheat", "wheel", "when", "whence", "where", "which", + "whiff", "whig", "while", "whim", "whip", "whisky", "white", "who", "whole", "wholly", "whom", + "whore", "whose", "why", "wide", "widely", "widen", "wider", "widow", "width", "wife", "wild", + "wildly", "wilful", "will", "willow", "win", "wind", "window", "windy", "wine", "wing", "wink", + "winner", "winter", "wipe", "wire", "wisdom", "wise", "wish", "wit", "witch", "with", "within", + "witty", "wizard", "woke", "wolf", "wolves", "woman", "womb", "won", "wonder", "wood", + "wooden", "woods", "woody", "wool", "word", "work", "worker", "world", "worm", "worry", + "worse", "worst", "worth", "worthy", "would", "wound", "wrap", "wrath", "wreath", "wreck", + "wright", "wrist", "writ", "write", "writer", "wrong", "xerox", "yacht", "yard", "yarn", + "yeah", "year", "yeast", "yellow", "yet", "yield", "yogurt", "yolk", "you", "young", "your", + "youth", "zeal", "zebra", "zenith", "zero", "zigzag", "zinc", "zombie", "zone", +]; diff --git a/src/date.rs b/src/date.rs index f1d56c46..90e63b89 100644 --- a/src/date.rs +++ b/src/date.rs @@ -1,6 +1,6 @@ -//! # Core date functionality +//! # Core Date functionality //! -//! Date provides an easy way to get the current date and time in multiple formats. +//! This crate provides an easy way to get the current date and time in multiple formats. //! //! diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 00000000..4ad993bf --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,110 @@ +//! # Core Cryptography Hash functionality based on the [blake3](https://crates.io/crates/blake3) crate. +//! +//! This crate provides an easy way to hash and verify passwords using the [blake3](https://crates.io/crates/blake3) crate. +//! +//! + +// Copyright Š 2022-2023 Mini Functions. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use blake3::Hasher; + +/// A struct for storing and verifying hashed passwords based on +/// the [blake3](https://crates.io/crates/blake3) crate. +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct Hash { + /// The password. + password: String, + /// The password hash. + hash: String, +} + +impl Hash { + /// Returns a new instance of `Hash`. + pub fn new() -> Self { + Self { + password: String::new(), + hash: String::new(), + } + } + + /// Hashes the password. + pub fn generate_hash(&self) -> String { + let mut hasher = Hasher::new(); + hasher.update(self.password.as_bytes()); + let hash = hasher.finalize().to_hex(); + hash.to_string() + } + + /// Verifies the password against the stored hash. + /// + /// Returns `true` if the password and hash match, `false` otherwise. + pub fn verify(&self, hash: &str, password: &str) -> bool { + let mut hasher = Hasher::new(); + hasher.update(password.as_bytes()); + let password_hash = hasher.finalize().to_hex(); + let password_hash_str = password_hash.to_string(); + password_hash_str == hash + } + + /// Sets the password and updates the hash. + pub fn set_password(&mut self, password: &str) { + self.password = password.to_string(); + self.hash = self.generate_hash(); + } + + /// Sets the hash. + pub fn set_hash(&mut self, hash: &str) { + self.hash = hash.to_string(); + } + + /// Returns the password. + pub fn password(&self) -> &str { + &self.password + } + + /// Returns the password length. + pub fn password_length(&self) -> usize { + self.password.len() + } + + /// Returns the hash. + pub fn hash(&self) -> &str { + &self.hash + } + + /// Returns the hash length. + pub fn hash_length(&self) -> usize { + self.hash.len() + } + /// Calculates the entropy of the hash in bits based on the Shannon + /// entropy formula. + /// https://en.wikipedia.org/wiki/Entropy_(information_theory) + /// + pub fn entropy(&self) -> f64 { + // Shannon entropy formula in bits. + let mut entropy = 0.0; + for c in self.hash.chars() { + let p = (c as u8) as f64 / 255.0; + entropy -= p * p.log2(); + } + entropy + } +} + +impl std::fmt::Display for Hash { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Hash {{ password: {}, hash: {} }}", + self.password, self.hash + ) + } +} + +impl Default for Hash { + fn default() -> Self { + Self::new() + } +} diff --git a/src/lib.rs b/src/lib.rs index cd66db82..98481b68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! Add the following to your `Cargo.toml` file: //! ```toml //! [dependencies] -//! mini_functions = "0.0.5" +//! mini_functions = "0.0.6" //! ``` //! Then, add the following to your crate root: //! ```rust @@ -66,10 +66,18 @@ /// Provides a set of utility functions for working with dates and times pub mod date; +/// Provides a set of utility functions for working with hashes. This +/// module is a wrapper around the `blake2` crate. +pub mod hash; + /// Provides a log function to log a message to the console with a /// simple, readable output format pub mod log; +/// Provides a set of utility functions for generating and working with +/// passwords/passphrases +pub mod password; + /// Provides a set of utility functions for generating QR codes pub mod qrcode; @@ -78,3 +86,6 @@ pub mod random; /// Provides a set of utility functions for working with UUIDs pub mod uuid; + +/// Provides a set of common constants used in the application +pub mod common; diff --git a/src/password.rs b/src/password.rs new file mode 100644 index 00000000..9b4c0b8a --- /dev/null +++ b/src/password.rs @@ -0,0 +1,128 @@ +pub use crate::common; +use crate::hash::Hash; +use crate::random::Random; +use common::constant::*; +use common::words::*; +use convert_case::{Case, Casing}; +use std::collections::HashSet; +// use std::path::Path; + +/// A random password / passphrase generator. The generated password +/// is a string of three words separated by hyphens. Each word is +/// between 6 and 8 characters long. The first character of each word +/// is capitalized. +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct Password { + /// The generated passphrase. + passphrase: String, +} + +impl Password { + /// Returns true if the generated passphrase is empty. + /// Returns false if the generated passphrase is not empty. + pub fn is_empty(&self) -> bool { + self.passphrase.is_empty() + } + /// Returns the length of the generated passphrase. + pub fn len(&self) -> usize { + self.passphrase.len() + } + /// Generates a random passphrase. + pub fn new(len: u8, separator: &str) -> Self { + // Setup a random number generator + let mut rng = Random::default(); + + // Generate a random number between 0 and 99. + let mut nb: i32 = rng.range(0, 99); + + // Create a new vector to store the words in. + let mut words: Vec = Vec::new(); + + // Convert the special characters to a vector of chars. + let ascii: Vec = SPECIAL.iter().map(|&c| c as char).collect(); + + // Create a new HashSet to store the generated words. + let mut word_set = HashSet::new(); + + // Generate `len` random words from the word list. + while words.len() < len as usize { + // Choose a random word from the list. + let word = if let Some(w) = Random::choose(&mut rng, WORD_LIST) { + // If a word was found, return it. + w + } else { + // If no word was found, return an empty string. + "" + }; + + // Check if the word is already in the HashSet. If it is, skip + // to the next iteration. + if word_set.contains(word) { + continue; + } + + // Add the word to the HashSet. + word_set.insert(word); + + // Convert the word to title case and add a number to the end + let word = format!( + "{}{}{}", + word.to_case(Case::Title), + Random::choose(&mut rng, &ascii).unwrap(), + nb + ); + // Generate a new random number between 0 and 99. + nb = rng.range(0, 99); + // Add the word to the vector of words. + words.push(word); + } + + // Join the words together with the separator + let pass = words.join(separator); + + // Return the password and hash + Self { passphrase: pass } + } + /// Returns the generated passphrase. + pub fn passphrase(&self) -> &str { + &self.passphrase + } + /// Sets the generated passphrase. + pub fn set_passphrase(&mut self, passphrase: &str) { + self.passphrase = passphrase.to_string(); + } + /// Returns the hash of the generated passphrase. + pub fn hash(&self) -> String { + let mut hash = Hash::new(); + hash.set_password(&self.passphrase); + let hash_value = hash.hash(); + hash_value.to_string() + } + /// Returns the password length. + pub fn password_length(&self) -> usize { + self.passphrase.len() + } + /// Returns the hash length. + pub fn hash_length(&self) -> usize { + self.hash().len() + } + /// Calculates the entropy of the generated passphrase. + /// Returns the entropy as a f64. + pub fn entropy(&self) -> f64 { + let mut hash = Hash::new(); + hash.set_password(&self.passphrase); + hash.entropy() + } +} + +impl std::fmt::Display for Password { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.passphrase) + } +} + +impl Default for Password { + fn default() -> Self { + Self::new(3, "-") + } +} diff --git a/src/random.rs b/src/random.rs index c191444c..65562c7f 100644 --- a/src/random.rs +++ b/src/random.rs @@ -1,4 +1,4 @@ -//! # Core random number generator +//! # Core Random number generator //! //! This crate provides a random number generator based on the linear congruential generator algorithm with the golden ratio as the multiplier. //! @@ -14,68 +14,181 @@ use std::time::SystemTime; /// algorithm with the golden ratio as the multiplier. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Random { - // The seed for the random number generator seed: u32, } impl Random { - /// Creates a new `Random` struct with a seed based on the current - /// system time. + /// Creates a new random number generator. pub fn new() -> Self { - // Get the current system time in milliseconds let seed = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_millis() as u32; Self { seed } } - - /// Generates a random number using the linear congruential - /// generator algorithm. The multiplier for the algorithm is the - /// golden ratio. + /// Generates a random number between the min and max values. pub fn random(&mut self) -> u32 { - // The multiplier for the linear congruential generator - // algorithm let golden_ratio = 1140071478; - // Update the seed with the next value in the sequence self.seed = self.seed.wrapping_mul(golden_ratio).wrapping_add(12345); - // Return the upper 15 bits of the seed as the random number (self.seed >> 16) & 0x7FFF } - - /// Generates a pseudo-random number by XORing the last 31 random - /// numbers together. + /// Generates a random number between the min and max values. pub fn pseudo(&mut self) -> u32 { let mut res = self.random(); - let mut rng = Random::default(); - // XOR the last 31 random numbers together to generate the - // pseudo-random number for _ in 0..31 { - res ^= rng.random(); + res ^= self.random(); } res } - - /// Generates a vector of random bytes of a given length. + /// Generates a random byte. pub fn bytes(&mut self, len: usize) -> Vec { let mut res = Vec::with_capacity(len); - let mut rng = Random::default(); for _ in 0..len { - res.push(rng.random() as u8); + res.push(self.random() as u8); } res } - /// Generates a random floating point number between 0 and 1. + /// Generates a random float between 0 and 1. pub fn float(&mut self) -> f32 { - let mut rng = Random::default(); - rng.random() as f32 / 0x7FFF as f32 + self.random() as f32 / 0x7FFF as f32 } - /// Generates a random integer between a minimum and maximum value. + /// Generates a random integer between the min and max values. pub fn int(&mut self, min: i32, max: i32) -> i32 { - let mut rng = Random::default(); - (rng.random() as f32 / 0x7FFF as f32 * (max - min) as f32) as i32 + min + (self.float() * (max - min) as f32) as i32 + min + } + /// Generates a random boolean value. + pub fn bool(&mut self) -> bool { + self.random() % 2 == 0 + } + /// Generates a random character. + pub fn char(&mut self) -> char { + let mut res = self.random() % 26; + if self.random() % 2 == 0 { + res += 65; + } else { + res += 97; + } + res as u8 as char + } + /// Generates a random number between the min and max values. + pub fn range(&mut self, min: i32, max: i32) -> i32 { + // Generate a random float between 0 and 1 + let random_float = self.float(); + // Calculate the range between the min and max values + let range = (max - min) as f32; + // Multiply the range by the random float and add the min value + let random_number = (range * random_float) as i32 + min; + random_number + } + + // pub fn choose(&mut self, values: &[T]) -> Option<&T> { + /// Chooses a random value from a slice of values. + pub fn choose<'a, T>(&'a mut self, values: &'a [T]) -> Option<&T> { + if values.is_empty() { + return None; + } + // Generate a random index between 0 and the length of the slice + let index = self.range(0, values.len() as i32 - 1) as usize; + Some(&values[index]) } } +// pub struct Random { +// // The seed for the random number generator +// seed: u32, +// } + +// impl Random { +// /// Creates a new `Random` struct with a seed based on the current +// /// system time. +// pub fn new() -> Self { +// // Get the current system time in milliseconds +// let seed = SystemTime::now() +// .duration_since(SystemTime::UNIX_EPOCH) +// .unwrap() +// .as_millis() as u32; +// Self { seed } +// } + +// /// Generates a random number using the linear congruential +// /// generator algorithm. The multiplier for the algorithm is the +// /// golden ratio. +// pub fn random(&mut self) -> u32 { +// // The multiplier for the linear congruential generator +// // algorithm +// let golden_ratio = 1140071478; +// // Update the seed with the next value in the sequence +// self.seed = self.seed.wrapping_mul(golden_ratio).wrapping_add(12345); +// // Return the upper 15 bits of the seed as the random number +// (self.seed >> 16) & 0x7FFF +// } + +// /// Generates a pseudo-random number by XORing the last 31 random +// /// numbers together. +// pub fn pseudo(&mut self) -> u32 { +// let mut res = self.random(); +// let mut rng = Random::default(); +// // XOR the last 31 random numbers together to generate the +// // pseudo-random number +// for _ in 0..31 { +// res ^= rng.random(); +// } +// res +// } + +// /// Generates a vector of random bytes of a given length. +// pub fn bytes(&mut self, len: usize) -> Vec { +// let mut res = Vec::with_capacity(len); +// let mut rng = Random::default(); +// for _ in 0..len { +// res.push(rng.random() as u8); +// } +// res +// } +// /// Generates a random floating point number between 0 and 1. +// pub fn float(&mut self) -> f32 { +// let mut rng = Random::default(); +// rng.random() as f32 / 0x7FFF as f32 +// } +// /// Generates a random integer between a minimum and maximum value. +// pub fn int(&mut self, min: i32, max: i32) -> i32 { +// let mut rng = Random::default(); +// (rng.random() as f32 / 0x7FFF as f32 * (max - min) as f32) as i32 + min +// } + +// /// Generates a random boolean value. +// pub fn bool(&mut self) -> bool { +// let mut rng = Random::default(); +// rng.random() % 2 == 0 +// } + +// /// Generates a random character. +// pub fn char(&mut self) -> char { +// let mut rng = Random::default(); +// let mut res = rng.random() % 26; +// if rng.random() % 2 == 0 { +// res += 65; +// } else { +// res += 97; +// } +// res as u8 as char +// } + +// /// Generates a random value in a given range. +// pub fn range(&mut self, min: i32, max: i32) -> i32 { +// let mut rng = Random::default(); +// rng.int(min, max) +// } +// pub fn choose(&self) -> u32 { +// let mut rng = Random::default(); +// let mut res = rng.random() % 26; +// if rng.random() % 2 == 0 { +// res += 65; +// } else { +// res += 97; +// } +// res +// } +// } impl std::fmt::Display for Random { /// Formats the `Random` struct as a string for display. diff --git a/tests/hash.rs b/tests/hash.rs new file mode 100644 index 00000000..2b7718e5 --- /dev/null +++ b/tests/hash.rs @@ -0,0 +1,79 @@ +#[cfg(test)] +mod tests { + use mini_functions::hash::Hash; + + #[test] + fn test_new() { + let hash = Hash::new(); + assert_eq!(hash.password(), ""); + assert_eq!(hash.hash(), ""); + } + + #[test] + fn test_default() { + let hash = Hash::default(); + assert_eq!(hash.password(), ""); + assert_eq!(hash.hash(), ""); + } + + #[test] + fn test_generate_hash() { + let hash = Hash::new(); + let hash = hash.generate_hash(); + assert_eq!(hash.len(), 64); + } + + #[test] + fn test_verify() { + let mut hash = Hash::new(); + hash.set_password("password"); + assert!(hash.verify( + "7f2611ba158b6dcea4a69c229c303358c5e04493abeadee106a4bfa464d55787", + "password", + )); + } + + #[test] + fn test_set_password() { + let mut hash = Hash::new(); + hash.set_password("password"); + assert_eq!(hash.password(), "password"); + } + #[test] + fn test_set_hash() { + let mut hash = Hash::new(); + hash.set_hash("hash"); + assert_eq!(hash.hash(), "hash"); + } + + #[test] + fn test_password() { + let mut hash = Hash::new(); + hash.set_password("password"); + assert_eq!(hash.password(), "password"); + } + #[test] + fn test_password_length() { + let mut hash = Hash::new(); + hash.set_password("password"); + assert_eq!(hash.password_length(), 8); + } + #[test] + fn test_hash() { + let mut hash = Hash::new(); + hash.set_hash("hash"); + assert_eq!(hash.hash(), "hash"); + } + #[test] + fn test_hash_length() { + let mut hash = Hash::new(); + hash.set_hash("hash"); + assert_eq!(hash.hash_length(), 4); + } + #[test] + fn test_entropy() { + let mut hash = Hash::new(); + hash.set_password("password"); + assert!(hash.entropy() > 0.0); + } +} diff --git a/tests/password.rs b/tests/password.rs new file mode 100644 index 00000000..c3f8faef --- /dev/null +++ b/tests/password.rs @@ -0,0 +1,31 @@ +#[cfg(test)] +mod tests { + use mini_functions::password::Password; + + #[test] + fn test_password() { + let password = Password::new(3, "-"); + println!("{}", password); + } + #[test] + fn test_passphrase() { + let password = Password::new(3, "-"); + println!("{}", password.passphrase()); + } + #[test] + fn test_set_passphrase() { + let mut password = Password::new(3, "-"); + password.set_passphrase("test"); + println!("{}", password.passphrase()); + } + #[test] + fn test_len() { + let password = Password::new(3, "-"); + println!("{}", password.len()); + } + #[test] + fn test_is_empty() { + let password = ""; + println!("{}", password.is_empty()); + } +} From 7ee10d4c53c66e2836f83d8726e04c0bdadc00aa Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 00:39:27 +0000 Subject: [PATCH 02/13] fix(random): unnecessary `let` binding, returning the expression directly --- src/random.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/random.rs b/src/random.rs index 65562c7f..cb157502 100644 --- a/src/random.rs +++ b/src/random.rs @@ -77,8 +77,7 @@ impl Random { // Calculate the range between the min and max values let range = (max - min) as f32; // Multiply the range by the random float and add the min value - let random_number = (range * random_float) as i32 + min; - random_number + (range * random_float) as i32 + min } // pub fn choose(&mut self, values: &[T]) -> Option<&T> { From 881540ee3e3874bc1b1b90487b449a642945c916 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 00:45:29 +0000 Subject: [PATCH 03/13] chore(random): cleanup and sorting methods alphabetically --- src/random.rs | 179 +++++++++++++------------------------------------- 1 file changed, 45 insertions(+), 134 deletions(-) diff --git a/src/random.rs b/src/random.rs index cb157502..87778d67 100644 --- a/src/random.rs +++ b/src/random.rs @@ -18,28 +18,11 @@ pub struct Random { } impl Random { - /// Creates a new random number generator. - pub fn new() -> Self { - let seed = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u32; - Self { seed } - } - /// Generates a random number between the min and max values. - pub fn random(&mut self) -> u32 { - let golden_ratio = 1140071478; - self.seed = self.seed.wrapping_mul(golden_ratio).wrapping_add(12345); - (self.seed >> 16) & 0x7FFF - } - /// Generates a random number between the min and max values. - pub fn pseudo(&mut self) -> u32 { - let mut res = self.random(); - for _ in 0..31 { - res ^= self.random(); - } - res + /// Generates a random boolean value. + pub fn bool(&mut self) -> bool { + self.random() % 2 == 0 } + /// Generates a random byte. pub fn bytes(&mut self, len: usize) -> Vec { let mut res = Vec::with_capacity(len); @@ -48,18 +31,7 @@ impl Random { } res } - /// Generates a random float between 0 and 1. - pub fn float(&mut self) -> f32 { - self.random() as f32 / 0x7FFF as f32 - } - /// Generates a random integer between the min and max values. - pub fn int(&mut self, min: i32, max: i32) -> i32 { - (self.float() * (max - min) as f32) as i32 + min - } - /// Generates a random boolean value. - pub fn bool(&mut self) -> bool { - self.random() % 2 == 0 - } + /// Generates a random character. pub fn char(&mut self) -> char { let mut res = self.random() % 26; @@ -70,15 +42,6 @@ impl Random { } res as u8 as char } - /// Generates a random number between the min and max values. - pub fn range(&mut self, min: i32, max: i32) -> i32 { - // Generate a random float between 0 and 1 - let random_float = self.float(); - // Calculate the range between the min and max values - let range = (max - min) as f32; - // Multiply the range by the random float and add the min value - (range * random_float) as i32 + min - } // pub fn choose(&mut self, values: &[T]) -> Option<&T> { /// Chooses a random value from a slice of values. @@ -90,104 +53,52 @@ impl Random { let index = self.range(0, values.len() as i32 - 1) as usize; Some(&values[index]) } -} -// pub struct Random { -// // The seed for the random number generator -// seed: u32, -// } -// impl Random { -// /// Creates a new `Random` struct with a seed based on the current -// /// system time. -// pub fn new() -> Self { -// // Get the current system time in milliseconds -// let seed = SystemTime::now() -// .duration_since(SystemTime::UNIX_EPOCH) -// .unwrap() -// .as_millis() as u32; -// Self { seed } -// } - -// /// Generates a random number using the linear congruential -// /// generator algorithm. The multiplier for the algorithm is the -// /// golden ratio. -// pub fn random(&mut self) -> u32 { -// // The multiplier for the linear congruential generator -// // algorithm -// let golden_ratio = 1140071478; -// // Update the seed with the next value in the sequence -// self.seed = self.seed.wrapping_mul(golden_ratio).wrapping_add(12345); -// // Return the upper 15 bits of the seed as the random number -// (self.seed >> 16) & 0x7FFF -// } + /// Generates a random float between 0 and 1. + pub fn float(&mut self) -> f32 { + self.random() as f32 / 0x7FFF as f32 + } -// /// Generates a pseudo-random number by XORing the last 31 random -// /// numbers together. -// pub fn pseudo(&mut self) -> u32 { -// let mut res = self.random(); -// let mut rng = Random::default(); -// // XOR the last 31 random numbers together to generate the -// // pseudo-random number -// for _ in 0..31 { -// res ^= rng.random(); -// } -// res -// } + /// Generates a random integer between the min and max values. + pub fn int(&mut self, min: i32, max: i32) -> i32 { + (self.float() * (max - min) as f32) as i32 + min + } -// /// Generates a vector of random bytes of a given length. -// pub fn bytes(&mut self, len: usize) -> Vec { -// let mut res = Vec::with_capacity(len); -// let mut rng = Random::default(); -// for _ in 0..len { -// res.push(rng.random() as u8); -// } -// res -// } -// /// Generates a random floating point number between 0 and 1. -// pub fn float(&mut self) -> f32 { -// let mut rng = Random::default(); -// rng.random() as f32 / 0x7FFF as f32 -// } -// /// Generates a random integer between a minimum and maximum value. -// pub fn int(&mut self, min: i32, max: i32) -> i32 { -// let mut rng = Random::default(); -// (rng.random() as f32 / 0x7FFF as f32 * (max - min) as f32) as i32 + min -// } + /// Creates a new random number generator. + pub fn new() -> Self { + let seed = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u32; + Self { seed } + } -// /// Generates a random boolean value. -// pub fn bool(&mut self) -> bool { -// let mut rng = Random::default(); -// rng.random() % 2 == 0 -// } + /// Generates a random number between the min and max values. + pub fn pseudo(&mut self) -> u32 { + let mut res = self.random(); + for _ in 0..31 { + res ^= self.random(); + } + res + } -// /// Generates a random character. -// pub fn char(&mut self) -> char { -// let mut rng = Random::default(); -// let mut res = rng.random() % 26; -// if rng.random() % 2 == 0 { -// res += 65; -// } else { -// res += 97; -// } -// res as u8 as char -// } + /// Generates a random number between the min and max values. + pub fn random(&mut self) -> u32 { + let golden_ratio = 1140071478; + self.seed = self.seed.wrapping_mul(golden_ratio).wrapping_add(12345); + (self.seed >> 16) & 0x7FFF + } -// /// Generates a random value in a given range. -// pub fn range(&mut self, min: i32, max: i32) -> i32 { -// let mut rng = Random::default(); -// rng.int(min, max) -// } -// pub fn choose(&self) -> u32 { -// let mut rng = Random::default(); -// let mut res = rng.random() % 26; -// if rng.random() % 2 == 0 { -// res += 65; -// } else { -// res += 97; -// } -// res -// } -// } + /// Generates a random number between the min and max values. + pub fn range(&mut self, min: i32, max: i32) -> i32 { + // Generate a random float between 0 and 1 + let random_float = self.float(); + // Calculate the range between the min and max values + let range = (max - min) as f32; + // Multiply the range by the random float and add the min value + (range * random_float) as i32 + min + } +} impl std::fmt::Display for Random { /// Formats the `Random` struct as a string for display. From 88363c42503a78654dda5ae086cc879a5170d218 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 03:10:32 +0000 Subject: [PATCH 04/13] feat(wip): refactoring password, random etc --- .vscode/settings.json | 1 + examples/password.rs | 5 ++-- src/common/constant.rs | 14 ++++++++-- src/common/mod.rs | 7 +++-- src/password.rs | 60 +++++++++++++++++++++++++++++++++++------- src/random.rs | 6 ++++- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8f6b03e9..b7e18926 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "datetime", "dylib", "Funct", + "getrandom", "github", "GITHUB", "gnubin", diff --git a/examples/password.rs b/examples/password.rs index 136e4ce8..04cb8bd5 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -1,7 +1,8 @@ +use mini_functions::common::constant::SPECIAL_CHARS; use mini_functions::password::Password; fn main() { - let password = Password::new(3, "-"); + let password = Password::new(4, "-", SPECIAL_CHARS.to_vec()); println!( "đŸĻ€ Password::default(): ✅ {}", Password::default() @@ -11,7 +12,7 @@ fn main() { "đŸĻ€ Password::passphrase(): ✅ {}", password.passphrase() ); - let mut password = Password::new(3, "-"); + let mut password = Password::new(4, "-", SPECIAL_CHARS.to_vec()); println!("đŸĻ€ Password::set_passphrase"); println!( " 🔓 Original passphrase: ✅ {}", diff --git a/src/common/constant.rs b/src/common/constant.rs index 438d3c0b..289bf948 100644 --- a/src/common/constant.rs +++ b/src/common/constant.rs @@ -14,7 +14,7 @@ // pub const GITHUB: &str = "https://github.com/sebastienrousseau/mini-functions"; /// The HASH_COST constant contains the cost of the hash. -// pub const HASH_COST: u32 = 8; +pub const HASH_COST: u32 = 8; /// The HASH_LENGTH constant contains the length of the hash. // pub const HASH_LENGTH: usize = 32; @@ -23,4 +23,14 @@ // pub const HOMEPAGE: &str = "https://minifunctions.com/"; /// The SPECIAL constant contains the special characters. -pub const SPECIAL: &[u8] = b"!@#$%^&*()_+-=[]{};':,./<>?"; +// pub const SPECIAL: &[u8] = b"!@#$%^&*()_+-=[]{};':,./<>?"; +// pub const SPECIAL: &[u8] = b"!@#$%^&*()_+-=[]{}|;':\"<>,.?/~`"; + +/// The SPECIAL_CHARS constant contains the special characters. +pub const SPECIAL_CHARS: &[char] = &[ + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '-', '=', '[', ']', '{', '}', '|', + ';', ':', '"', '<', '>', ',', '.', '?', '/', '~', '`', +]; + +/// The GOLDEN_RATIO constant contains the golden ratio value. +pub const GOLDEN_RATIO: u32 = 0x9E3779B9; diff --git a/src/common/mod.rs b/src/common/mod.rs index dee51940..77d40544 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,4 +1,7 @@ pub use crate::common; -pub(crate) mod constant; -pub(crate) mod words; +/// Path: src/common/mod.rs +pub mod constant; + +/// Path: src/common/mod.rs +pub mod words; diff --git a/src/password.rs b/src/password.rs index 9b4c0b8a..8f6b5d34 100644 --- a/src/password.rs +++ b/src/password.rs @@ -15,6 +15,8 @@ use std::collections::HashSet; pub struct Password { /// The generated passphrase. passphrase: String, + /// The special characters to use for replacing letters in words. + special_chars: Vec, } impl Password { @@ -28,18 +30,18 @@ impl Password { self.passphrase.len() } /// Generates a random passphrase. - pub fn new(len: u8, separator: &str) -> Self { + pub fn new(len: u8, separator: &str, special_chars: Vec) -> Self { // Setup a random number generator let mut rng = Random::default(); // Generate a random number between 0 and 99. - let mut nb: i32 = rng.range(0, 99); + let mut nb: i32 = rng.range(HASH_COST.try_into().unwrap(), 99); // Create a new vector to store the words in. let mut words: Vec = Vec::new(); // Convert the special characters to a vector of chars. - let ascii: Vec = SPECIAL.iter().map(|&c| c as char).collect(); + let ascii: Vec = SPECIAL_CHARS.iter().map(|&c| c as char).collect(); // Create a new HashSet to store the generated words. let mut word_set = HashSet::new(); @@ -47,7 +49,7 @@ impl Password { // Generate `len` random words from the word list. while words.len() < len as usize { // Choose a random word from the list. - let word = if let Some(w) = Random::choose(&mut rng, WORD_LIST) { + let mut word = if let Some(w) = Random::choose(&mut rng, WORD_LIST) { // If a word was found, return it. w } else { @@ -55,6 +57,17 @@ impl Password { "" }; + // Ensure that the random word is not already present in the vector of words + while words.contains(&word.to_string()) { + word = if let Some(w) = Random::choose(&mut rng, WORD_LIST) { + // If a word was found, return it. + w + } else { + // If no word was found, return an empty string. + "" + }; + } + // Check if the word is already in the HashSet. If it is, skip // to the next iteration. if word_set.contains(word) { @@ -64,15 +77,31 @@ impl Password { // Add the word to the HashSet. word_set.insert(word); + // Generate a random uppercase or lowercase letter + let mut random_letter = rng.char(); + + // Ensure that the random letter is not already present in the word + while word.contains(random_letter) { + random_letter = rng.char(); + } + // Convert the word to title case and add a number to the end let word = format!( - "{}{}{}", + "{}{}{}{}", word.to_case(Case::Title), + random_letter, Random::choose(&mut rng, &ascii).unwrap(), nb ); // Generate a new random number between 0 and 99. - nb = rng.range(0, 99); + nb = rng.range(HASH_COST.try_into().unwrap(), 99); + + // Replace a random letter in the word with a special character from the list. + let mut chars: Vec = word.chars().collect(); + let index = rng.range(0, chars.len().try_into().unwrap()) as usize; + chars[index] = *Random::choose(&mut rng, &special_chars).unwrap(); + let word = chars.into_iter().collect::(); + // Add the word to the vector of words. words.push(word); } @@ -81,7 +110,10 @@ impl Password { let pass = words.join(separator); // Return the password and hash - Self { passphrase: pass } + Self { + passphrase: pass, + special_chars, + } } /// Returns the generated passphrase. pub fn passphrase(&self) -> &str { @@ -94,7 +126,11 @@ impl Password { /// Returns the hash of the generated passphrase. pub fn hash(&self) -> String { let mut hash = Hash::new(); - hash.set_password(&self.passphrase); + hash.set_password(&format!( + "{}{}", + self.passphrase, + self.special_chars.iter().collect::() + )); let hash_value = hash.hash(); hash_value.to_string() } @@ -110,7 +146,11 @@ impl Password { /// Returns the entropy as a f64. pub fn entropy(&self) -> f64 { let mut hash = Hash::new(); - hash.set_password(&self.passphrase); + hash.set_password(&format!( + "{}{}", + self.passphrase, + self.special_chars.iter().collect::() + )); hash.entropy() } } @@ -123,6 +163,6 @@ impl std::fmt::Display for Password { impl Default for Password { fn default() -> Self { - Self::new(3, "-") + Self::new(4, "-", SPECIAL_CHARS.to_vec()) } } diff --git a/src/random.rs b/src/random.rs index 87778d67..654b7678 100644 --- a/src/random.rs +++ b/src/random.rs @@ -10,6 +10,9 @@ use std::time::SystemTime; +// pub use crate::common; +// use common::constant::GOLDEN_RATIO; + /// A random number generator based on the linear congruential generator /// algorithm with the golden ratio as the multiplier. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] @@ -26,8 +29,9 @@ impl Random { /// Generates a random byte. pub fn bytes(&mut self, len: usize) -> Vec { let mut res = Vec::with_capacity(len); + let mut rng = Random::default(); for _ in 0..len { - res.push(self.random() as u8); + res.push(rng.random() as u8); } res } From 2a43567819a20c31ac8d18087dd916a76804d99e Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 08:57:27 +0000 Subject: [PATCH 05/13] fix(password): casting to the same type is unnecessary --- examples/password.rs | 2 +- src/common/mod.rs | 11 ++++++----- src/date.rs | 19 +++++++++++++++++++ src/hash.rs | 1 + src/lib.rs | 10 ++++++---- src/password.rs | 9 ++++----- src/qrcode.rs | 1 + src/random.rs | 4 +--- tests/password.rs | 9 +++++---- 9 files changed, 44 insertions(+), 22 deletions(-) diff --git a/examples/password.rs b/examples/password.rs index 04cb8bd5..025db467 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -18,7 +18,7 @@ fn main() { " 🔓 Original passphrase: ✅ {}", password.passphrase() ); - password.set_passphrase("M1n1Funct1()ns-N3wP@ssphr4s3-Ex@mpl3"); + password.set_passphrase("M1n1Funct1()ns-N3wP@s5phr4s3-Ex@mpl3"); println!( " 🔐 Updated passphrase: ✅ {}", password.passphrase() diff --git a/src/common/mod.rs b/src/common/mod.rs index 77d40544..6ab3f4d1 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,7 +1,8 @@ -pub use crate::common; +/// Commonly used constants +pub mod constant; // src/common/constant.rs -/// Path: src/common/mod.rs -pub mod constant; +/// Commonly used word lists +pub mod words; // src/common/words.rs -/// Path: src/common/mod.rs -pub mod words; +/// Commonly used functions and constants +pub use {constant::*, words::*}; diff --git a/src/date.rs b/src/date.rs index 90e63b89..068ec615 100644 --- a/src/date.rs +++ b/src/date.rs @@ -8,12 +8,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +#[cfg(feature = "time")] extern crate time; + use time::OffsetDateTime; /// Date Utility /// /// By default, the current date and time in UTC is returned. +#[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Date {} @@ -29,36 +32,42 @@ impl Date { pub fn day() -> String { OffsetDateTime::now_utc().day().to_string() } + /// Get the clock hour in the stored offset. /// /// The hour is a number from 0 to 23. 0 is midnight. 12 is noon. pub fn hour() -> String { OffsetDateTime::now_utc().hour().to_string() } + /// Get the ISO week number of the date in the stored offset. /// /// The returned value will always be in the range 1..=53. pub fn iso_week() -> String { OffsetDateTime::now_utc().iso_week().to_string() } + /// Get the microsecond of the second in the stored offset. /// /// The returned value will always be in the range 0..1_000_000. pub fn microsecond() -> String { OffsetDateTime::now_utc().microsecond().to_string() } + /// Get the milliseconds within the second in the stored offset. /// /// The returned value will always be in the range 0..1_000. pub fn millisecond() -> String { OffsetDateTime::now_utc().millisecond().to_string() } + /// Get the minute within the hour in the stored offset. /// /// The returned value will always be in the range 0..60. pub fn minute() -> String { OffsetDateTime::now_utc().minute().to_string() } + /// Get the month of the date in the stored offset. /// /// The month is a number from 1 to 12. @@ -66,12 +75,14 @@ impl Date { pub fn month() -> String { OffsetDateTime::now_utc().month().to_string() } + /// Get the nanoseconds within the second in the stored offset. /// /// The returned value will always be in the range 0..1_000_000_000. pub fn nanosecond() -> String { OffsetDateTime::now_utc().nanosecond().to_string() } + /// Create a new OffsetDateTime in the ISO 8601 format. pub fn iso_8601() -> String { let now = OffsetDateTime::now_utc(); @@ -87,40 +98,48 @@ impl Date { ); iso_8601 } + /// Create a new OffsetDateTime with the current date and time (UTC) pub fn now_utc() -> String { OffsetDateTime::now_utc().to_string() } + /// Get the UtcOffset. pub fn offset() -> String { OffsetDateTime::now_utc().offset().to_string() } + /// Get the day of the year of the date in the stored offset. /// /// The returned value will always be in the range 1..=366. pub fn ordinal() -> String { OffsetDateTime::now_utc().ordinal().to_string() } + /// Get the second within the minute in the stored offset. /// /// The returned value will always be in the range 0..60. pub fn second() -> String { OffsetDateTime::now_utc().second().to_string() } + /// Get the Time in the stored offset. pub fn time() -> String { OffsetDateTime::now_utc().time().to_string() } + /// Get the Unix timestamp. pub fn timestamp() -> String { OffsetDateTime::now_utc().unix_timestamp().to_string() } + /// Get the weekday of the date in the stored offset. /// /// This current uses Zellers congruence internally. pub fn weekday() -> String { OffsetDateTime::now_utc().weekday().to_string() } + /// Get the year of the date in the stored offset. pub fn year() -> String { OffsetDateTime::now_utc().year().to_string() diff --git a/src/hash.rs b/src/hash.rs index 4ad993bf..db9fc193 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -12,6 +12,7 @@ use blake3::Hasher; /// A struct for storing and verifying hashed passwords based on /// the [blake3](https://crates.io/crates/blake3) crate. +#[non_exhaustive] #[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Hash { /// The password. diff --git a/src/lib.rs b/src/lib.rs index 98481b68..974fc310 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,9 @@ //! #![warn(missing_docs)] #![doc( - html_logo_url = "https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/icons/ico-mini-functions.svg" + html_logo_url = "https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/icons/ico-mini-functions.svg", + html_favicon_url = "https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/icons/ico-mini-functions.svg", + html_root_url = "https://docs.rs/mini-functions" )] #![crate_name = "mini_functions"] #![crate_type = "lib"] @@ -63,6 +65,9 @@ #![crate_type = "dylib"] #[macro_use] +/// Provides a set of common constants used in the application +pub mod common; + /// Provides a set of utility functions for working with dates and times pub mod date; @@ -86,6 +91,3 @@ pub mod random; /// Provides a set of utility functions for working with UUIDs pub mod uuid; - -/// Provides a set of common constants used in the application -pub mod common; diff --git a/src/password.rs b/src/password.rs index 8f6b5d34..6f55737d 100644 --- a/src/password.rs +++ b/src/password.rs @@ -1,16 +1,15 @@ -pub use crate::common; +use crate::common::constant::{HASH_COST, SPECIAL_CHARS}; +use crate::common::words::WORD_LIST; use crate::hash::Hash; use crate::random::Random; -use common::constant::*; -use common::words::*; use convert_case::{Case, Casing}; use std::collections::HashSet; -// use std::path::Path; /// A random password / passphrase generator. The generated password /// is a string of three words separated by hyphens. Each word is /// between 6 and 8 characters long. The first character of each word /// is capitalized. +#[non_exhaustive] #[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Password { /// The generated passphrase. @@ -41,7 +40,7 @@ impl Password { let mut words: Vec = Vec::new(); // Convert the special characters to a vector of chars. - let ascii: Vec = SPECIAL_CHARS.iter().map(|&c| c as char).collect(); + let ascii: Vec = SPECIAL_CHARS.iter().map(|&c| c).collect(); // Create a new HashSet to store the generated words. let mut word_set = HashSet::new(); diff --git a/src/qrcode.rs b/src/qrcode.rs index f1549d28..95a9639c 100644 --- a/src/qrcode.rs +++ b/src/qrcode.rs @@ -15,6 +15,7 @@ use image::{ImageBuffer, Rgb, RgbImage}; use qrcode::render::svg; use qrcode::QrCode; +#[non_exhaustive] #[derive(Debug, Clone, Default, PartialEq)] /// The `QRCode` struct represents a QR code image. pub struct QRCode { diff --git a/src/random.rs b/src/random.rs index 654b7678..b56b49a1 100644 --- a/src/random.rs +++ b/src/random.rs @@ -10,11 +10,9 @@ use std::time::SystemTime; -// pub use crate::common; -// use common::constant::GOLDEN_RATIO; - /// A random number generator based on the linear congruential generator /// algorithm with the golden ratio as the multiplier. +#[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub struct Random { seed: u32, diff --git a/tests/password.rs b/tests/password.rs index c3f8faef..272a3551 100644 --- a/tests/password.rs +++ b/tests/password.rs @@ -1,26 +1,27 @@ #[cfg(test)] mod tests { + use mini_functions::common::constant::SPECIAL_CHARS; use mini_functions::password::Password; #[test] fn test_password() { - let password = Password::new(3, "-"); + let password = Password::new(3, "-", SPECIAL_CHARS.to_vec()); println!("{}", password); } #[test] fn test_passphrase() { - let password = Password::new(3, "-"); + let password = Password::new(3, "-", SPECIAL_CHARS.to_vec()); println!("{}", password.passphrase()); } #[test] fn test_set_passphrase() { - let mut password = Password::new(3, "-"); + let mut password = Password::new(3, "-", SPECIAL_CHARS.to_vec()); password.set_passphrase("test"); println!("{}", password.passphrase()); } #[test] fn test_len() { - let password = Password::new(3, "-"); + let password = Password::new(3, "-", SPECIAL_CHARS.to_vec()); println!("{}", password.len()); } #[test] From 8b2fa3f9d192cac9173e056f118889b6fd35e695 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 09:01:36 +0000 Subject: [PATCH 06/13] fix(password): using an explicit closure for copying elements --- src/password.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/password.rs b/src/password.rs index 6f55737d..2d20835b 100644 --- a/src/password.rs +++ b/src/password.rs @@ -40,7 +40,7 @@ impl Password { let mut words: Vec = Vec::new(); // Convert the special characters to a vector of chars. - let ascii: Vec = SPECIAL_CHARS.iter().map(|&c| c).collect(); + let ascii: Vec = SPECIAL_CHARS.iter().copied().collect(); // Create a new HashSet to store the generated words. let mut word_set = HashSet::new(); From 39528d96c68a4ba404f6f6bd67820e1c80327fd1 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 09:05:32 +0000 Subject: [PATCH 07/13] fix(pasword): called `iter().copied().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable --- src/password.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/password.rs b/src/password.rs index 2d20835b..758183ce 100644 --- a/src/password.rs +++ b/src/password.rs @@ -40,7 +40,7 @@ impl Password { let mut words: Vec = Vec::new(); // Convert the special characters to a vector of chars. - let ascii: Vec = SPECIAL_CHARS.iter().copied().collect(); + let ascii: Vec = SPECIAL_CHARS.to_vec(); // Create a new HashSet to store the generated words. let mut word_set = HashSet::new(); From f485c9c1ec35a79dafd5983f0d968da216600c35 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 10:30:51 +0000 Subject: [PATCH 08/13] doc(readme): content updates for 0.0.6 and sorting methods --- README.md | 153 ++++++++++++++++++++++++++++++++++-------------- src/hash.rs | 76 ++++++++++++------------ src/password.rs | 58 +++++++++--------- 3 files changed, 176 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 25a48034..538ac75c 100644 --- a/README.md +++ b/README.md @@ -52,24 +52,19 @@ integration into a variety of projects and applications. - **Regular Maintenance and Updates** — Regular updates and maintenance to ensure the library stays up-to-date and reliable. -## Requirements 📋 - -`mini-functions` requires Rust **1.57.0** or later. - ## Installation đŸ“Ļ It takes just a few minutes to get up and running with `mini-functions`. -Please check out our [website][0] for more information. You can also -find our documentation on [docs.rs][9] and [lib.rs][10] and our -[crates.io][8] page. -Mini Functions comes with a set of examples that you can use to get -started. To run the examples, clone the repository and run the following -command: +### Requirements -```shell -cargo run --example -``` +`mini-functions` requires Rust **1.57.0** or later. + +### Documentation + +> ℹī¸ **Info:** Please check out our [website][0] for more information +and find our documentation on [docs.rs][9], [lib.rs][10] and +[crates.io][8]. ## Usage 📖 @@ -90,34 +85,34 @@ use mini_functions::*; then you can use the functions in your application code. +### Examples + +`Mini Functions` comes with a set of examples that you can use to get +started. The examples are located in the `examples` directory of the +project. To run the examples, clone the repository and run the following +command in your terminal from the project root directory. + +```shell +cargo run --example date +``` + +> 💡 **Note:** The examples available are date, hash, log, password, qrcode, random and uuid. + ## The Functions library 📚 `Mini Functions` is a library of functions for Rust that provides a collection of tools for working with various aspects of a Rust -application. - -The functions in `Mini Functions` are optimized for speed and -efficiency, making them useful for a variety of applications, including -those that require fast performance or that need to handle large amounts -of data. +application. The `mini-functions` library consists of the following +`non-exhaustive` functions: -The `mini-functions` library consists of the following functions: +### 1) Date and time functions -- The **[Date and time functions](#date-and-time-functions)** are used - to retrieve and manipulate information about dates and times. -- The **[Log functions](#log-functions)** are used to log messages to - the console. -- The **[QRCode functions](#qrcode-functions)** are used to generate - QRCode images and data. -- The **[Random number functions](#random-number-functions)** are used - to generate random numbers in a variety of sizes and formats. -- The **[UUID functions](#uuid-functions)** are used to generate UUIDs - (Universally Unique Identifiers). +The **Date and time functions** are used to retrieve and manipulate +information about dates and times. -The following tables provide a brief description of each function in the -`mini-functions` library. - -### Date and time functions + +
+ Open to view the Date and time functions available in the library

| Function | Include File | Function Prototype | Description | | -------- | ------------ | ------------------ | ----------- | @@ -135,15 +130,74 @@ The following tables provide a brief description of each function in the | `Date::timestamp()` | `date.rs` | `fn timestamp()` | Returns the current timestamp. | | `Date::weekday()` | `date.rs` | `fn weekday()` | Returns the current weekday. | | `Date::year()` | `date.rs` | `fn year()` | Returns the current year. | +
+ +### 2) Hash functions + +The **Hash functions** are used to generate hashes for various data +types. -### Log functions + +
+ Open to view the Hash functions available in the library

+ +| Function | Include File | Function Prototype | Description | +| -------- | ------------ | ------------------ | ----------- | +| `Hash::entropy` | `hash.rs` | `fn entropy()` | Returns the entropy of a string. | +| `Hash::generate_hash` | `hash.rs` | `fn generate_hash()` | Generates a hash for a string. | +| `Hash::hash` | `hash.rs` | `fn hash()` | Returns the hash of a string. | +| `Hash::hash_length` | `hash.rs` | `fn hash_length()` | Returns the length of a hash. | +| `Hash::new` | `hash.rs` | `fn new()` | Creates a new hash instance. | +| `Hash::password` | `hash.rs` | `fn password()` | Returns the hash of a password. | +| `Hash::password_length` | `hash.rs` | `fn password_length()` | Returns the length of a password hash. | +| `Hash::set_hash` | `hash.rs` | `fn set_hash()` | Sets the hash for a string. | +| `Hash::set_password` | `hash.rs` | `fn set_password()` | Sets the hash for a password. | +| `Hash::verify` | `hash.rs` | `fn verify()` | Verifies a hash. | +
+ +### 3) Log functions + +The **Log functions** are used to log messages to the console. + + +
+ Open to view the Log functions available in the library

| Function | Include File | Function Prototype | Description | | -------- | ------------ | ------------------ | ----------- | | `Log::log()` | `log.rs` | `fn log()` | Logs a message to the console.| | `Log::new()` | `log.rs` | `fn new()` | Creates a new log instance. | +
+ +### 4) Password functions + +The **Password functions** are used to generate passwords and verify +passwords. + + +
+ Open to view the Password functions available in the library

+ +| Function | Include File | Function Prototype | Description | +| -------- | ------------ | ------------------ | ----------- | +| `Password::entropy` | `password.rs` | `fn entropy()` | Returns the entropy of a string. | +| `Password::hash_length` | `password.rs` | `fn hash_length()` | Returns the length of a hash. | +| `Password::hash` | `password.rs` | `fn hash()` | Returns the hash of a password. | +| `Password::is_empty` | `password.rs` | `fn is_empty()` | Checks if a password is empty. | +| `Password::len` | `password.rs` | `fn len()` | Returns the length of a password. | +| `Password::new` | `password.rs` | `fn new()` | Creates a new password instance. | +| `Password::passphrase` | `password.rs` | `fn passphrase()` | Generates a passphrase. | +| `Password::password_length` | `password.rs` | `fn password_length()` | Returns the length of a password hash. | +| `Password::set_passphrase` | `password.rs` | `fn set_passphrase()` | Sets a passphrase. | +
+ +### 5) QRCode functions -### QRCode functions +The **QRCode functions** are used to generate QRCode images and data. + + +
+ Open to view the QRCode functions available in the library

| Function | Include File | Function Prototype | Description | | -------- | ------------ | ------------------ | ----------- | @@ -155,8 +209,16 @@ The following tables provide a brief description of each function in the | `QRCode::to_png()` | `qrcode.rs` | `fn to_png()` | Converts the QRCode instance to a PNG image. | | `QRCode::to_qrcode()` | `qrcode.rs` | `fn to_qrcode()` | Converts the QRCode instance to a QRCode image. | | `QRCode::to_svg()` | `qrcode.rs` | `fn to_svg()` | Converts the QRCode instance to a SVG image. | +
+ +### 6) Random number functions -### Random number functions +The **Random number functions** are used to +generate random numbers in a variety of sizes and formats. + + +
+ Open to view the Random number functions available in the library

| Function | Include File | Function Prototype | Description | | -------- | ------------ | ------------------ | ----------- | @@ -167,8 +229,16 @@ The following tables provide a brief description of each function in the | `Random::new()` | `random.rs` | `fn new()` | Creates a new `Random` struct with a seed based on the current system time. | | `Random::pseudo()` | `random.rs` | `fn pseudo()` | Generates a pseudo-random number by XORing the last 31 random numbers together. | | `Random::random()` | `random.rs` | `fn random()` | Generates a random number using the linear congruential generator algorithm. The multiplier for the algorithm is the golden ratio. | +
+ +### 7) UUID functions + +The **UUID functions** are used to generate UUIDs (Universally Unique +Identifiers). -### UUID functions + +
+ Open to view the UUID functions available in the library

| Function | Include File | Function Prototype | Description | | -------- | ------------ | ------------------ | ----------- | @@ -176,8 +246,7 @@ The following tables provide a brief description of each function in the | `UUID::uuid_v3()` | `uuid.rs` | `fn uuid_v3()` | Creates a new UUID v3 instance. | | `UUID::uuid_v4()` | `uuid.rs` | `fn uuid_v4()` | Creates a new UUID v4 instance. | | `UUID::uuid_v5()` | `uuid.rs` | `fn uuid_v5()` | Creates a new UUID v5 instance. | - -![divider][divider] +
## Semantic Versioning Policy đŸšĨ @@ -185,8 +254,6 @@ For transparency into our release cycle and in striving to maintain backward compatibility, `Mini Functions` follows [semantic versioning][7]. -![divider][divider] - ## License 📝 The project is licensed under the terms of both the MIT license and the @@ -195,8 +262,6 @@ Apache License (Version 2.0). - [Apache License, Version 2.0][1] - [MIT license][2] -![divider][divider] - ## Contribution 🤝 Unless you explicitly state otherwise, any contribution intentionally diff --git a/src/hash.rs b/src/hash.rs index db9fc193..6d1d42be 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -22,12 +22,18 @@ pub struct Hash { } impl Hash { - /// Returns a new instance of `Hash`. - pub fn new() -> Self { - Self { - password: String::new(), - hash: String::new(), + /// Calculates the entropy of the hash in bits based on the Shannon + /// entropy formula. + /// https://en.wikipedia.org/wiki/Entropy_(information_theory) + /// + pub fn entropy(&self) -> f64 { + // Shannon entropy formula in bits. + let mut entropy = 0.0; + for c in self.hash.chars() { + let p = (c as u8) as f64 / 255.0; + entropy -= p * p.log2(); } + entropy } /// Hashes the password. @@ -38,26 +44,22 @@ impl Hash { hash.to_string() } - /// Verifies the password against the stored hash. - /// - /// Returns `true` if the password and hash match, `false` otherwise. - pub fn verify(&self, hash: &str, password: &str) -> bool { - let mut hasher = Hasher::new(); - hasher.update(password.as_bytes()); - let password_hash = hasher.finalize().to_hex(); - let password_hash_str = password_hash.to_string(); - password_hash_str == hash + /// Returns the hash. + pub fn hash(&self) -> &str { + &self.hash } - /// Sets the password and updates the hash. - pub fn set_password(&mut self, password: &str) { - self.password = password.to_string(); - self.hash = self.generate_hash(); + /// Returns the hash length. + pub fn hash_length(&self) -> usize { + self.hash.len() } - /// Sets the hash. - pub fn set_hash(&mut self, hash: &str) { - self.hash = hash.to_string(); + /// Returns a new instance of `Hash`. + pub fn new() -> Self { + Self { + password: String::new(), + hash: String::new(), + } } /// Returns the password. @@ -70,27 +72,25 @@ impl Hash { self.password.len() } - /// Returns the hash. - pub fn hash(&self) -> &str { - &self.hash + /// Sets the hash. + pub fn set_hash(&mut self, hash: &str) { + self.hash = hash.to_string(); } - /// Returns the hash length. - pub fn hash_length(&self) -> usize { - self.hash.len() + /// Sets the password and updates the hash. + pub fn set_password(&mut self, password: &str) { + self.password = password.to_string(); + self.hash = self.generate_hash(); } - /// Calculates the entropy of the hash in bits based on the Shannon - /// entropy formula. - /// https://en.wikipedia.org/wiki/Entropy_(information_theory) + /// Verifies the password against the stored hash. /// - pub fn entropy(&self) -> f64 { - // Shannon entropy formula in bits. - let mut entropy = 0.0; - for c in self.hash.chars() { - let p = (c as u8) as f64 / 255.0; - entropy -= p * p.log2(); - } - entropy + /// Returns `true` if the password and hash match, `false` otherwise. + pub fn verify(&self, hash: &str, password: &str) -> bool { + let mut hasher = Hasher::new(); + hasher.update(password.as_bytes()); + let password_hash = hasher.finalize().to_hex(); + let password_hash_str = password_hash.to_string(); + password_hash_str == hash } } diff --git a/src/password.rs b/src/password.rs index 758183ce..8f2eaf47 100644 --- a/src/password.rs +++ b/src/password.rs @@ -19,6 +19,32 @@ pub struct Password { } impl Password { + /// Calculates the entropy of the generated passphrase. + /// Returns the entropy as a f64. + pub fn entropy(&self) -> f64 { + let mut hash = Hash::new(); + hash.set_password(&format!( + "{}{}", + self.passphrase, + self.special_chars.iter().collect::() + )); + hash.entropy() + } + /// Returns the hash of the generated passphrase. + pub fn hash(&self) -> String { + let mut hash = Hash::new(); + hash.set_password(&format!( + "{}{}", + self.passphrase, + self.special_chars.iter().collect::() + )); + let hash_value = hash.hash(); + hash_value.to_string() + } + /// Returns the hash length. + pub fn hash_length(&self) -> usize { + self.hash().len() + } /// Returns true if the generated passphrase is empty. /// Returns false if the generated passphrase is not empty. pub fn is_empty(&self) -> bool { @@ -118,39 +144,13 @@ impl Password { pub fn passphrase(&self) -> &str { &self.passphrase } - /// Sets the generated passphrase. - pub fn set_passphrase(&mut self, passphrase: &str) { - self.passphrase = passphrase.to_string(); - } - /// Returns the hash of the generated passphrase. - pub fn hash(&self) -> String { - let mut hash = Hash::new(); - hash.set_password(&format!( - "{}{}", - self.passphrase, - self.special_chars.iter().collect::() - )); - let hash_value = hash.hash(); - hash_value.to_string() - } /// Returns the password length. pub fn password_length(&self) -> usize { self.passphrase.len() } - /// Returns the hash length. - pub fn hash_length(&self) -> usize { - self.hash().len() - } - /// Calculates the entropy of the generated passphrase. - /// Returns the entropy as a f64. - pub fn entropy(&self) -> f64 { - let mut hash = Hash::new(); - hash.set_password(&format!( - "{}{}", - self.passphrase, - self.special_chars.iter().collect::() - )); - hash.entropy() + /// Sets the generated passphrase. + pub fn set_passphrase(&mut self, passphrase: &str) { + self.passphrase = passphrase.to_string(); } } From ed9a499e308b684018a3266dec1522d94b5cdbde Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 10:38:35 +0000 Subject: [PATCH 09/13] doc(readme): minor layout tweaks --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 538ac75c..5df64cc4 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # Mini Functions đŸĻ€ -[![Made With Love][mwl]][6] - -## Highly performant utility and wrapper functions library for Rust 🚀 + +
![Mini Functions][banner] +## Highly performant utility and wrapper functions library for Rust 🚀 + [![Crates.io][crates-badge]][8] [![Lib.rs][libs-badge]][10] [![Docs.rs][docs-badge]][9] [![License][license-badge]][2] +[![Made With Love][mwl]][6] ![divider][divider] @@ -19,6 +21,8 @@ â€ĸ [Request Feature][3] â€ĸ [Contributing Guidelines][4]** +
+ ## Welcome to the Mini Functions Library for Rust 👋 `Mini Functions` is a highly performant utility and wrapper functions From ac819d52f97885cb1e9996ed1512c13a28f33a76 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 10:41:15 +0000 Subject: [PATCH 10/13] fix(doc): layout tweaks --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5df64cc4..e6661e4c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ # Mini Functions đŸĻ€ +[![Made With Love][mwl]][6] +
-![Mini Functions][banner] - ## Highly performant utility and wrapper functions library for Rust 🚀 +![Mini Functions][banner] + [![Crates.io][crates-badge]][8] [![Lib.rs][libs-badge]][10] [![Docs.rs][docs-badge]][9] [![License][license-badge]][2] -[![Made With Love][mwl]][6] ![divider][divider] @@ -291,7 +292,7 @@ for their help and support. [9]: https://docs.rs/mini-functions [10]: https://lib.rs/crates/mini-functions -[banner]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/banners/banner-mini-functions.svg "Mini Functions - Rust đŸĻ€" +[banner]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/logos/logo-mini-functions.svg "Mini Functions - Rust đŸĻ€" [crates-badge]: https://img.shields.io/crates/v/mini-functions.svg?style=for-the-badge 'Crates.io' [divider]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/elements/divider.svg "divider" [docs-badge]: https://img.shields.io/docsrs/mini-functions.svg?style=for-the-badge 'Docs.rs' From 269cc8d5567b89dd8b799f56f227413b24def276 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 11:13:23 +0000 Subject: [PATCH 11/13] feat(badge): new made with rust icon --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e6661e4c..2d52402c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ ![Mini Functions][banner] +[![Rust][made-with-rust-badge]][12] [![Crates.io][crates-badge]][8] [![Lib.rs][libs-badge]][10] [![Docs.rs][docs-badge]][9] @@ -291,11 +292,13 @@ for their help and support. [8]: https://crates.io/crates/mini-functions [9]: https://docs.rs/mini-functions [10]: https://lib.rs/crates/mini-functions +[12]: https://www.rust-lang.org/ -[banner]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/logos/logo-mini-functions.svg "Mini Functions - Rust đŸĻ€" +[banner]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/banners/banner-mini-functions.svg "Mini Functions - Rust đŸĻ€" [crates-badge]: https://img.shields.io/crates/v/mini-functions.svg?style=for-the-badge 'Crates.io' [divider]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/elements/divider.svg "divider" [docs-badge]: https://img.shields.io/docsrs/mini-functions.svg?style=for-the-badge 'Docs.rs' [libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.6-orange.svg?style=for-the-badge 'Lib.rs' [license-badge]: https://img.shields.io/crates/l/mini-functions.svg?style=for-the-badge 'License' +[made-with-rust-badge]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/shields/made-with-rust.svg "Made With Rust đŸĻ€" [mwl]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/shields/made-with-love.svg "Made With Love" From 80defd717b34cd764ec5485b78be9142acf18351 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 14:00:20 +0000 Subject: [PATCH 12/13] fixed(anti-pattern): The new() function is used to initialise an object with specific data. If no arguments are passed, the behaviour is identical to default(). --- src/hash.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index 6d1d42be..630e0306 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -57,8 +57,8 @@ impl Hash { /// Returns a new instance of `Hash`. pub fn new() -> Self { Self { - password: String::new(), - hash: String::new(), + password: String::default(), + hash: String::default(), } } From 089b4aa3ad3dd963e576931a23d4566e5499ca69 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Fri, 6 Jan 2023 14:23:14 +0000 Subject: [PATCH 13/13] fix(workflow): create_release to run only when a pull request is closed and merged --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2835d590..2391aeab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: - name: Extract version number 📜 id: version run: | - version=$(grep version Cargo.toml | cut -d '"' -f 2) + version=$(grep version Cargo.toml | cut -d '"' -f 2 | sed 's/^/v/') echo "version=$version" - name: Build libraries 🏗 @@ -86,15 +86,15 @@ jobs: - name: Create Release 🚀 id: create_release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: body_path: ${{ github.workspace }}-CHANGELOG.txt files: | - packages/mini_functions-$version.tar.gz + packages/mini_functions-${{ steps.version.outputs.version }}.tar.gz name: ${{ github.workspace }} release_name: ${{ github.workspace }} - tag_name: ${{ github.workspace }} + tag_name: v${{ steps.version.outputs.version }} draft: false prerelease: false