#include #include #include #include #include #include #include "Catch2.h" using namespace Leonetienne::GCrypt; // We can generate passwords by just translating a decimal number to base "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // So this is just a helper function to generate random passwords inline std::string Base10_2_X(const unsigned long long int i, const std::string set, unsigned int padding) { if (set.length() == 0) return ""; // Return empty string, if set is empty. Play stupid games, win stupid prizes. std::stringstream ss; if (i != 0) { { unsigned long long int buf = i; while (buf != 0) { const unsigned long long int mod = buf % set.length(); buf /= set.length(); ss << set[(std::size_t)mod]; } } { const std::string buf = ss.str(); ss.str(""); for (long long int i = buf.length() - 1; i >= 0; i--) { ss << buf[(std::size_t)i]; } } } else { ss << set[0]; // If i is 0, just pass a null-value. The algorithm would hang otherwise. } // Add as much null-values to the left as requested. if (ss.str().length() < padding) { const std::size_t cachedLen = ss.str().length(); const std::string cachedStr = ss.str(); ss.str(""); for (std::size_t i = 0; i < padding - cachedLen; i++) { ss << set[0]; } ss << cachedStr; } return ss.str(); } // Run a few thousand random passwords through the keygen and see if we'll find a collision. // This test passing does NOT mean that it's resistant! Maybe good, maybe shit! But if it fails, it's definitely shit. // Already validated range: Password 0 - 1.000.000 TEST_CASE(__FILE__"/Password to key transformation collision resistance", "[Key extrapolation]") { // To test resistence set this to a high number around a million. // This will take a LONG while to execute though (about 2.5hrs on my machine), hence why it's set so low. constexpr std::size_t NUM_RUN_TESTS = 10; std::unordered_map keys; // // Try NUM_RUN_TESTS passwords const std::string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (std::size_t i = 0; i < NUM_RUN_TESTS; i++) { // Get password const std::string password = Base10_2_X(i, charset, 0); // Generate key const std::string newKeyBits = Key::FromPassword(password).ToBinaryString(); // Check if this block is already in our map if (keys.find(newKeyBits) != keys.cend()) { std::cout << "Collision found between password \"" << password << "\" and \"" << keys[newKeyBits] << "\". The key is \"" << newKeyBits << "\"."; FAIL(); } // All good? Insert it into our map keys[newKeyBits] = password; } return; }