diff --git a/Src/Password_Generator/CMakeLists.txt b/Src/Password_Generator/CMakeLists.txt new file mode 100644 index 0000000..9859ef6 --- /dev/null +++ b/Src/Password_Generator/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.15) +project(Password_Generator LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(pwg + src/Random.cpp + src/PasswordGenerator.cpp + src/PasswordStrengthEvaluator.cpp +) + +target_include_directories(pwg PUBLIC include) + +add_executable(password_generator + src/main.cpp +) + +target_link_libraries(password_generator PRIVATE pwg) diff --git a/Src/Password_Generator/README.md b/Src/Password_Generator/README.md index 6094ed5..ac447bf 100644 --- a/Src/Password_Generator/README.md +++ b/Src/Password_Generator/README.md @@ -32,3 +32,61 @@ The project is designed to be **modular, maintainable, and easy to extend**, fol 1. Clone the repository: ```bash git clone https://github.com/your-username/your-repo.git + ``` +2. Navigate to the Password Generator project directory: + ```bash + cd your-repo/Src/Password_Generator + ``` +3. Build with CMake: + ```bash + mkdir -p build && cd build + cmake .. + cmake --build . + ``` +4. Run: + ```bash + ./password_generator + ``` + +Alternative: build without CMake (ensure you are in Src/Password_Generator) +```bash +g++ -std=cpp17 -Iinclude src/*.cpp -o password_generator +./password_generator +``` + +## Usage +The app runs in interactive CLI mode. + +- Option 1 (Preset): Choose weak, medium, or strong, then how many passwords to generate. +- Option 2 (Custom): Choose length, inclusion of lowercase/uppercase/digits/symbols, whether to require at least one of each selected type, and how many passwords. + +Example session +``` +C++ Password Generator +1) Preset (weak/medium/strong) +2) Custom +Choose option [1/2]: 1 +Preset [weak/medium/strong]: strong +How many passwords to generate [1]: 3 +[1] 2Gz!tq@K3wqM)h^L (entropy: 95 bits, strength: strong) +[2] Lz5{nZV8pQ%g=J7! (entropy: 95 bits, strength: strong) +[3] 7Y@kR`mT2s&Qh9)C (entropy: 95 bits, strength: strong) +``` + +Options explained +- length: number of characters in each password. +- includeLowercase/includeUppercase/includeDigits/includeSymbols: toggle character sets. +- requireEachSelectedType: if enabled, guarantees at least one character from each selected set and shuffles the result. +- count: how many passwords to output. + +## Design overview (OOP + SOLID) +- PasswordGenerator: single responsibility to generate passwords; depends on abstractions. +- IRandom + MTRandom: randomness abstraction and mt19937-based implementation (Dependency Inversion). +- UserPreferences: clean container for user options and presets (Weak/Medium/Strong). +- IPasswordStrengthEvaluator + EntropyPasswordStrengthEvaluator: pluggable strength evaluation. +- CharacterSets: centralized immutable character sets. +- Dependency Injection: Generator receives IRandom; evaluator is independent and reusable. + +## Notes +- Requires a C++17-compatible compiler and CMake 3.15+ (if using CMake). +- Input validated with helpful error messages (e.g., length must be > 0, at least one character set required). diff --git a/Src/Password_Generator/include/CharacterSets.h b/Src/Password_Generator/include/CharacterSets.h new file mode 100644 index 0000000..148e8a0 --- /dev/null +++ b/Src/Password_Generator/include/CharacterSets.h @@ -0,0 +1,7 @@ +#pragma once +namespace charset { + inline constexpr const char* LOWER = "abcdefghijklmnopqrstuvwxyz"; + inline constexpr const char* UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + inline constexpr const char* DIGITS = "0123456789"; + inline constexpr const char* SYMBOLS= "!@#$%^&*()-_=+[]{}|;:,.<>?/~`"; +} diff --git a/Src/Password_Generator/include/IPasswordStrengthEvaluator.h b/Src/Password_Generator/include/IPasswordStrengthEvaluator.h new file mode 100644 index 0000000..7ba316d --- /dev/null +++ b/Src/Password_Generator/include/IPasswordStrengthEvaluator.h @@ -0,0 +1,11 @@ +#pragma once +#include + +enum class PasswordStrength { Weak, Medium, Strong }; + +struct IPasswordStrengthEvaluator { + virtual ~IPasswordStrengthEvaluator() = default; + virtual double entropyBits(const std::string& password) const = 0; + virtual PasswordStrength classify(double entropyBits) const = 0; + virtual const char* toString(PasswordStrength s) const = 0; +}; diff --git a/Src/Password_Generator/include/IRandom.h b/Src/Password_Generator/include/IRandom.h new file mode 100644 index 0000000..e6f576b --- /dev/null +++ b/Src/Password_Generator/include/IRandom.h @@ -0,0 +1,7 @@ +#pragma once +#include + +struct IRandom { + virtual ~IRandom() = default; + virtual std::size_t nextIndex(std::size_t upperExclusive) = 0; // [0, upperExclusive) +}; diff --git a/Src/Password_Generator/include/PasswordGenerator.h b/Src/Password_Generator/include/PasswordGenerator.h new file mode 100644 index 0000000..256c910 --- /dev/null +++ b/Src/Password_Generator/include/PasswordGenerator.h @@ -0,0 +1,22 @@ +#pragma once +#include "IRandom.h" +#include "UserPreferences.h" +#include +#include + +class PasswordGenerator { +public: + explicit PasswordGenerator(IRandom& rng); + + std::string generate(const UserPreferences& prefs); + std::vector generateMany(const UserPreferences& prefs); + +private: + IRandom& rng_; + + std::string buildAlphabet(const UserPreferences& prefs) const; + void ensureValid(const UserPreferences& prefs) const; + char randomFrom(const std::string& alphabet); + void fisherYatesShuffle(std::string& s); + std::string generateWithEachType(const UserPreferences& prefs, const std::string& alphabet); +}; diff --git a/Src/Password_Generator/include/PasswordStrengthEvaluator.h b/Src/Password_Generator/include/PasswordStrengthEvaluator.h new file mode 100644 index 0000000..e904e11 --- /dev/null +++ b/Src/Password_Generator/include/PasswordStrengthEvaluator.h @@ -0,0 +1,9 @@ +#pragma once +#include "IPasswordStrengthEvaluator.h" + +class EntropyPasswordStrengthEvaluator final : public IPasswordStrengthEvaluator { +public: + double entropyBits(const std::string& password) const override; + PasswordStrength classify(double entropyBits) const override; + const char* toString(PasswordStrength s) const override; +}; diff --git a/Src/Password_Generator/include/Random.h b/Src/Password_Generator/include/Random.h new file mode 100644 index 0000000..6e1056e --- /dev/null +++ b/Src/Password_Generator/include/Random.h @@ -0,0 +1,12 @@ +#pragma once +#include "IRandom.h" +#include + +class MTRandom final : public IRandom { +public: + explicit MTRandom(unsigned int seed = std::random_device{}()); + std::size_t nextIndex(std::size_t upperExclusive) override; + +private: + std::mt19937 engine_; +}; diff --git a/Src/Password_Generator/include/UserPreferences.h b/Src/Password_Generator/include/UserPreferences.h new file mode 100644 index 0000000..805c0dc --- /dev/null +++ b/Src/Password_Generator/include/UserPreferences.h @@ -0,0 +1,22 @@ +#pragma once +#include + +struct UserPreferences { + std::size_t length{12}; + bool includeLowercase{true}; + bool includeUppercase{true}; + bool includeDigits{true}; + bool includeSymbols{false}; + std::size_t count{1}; + bool requireEachSelectedType{true}; + + static UserPreferences Weak() { + return UserPreferences{8, true, false, false, false, 1, false}; + } + static UserPreferences Medium() { + return UserPreferences{12, true, true, true, false, 1, true}; + } + static UserPreferences Strong() { + return UserPreferences{16, true, true, true, true, 1, true}; + } +}; diff --git a/Src/Password_Generator/src/PasswordGenerator.cpp b/Src/Password_Generator/src/PasswordGenerator.cpp new file mode 100644 index 0000000..ad1078c --- /dev/null +++ b/Src/Password_Generator/src/PasswordGenerator.cpp @@ -0,0 +1,80 @@ +#include "PasswordGenerator.h" +#include "CharacterSets.h" +#include + +PasswordGenerator::PasswordGenerator(IRandom& rng) : rng_{rng} {} + +void PasswordGenerator::ensureValid(const UserPreferences& prefs) const { + if (prefs.length == 0) throw std::invalid_argument("Password length must be > 0"); + if (!prefs.includeLowercase && !prefs.includeUppercase && !prefs.includeDigits && !prefs.includeSymbols) { + throw std::invalid_argument("At least one character set must be enabled"); + } + std::size_t types = (prefs.includeLowercase?1:0) + (prefs.includeUppercase?1:0) + + (prefs.includeDigits?1:0) + (prefs.includeSymbols?1:0); + if (prefs.requireEachSelectedType && prefs.length < types) { + throw std::invalid_argument("Length too short to include at least one of each selected type"); + } +} + +std::string PasswordGenerator::buildAlphabet(const UserPreferences& prefs) const { + std::string alphabet; + if (prefs.includeLowercase) alphabet += charset::LOWER; + if (prefs.includeUppercase) alphabet += charset::UPPER; + if (prefs.includeDigits) alphabet += charset::DIGITS; + if (prefs.includeSymbols) alphabet += charset::SYMBOLS; + return alphabet; +} + +char PasswordGenerator::randomFrom(const std::string& alphabet) { + return alphabet[rng_.nextIndex(alphabet.size())]; +} + +void PasswordGenerator::fisherYatesShuffle(std::string& s) { + if (s.size() <= 1) return; + for (std::size_t i = s.size() - 1; i > 0; --i) { + std::size_t j = rng_.nextIndex(i + 1); + std::swap(s[i], s[j]); + } +} + +std::string PasswordGenerator::generateWithEachType(const UserPreferences& prefs, const std::string& alphabet) { + std::string pw; + pw.reserve(prefs.length); + + // Ensure at least one char from each selected set + if (prefs.includeLowercase) pw.push_back(randomFrom(charset::LOWER)); + if (prefs.includeUppercase) pw.push_back(randomFrom(charset::UPPER)); + if (prefs.includeDigits) pw.push_back(randomFrom(charset::DIGITS)); + if (prefs.includeSymbols) pw.push_back(randomFrom(charset::SYMBOLS)); + + // Fill remaining with random chars from the combined alphabet + while (pw.size() < prefs.length) { + pw.push_back(randomFrom(alphabet)); + } + + fisherYatesShuffle(pw); + return pw; +} + +std::string PasswordGenerator::generate(const UserPreferences& prefs) { + ensureValid(prefs); + const std::string alphabet = buildAlphabet(prefs); + if (prefs.requireEachSelectedType) return generateWithEachType(prefs, alphabet); + + std::string pw; + pw.reserve(prefs.length); + for (std::size_t i = 0; i < prefs.length; ++i) { + pw.push_back(randomFrom(alphabet)); + } + return pw; +} + +std::vector PasswordGenerator::generateMany(const UserPreferences& prefs) { + if (prefs.count == 0) throw std::invalid_argument("Count must be > 0"); + std::vector out; + out.reserve(prefs.count); + for (std::size_t i = 0; i < prefs.count; ++i) { + out.push_back(generate(prefs)); + } + return out; +} diff --git a/Src/Password_Generator/src/PasswordStrengthEvaluator.cpp b/Src/Password_Generator/src/PasswordStrengthEvaluator.cpp new file mode 100644 index 0000000..7d60c8f --- /dev/null +++ b/Src/Password_Generator/src/PasswordStrengthEvaluator.cpp @@ -0,0 +1,41 @@ +#include "PasswordStrengthEvaluator.h" +#include "CharacterSets.h" +#include +#include + +static bool containsAny(std::string_view s, std::string_view bag) { + for (char c : s) { + for (char b : bag) { + if (c == b) return true; + } + } + return false; +} + +double EntropyPasswordStrengthEvaluator::entropyBits(const std::string& password) const { + using namespace std::literals; + std::string_view pw{password}; + double alphabetSize = 0.0; + if (containsAny(pw, charset::LOWER)) alphabetSize += std::char_traits::length(charset::LOWER); + if (containsAny(pw, charset::UPPER)) alphabetSize += std::char_traits::length(charset::UPPER); + if (containsAny(pw, charset::DIGITS)) alphabetSize += std::char_traits::length(charset::DIGITS); + if (containsAny(pw, charset::SYMBOLS))alphabetSize += std::char_traits::length(charset::SYMBOLS); + + if (alphabetSize <= 1.0) return 0.0; + return static_cast(password.size()) * std::log2(alphabetSize); +} + +PasswordStrength EntropyPasswordStrengthEvaluator::classify(double e) const { + if (e < 36.0) return PasswordStrength::Weak; + if (e < 60.0) return PasswordStrength::Medium; + return PasswordStrength::Strong; +} + +const char* EntropyPasswordStrengthEvaluator::toString(PasswordStrength s) const { + switch (s) { + case PasswordStrength::Weak: return "weak"; + case PasswordStrength::Medium: return "medium"; + case PasswordStrength::Strong: return "strong"; + } + return "unknown"; +} diff --git a/Src/Password_Generator/src/Random.cpp b/Src/Password_Generator/src/Random.cpp new file mode 100644 index 0000000..88bb57a --- /dev/null +++ b/Src/Password_Generator/src/Random.cpp @@ -0,0 +1,10 @@ +#include "Random.h" +#include + +MTRandom::MTRandom(unsigned int seed) : engine_{seed} {} + +std::size_t MTRandom::nextIndex(std::size_t upperExclusive) { + if (upperExclusive == 0) throw std::invalid_argument("upperExclusive must be > 0"); + std::uniform_int_distribution dist(0, upperExclusive - 1); + return dist(engine_); +} diff --git a/Src/Password_Generator/src/main.cpp b/Src/Password_Generator/src/main.cpp new file mode 100644 index 0000000..b754e74 --- /dev/null +++ b/Src/Password_Generator/src/main.cpp @@ -0,0 +1,97 @@ +#include "PasswordGenerator.h" +#include "PasswordStrengthEvaluator.h" +#include "Random.h" +#include "UserPreferences.h" +#include +#include +#include +#include + +static std::string readLine(const std::string& prompt) { + std::cout << prompt; + std::string s; + std::getline(std::cin, s); + return s; +} + +static bool yesNo(const std::string& prompt, bool def = true) { + while (true) { + std::string s = readLine(prompt + (def ? " [Y/n]: " : " [y/N]: ")); + if (s.empty()) return def; + for (auto& c : s) c = static_cast(std::tolower(static_cast(c))); + if (s == "y" || s == "yes") return true; + if (s == "n" || s == "no") return false; + std::cout << "Please answer y or n.\n"; + } +} + +static std::size_t readSizeT(const std::string& prompt, std::size_t def, std::size_t minV = 1, std::size_t maxV = 1024) { + while (true) { + std::string s = readLine(prompt + " [" + std::to_string(def) + "]: "); + if (s.empty()) return def; + try { + std::size_t v = std::stoull(s); + if (v < minV || v > maxV) { + std::cout << "Enter a value between " << minV << " and " << maxV << ".\n"; + continue; + } + return v; + } catch (...) { + std::cout << "Invalid number.\n"; + } + } +} + +static UserPreferences readCustomPreferences() { + UserPreferences prefs; + prefs.length = readSizeT("Password length", prefs.length, 1, 2048); + prefs.includeLowercase = yesNo("Include lowercase letters?", true); + prefs.includeUppercase = yesNo("Include uppercase letters?", true); + prefs.includeDigits = yesNo("Include digits?", true); + prefs.includeSymbols = yesNo("Include symbols?", false); + prefs.requireEachSelectedType = yesNo("Require at least one of each selected type?", true); + prefs.count = readSizeT("How many passwords to generate", 1, 1, 10000); + return prefs; +} + +int main() { + std::cout << "C++ Password Generator\n"; + std::cout << "1) Preset (weak/medium/strong)\n2) Custom\n"; + std::string choice = readLine("Choose option [1/2]: "); + UserPreferences prefs; + + if (choice == "1") { + std::string preset = readLine("Preset [weak/medium/strong]: "); + for (auto& c : preset) c = static_cast(std::tolower(static_cast(c))); + if (preset == "weak") prefs = UserPreferences::Weak(); + else if (preset == "medium") prefs = UserPreferences::Medium(); + else if (preset == "strong") prefs = UserPreferences::Strong(); + else { + std::cout << "Unknown preset. Using 'strong'.\n"; + prefs = UserPreferences::Strong(); + } + prefs.count = readSizeT("How many passwords to generate", 1, 1, 10000); + } else { + prefs = readCustomPreferences(); + } + + MTRandom rng; + PasswordGenerator generator{rng}; + EntropyPasswordStrengthEvaluator evaluator; + + try { + auto passwords = generator.generateMany(prefs); + for (std::size_t i = 0; i < passwords.size(); ++i) { + const auto& p = passwords[i]; + double e = evaluator.entropyBits(p); + auto cls = evaluator.classify(e); + std::cout << "[" << (i + 1) << "] " << p << " (entropy: " << static_cast(e) + << " bits, strength: " << evaluator.toString(cls) << ")\n"; + } + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << "\n"; + return 1; + } + + return 0; +}