diff --git a/include/faker-cxx/crypto.h b/include/faker-cxx/crypto.h index 53c5caf5..34c52ec7 100644 --- a/include/faker-cxx/crypto.h +++ b/include/faker-cxx/crypto.h @@ -30,4 +30,16 @@ FAKER_CXX_EXPORT std::string sha256(std::optional = std::nullopt); * @endcode */ FAKER_CXX_EXPORT std::string md5(std::optional = std::nullopt); + +/** + * @brief Returns a SHA1 hash of provided data. + * + * @returns SHA1 hash string. + * + * @code + * faker::crypto::sha1("hello world") // "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed" + * @endcode + */ +FAKER_CXX_EXPORT std::string sha1(std::optional = std::nullopt); + } diff --git a/include/faker-cxx/string.h b/include/faker-cxx/string.h index 753dbf64..4918710f 100644 --- a/include/faker-cxx/string.h +++ b/include/faker-cxx/string.h @@ -21,16 +21,6 @@ namespace faker::string { -enum class Uuid -{ - V1, // Version 1: UUIDs using a timestamp and monotonic counter. - V3, // Version 3: UUIDs based on the MD5 hash of some data. - V4, // Version 4: UUIDs with random data. - V5, // Version 5: UUIDs based on the SHA1 hash of some data. - V6, // Version 6: UUIDs using a timestamp and monotonic counter (sortable). - V7, // Version 7: UUIDs using a Unix timestamp (sortable). - V8 // Version 8: UUIDs using user-defined data. -}; enum class StringCasing { @@ -80,24 +70,71 @@ FAKER_CXX_EXPORT bool isValidGuarantee(GuaranteeMap& guarantee, std::set& FAKER_CXX_EXPORT std::string generateAtLeastString(const GuaranteeMap& guarantee); /** - * @brief Generates an Universally Unique Identifier, defaults to V4. + * @brief Generates a UUID version 1 string. + * + * UUID v1 is generated using the current timestamp, a randomly generated clock sequence, + * and a randomly generated node identifier. The timestamp is offset by the UUID epoch. + * + * @returns UUID v1 string in the standard 8-4-4-4-12 format. + * + * @code + * FAKER_CXX_EXPORT std::string uuid = uuidV1(); // "550e8400-e29b-41d4-a716-446655440000" + * @endcode + */ +FAKER_CXX_EXPORT std::string uuidV1(); + +/** + * @brief Generates a UUID version 3 string. + * + * UUID v3 is generated by creating a random 16-byte hash and modifying specific bytes + * to conform to the UUID version 3 and variant specifications. + * + * @returns UUID v3 string in the standard 8-4-4-4-12 format. + * + * @code + * FAKER_CXX_EXPORT std::string uuid = uuidV3(); // "d9b2d63d-a233-3de7-8c4b-fb6e96c46e04" + * @endcode + */ +FAKER_CXX_EXPORT std::string uuidV3(); + +/** + * @brief Generates a UUID version 4 string. + * + * UUID v4 is randomly generated. Specific bytes are modified to ensure compliance + * with the UUID version 4 and variant standards. + * + * @returns UUID v4 string in the standard 8-4-4-4-12 format. + * + * @code + * FAKER_CXX_EXPORT std::string uuid = uuidV4(); // "550e8400-e29b-41d4-a716-446655440000" + * @endcode + */ +FAKER_CXX_EXPORT std::string uuidV4(); + +/** + * @brief Generates a UUID version 5 string. + * + * UUID v5 is generated using a namespace UUID and a name string. The SHA-1 hash of the + * namespace and name is used to produce the UUID. Specific bytes are modified to conform + * to the UUID version 5 and variant specifications. If no namespace UUID is provided, + * a default value will be used. * - * @param gen A random number generator (type RandomGenerator) + * @param name The name string used in the UUID generation. + * @param namespaceUuid (Optional) The namespace UUID used in the UUID generation. + * Defaults to "bfd98c8e-48c0-46af-bf44-255d24883f8f". * - * @returns UUID. + * @returns UUID v5 string in the standard 8-4-4-4-12 format. * * @code - * faker::string::uuid() // "27666229-cedb-4a45-8018-98b1e1d921e2" // V4 - * faker::string::uuid(Uuid::V1) // "04f916a0-af32-11ef-9cd2-0242ac120002" - * faker::string::uuid(Uuid::V3) // "a3bb189e-8bf9-3888-9912-ace4e6543002" - * faker::string::uuid(Uuid::V4) // "27666229-cedb-4a45-8018-98b1e1d921e2" - * faker::string::uuid(Uuid::V5) // "27666229-cedb-4a45-8018-98b1e1d921e2" - * faker::string::uuid(Uuid::V6) // "27666229-cedb-4a45-8018-98b1e1d921e2" - * faker::string::uuid(Uuid::V7) // "27666229-cedb-4a45-8018-98b1e1d921e2" - * faker::string::uuid(Uuid::V8) // "27666229-cedb-4a45-8018-98b1e1d921e2" + * FAKER_CXX_EXPORT std::string uuid1 = uuidV5("example"); + * // Uses default namespaceUuid: "bfd98c8e-48c0-46af-bf44-255d24883f8f" + * + * FAKER_CXX_EXPORT std::string uuid2 = uuidV5("example", "550e8400-e29b-41d4-a716-446655440000"); + * // Uses provided namespaceUuid * @endcode */ -FAKER_CXX_EXPORT std::string uuid(Uuid uuid = Uuid::V4); +FAKER_CXX_EXPORT std::string uuidV5(std::string name, + std::string namespaceUuid = "bfd98c8e-48c0-46af-bf44-255d24883f8f"); /** * @brief Generates an Universally Unique Lexicographically Sortable Identifier. diff --git a/src/modules/crypto.cpp b/src/modules/crypto.cpp index 53ee5e5d..0d3e719f 100644 --- a/src/modules/crypto.cpp +++ b/src/modules/crypto.cpp @@ -3,7 +3,10 @@ #include #include #include +#include +#include #include +#include #include #include "faker-cxx/word.h" @@ -29,6 +32,20 @@ inline std::string toHex(const std::array& data) return result; } +class SHA1 +{ +public: + SHA1(); + void update(const std::string& s); + void update(std::istream& is); + std::string final(); + +private: + uint32_t digest[5]; + std::string buffer; + uint64_t transforms; +}; + class SHA256 { public: @@ -121,6 +138,287 @@ std::string sha256(std::optional data) return result; } +inline static void reset(uint32_t digest[], std::string& buffer, uint64_t& transforms) +{ + /* SHA1 initialization constants */ + digest[0] = 0x67452301; + digest[1] = 0xefcdab89; + digest[2] = 0x98badcfe; + digest[3] = 0x10325476; + digest[4] = 0xc3d2e1f0; + + /* Reset counters */ + buffer = ""; + transforms = 0; +} + +inline SHA1::SHA1() +{ + reset(digest, buffer, transforms); +} + +static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ +static const size_t BLOCK_BYTES = BLOCK_INTS * 4; + +inline static uint32_t rol(const uint32_t value, const size_t bits) +{ + return (value << bits) | (value >> (32 - bits)); +} + +inline static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i) +{ + return rol(block[(i + 13) & 15] ^ block[(i + 8) & 15] ^ block[(i + 2) & 15] ^ block[i], 1); +} + +/* + * (R0+R1), R2, R3, R4 are the different operations used in SHA1 + */ + +inline static void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t& w, const uint32_t x, + const uint32_t y, uint32_t& z, const size_t i) +{ + z += ((w & (x ^ y)) ^ y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + +inline static void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t& w, const uint32_t x, const uint32_t y, + uint32_t& z, const size_t i) +{ + block[i] = blk(block, i); + z += ((w & (x ^ y)) ^ y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + +inline static void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t& w, const uint32_t x, const uint32_t y, + uint32_t& z, const size_t i) +{ + block[i] = blk(block, i); + z += (w ^ x ^ y) + block[i] + 0x6ed9eba1 + rol(v, 5); + w = rol(w, 30); +} + +inline static void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t& w, const uint32_t x, const uint32_t y, + uint32_t& z, const size_t i) +{ + block[i] = blk(block, i); + z += (((w | x) & y) | (w & x)) + block[i] + 0x8f1bbcdc + rol(v, 5); + w = rol(w, 30); +} + +inline static void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t& w, const uint32_t x, const uint32_t y, + uint32_t& z, const size_t i) +{ + block[i] = blk(block, i); + z += (w ^ x ^ y) + block[i] + 0xca62c1d6 + rol(v, 5); + w = rol(w, 30); +} + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ + +inline static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint64_t& transforms) +{ + /* Copy digest[] to working vars */ + uint32_t a = digest[0]; + uint32_t b = digest[1]; + uint32_t c = digest[2]; + uint32_t d = digest[3]; + uint32_t e = digest[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(block, a, b, c, d, e, 0); + R0(block, e, a, b, c, d, 1); + R0(block, d, e, a, b, c, 2); + R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); + R0(block, a, b, c, d, e, 5); + R0(block, e, a, b, c, d, 6); + R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); + R0(block, b, c, d, e, a, 9); + R0(block, a, b, c, d, e, 10); + R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); + R0(block, c, d, e, a, b, 13); + R0(block, b, c, d, e, a, 14); + R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 0); + R1(block, d, e, a, b, c, 1); + R1(block, c, d, e, a, b, 2); + R1(block, b, c, d, e, a, 3); + R2(block, a, b, c, d, e, 4); + R2(block, e, a, b, c, d, 5); + R2(block, d, e, a, b, c, 6); + R2(block, c, d, e, a, b, 7); + R2(block, b, c, d, e, a, 8); + R2(block, a, b, c, d, e, 9); + R2(block, e, a, b, c, d, 10); + R2(block, d, e, a, b, c, 11); + R2(block, c, d, e, a, b, 12); + R2(block, b, c, d, e, a, 13); + R2(block, a, b, c, d, e, 14); + R2(block, e, a, b, c, d, 15); + R2(block, d, e, a, b, c, 0); + R2(block, c, d, e, a, b, 1); + R2(block, b, c, d, e, a, 2); + R2(block, a, b, c, d, e, 3); + R2(block, e, a, b, c, d, 4); + R2(block, d, e, a, b, c, 5); + R2(block, c, d, e, a, b, 6); + R2(block, b, c, d, e, a, 7); + R3(block, a, b, c, d, e, 8); + R3(block, e, a, b, c, d, 9); + R3(block, d, e, a, b, c, 10); + R3(block, c, d, e, a, b, 11); + R3(block, b, c, d, e, a, 12); + R3(block, a, b, c, d, e, 13); + R3(block, e, a, b, c, d, 14); + R3(block, d, e, a, b, c, 15); + R3(block, c, d, e, a, b, 0); + R3(block, b, c, d, e, a, 1); + R3(block, a, b, c, d, e, 2); + R3(block, e, a, b, c, d, 3); + R3(block, d, e, a, b, c, 4); + R3(block, c, d, e, a, b, 5); + R3(block, b, c, d, e, a, 6); + R3(block, a, b, c, d, e, 7); + R3(block, e, a, b, c, d, 8); + R3(block, d, e, a, b, c, 9); + R3(block, c, d, e, a, b, 10); + R3(block, b, c, d, e, a, 11); + R4(block, a, b, c, d, e, 12); + R4(block, e, a, b, c, d, 13); + R4(block, d, e, a, b, c, 14); + R4(block, c, d, e, a, b, 15); + R4(block, b, c, d, e, a, 0); + R4(block, a, b, c, d, e, 1); + R4(block, e, a, b, c, d, 2); + R4(block, d, e, a, b, c, 3); + R4(block, c, d, e, a, b, 4); + R4(block, b, c, d, e, a, 5); + R4(block, a, b, c, d, e, 6); + R4(block, e, a, b, c, d, 7); + R4(block, d, e, a, b, c, 8); + R4(block, c, d, e, a, b, 9); + R4(block, b, c, d, e, a, 10); + R4(block, a, b, c, d, e, 11); + R4(block, e, a, b, c, d, 12); + R4(block, d, e, a, b, c, 13); + R4(block, c, d, e, a, b, 14); + R4(block, b, c, d, e, a, 15); + + /* Add the working vars back into digest[] */ + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; + + /* Count the number of transformations */ + transforms++; +} + +inline static void buffer_to_block(const std::string& buffer, uint32_t block[BLOCK_INTS]) +{ + /* Convert the std::string (byte buffer) to a uint32_t array (MSB) */ + for (size_t i = 0; i < BLOCK_INTS; i++) + { + block[i] = (buffer[4 * i + 3] & 0xff) | (buffer[4 * i + 2] & 0xff) << 8 | (buffer[4 * i + 1] & 0xff) << 16 | + (buffer[4 * i + 0] & 0xff) << 24; + } +} + +inline void SHA1::update(const std::string& s) +{ + std::istringstream is(s); + update(is); +} + +inline void SHA1::update(std::istream& is) +{ + while (true) + { + char sbuf[BLOCK_BYTES]; + is.read(sbuf, BLOCK_BYTES - buffer.size()); + buffer.append(sbuf, (std::size_t)is.gcount()); + if (buffer.size() != BLOCK_BYTES) + { + return; + } + uint32_t block[BLOCK_INTS]; + buffer_to_block(buffer, block); + transform(digest, block, transforms); + buffer.clear(); + } +} + +/* + * Add padding and return the message digest. + */ + +inline std::string SHA1::final() +{ + /* Total number of hashed bits */ + uint64_t total_bits = (transforms * BLOCK_BYTES + buffer.size()) * 8; + + /* Padding */ + buffer += (char)0x80; + size_t orig_size = buffer.size(); + while (buffer.size() < BLOCK_BYTES) + { + buffer += (char)0x00; + } + + uint32_t block[BLOCK_INTS]; + buffer_to_block(buffer, block); + + if (orig_size > BLOCK_BYTES - 8) + { + transform(digest, block, transforms); + for (size_t i = 0; i < BLOCK_INTS - 2; i++) + { + block[i] = 0; + } + } + + /* Append total_bits, split this uint64_t into two uint32_t */ + block[BLOCK_INTS - 1] = (uint32_t)total_bits; + block[BLOCK_INTS - 2] = (uint32_t)(total_bits >> 32); + transform(digest, block, transforms); + + /* Hex std::string */ + std::ostringstream result; + for (size_t i = 0; i < sizeof(digest) / sizeof(digest[0]); i++) + { + result << std::hex << std::setfill('0') << std::setw(8); + result << digest[i]; + } + + /* Reset for next run */ + reset(digest, buffer, transforms); + return result.str(); +} + +std::string sha1(std::optional data) +{ + std::string orgData; + if (!data.has_value() || data->empty()) + { + orgData = word::sample(); + } + else + { + orgData = data.value(); + } + + SHA1 sha1; + sha1.update(orgData); + std::string result = sha1.final(); + + return result; +} + std::string md5(std::optional data) { std::string orgData; diff --git a/src/modules/internet.cpp b/src/modules/internet.cpp index 07ef976e..1d606c10 100644 --- a/src/modules/internet.cpp +++ b/src/modules/internet.cpp @@ -493,9 +493,9 @@ std::string getJWTToken(const std::optional>& {"exp", std::to_string(std::round(std::stoll(expDefault)))}, {"nbf", std::to_string(std::round(std::stoll(nbfDefault)))}, {"iss", faker::company::companyName()}, - {"sub", faker::string::uuid()}, - {"aud", faker::string::uuid()}, - {"jti", faker::string::uuid()}}; + {"sub", faker::string::uuidV4()}, + {"aud", faker::string::uuidV4()}, + {"jti", faker::string::uuidV4()}}; } const auto headerToJSON = toJSON(localHeader.value()); diff --git a/src/modules/string.cpp b/src/modules/string.cpp index 62e141f8..871f1cfa 100644 --- a/src/modules/string.cpp +++ b/src/modules/string.cpp @@ -8,6 +8,7 @@ #include #include "common/algo_helper.h" +#include "faker-cxx/crypto.h" #include "faker-cxx/helper.h" #include "faker-cxx/number.h" #include "string_data.h" @@ -431,7 +432,6 @@ std::string uuidV3() std::ostringstream ss; ss << std::hex << std::setfill('0'); - for (size_t i = 0; i < hash.size(); ++i) { ss << std::setw(2) << static_cast(hash[i]); @@ -441,7 +441,6 @@ std::string uuidV3() ss << '-'; } } - return ss.str(); } @@ -487,34 +486,67 @@ std::string uuidV4() { result.append(1, hexCharacters[static_cast(gen(dist))]); } - return result; } -std::string uuid(Uuid uuid) +std::string uuidV5(std::string name, std::string namespaceUuid) { - switch (uuid) - { - case Uuid::V1: - return uuidV1(); - case Uuid::V3: - return uuidV3(); - case Uuid::V4: - return uuidV4(); - case Uuid::V5: - // TODO: implement uuidV5 - return uuidV4(); - case Uuid::V6: - // TODO: implement uuidV6 - return uuidV4(); - case Uuid::V7: - // TODO: implement uuidV7 - return uuidV4(); - case Uuid::V8: - // TODO: implement uuidV8 - return uuidV4(); - default: - return uuidV4(); + std::vector namespaceBytes; + for (size_t i = 0; i < namespaceUuid.size(); i += 2) + { + if (namespaceUuid[i] == '-') + { // Skip dashes + i--; + continue; + } + namespaceBytes.push_back(static_cast(std::stoul(namespaceUuid.substr(i, 2), nullptr, 16))); } + std::string combined(reinterpret_cast(namespaceBytes.data()), namespaceBytes.size()); + combined += name; + + std::string sha1Hex = faker::crypto::sha1(combined); + std::array sha1_bytes; + + for (int i = 0; i < 20; ++i) + { + std::string byte_str = sha1Hex.substr(i * 2, 2); + + try + { + sha1_bytes[i] = std::stoi(byte_str, nullptr, 16); + } + catch (const std::out_of_range& e) + { + throw std::invalid_argument("Error converting hex string to byte: " + byte_str); + } + } + + std::array uuid; + + std::copy(sha1_bytes.begin(), sha1_bytes.begin() + 10, uuid.begin()); + + uuid[6] &= 0x0F; + uuid[6] |= 0x50; + + uuid[8] &= 0x3F; + uuid[8] |= 0x80; + + std::copy(sha1_bytes.begin() + 10, sha1_bytes.end(), uuid.begin() + 10); + + std::ostringstream uuidV5; + uuidV5 << std::hex << std::setfill('0'); + + for (size_t i = 0; i < uuid.size(); ++i) + { + uuidV5 << std::setw(2) << static_cast(uuid[i]); + + if (i == 3 || i == 5 || i == 7 || i == 9) + { + uuidV5 << '-'; + } + } + + return uuidV5.str(); } + } diff --git a/tests/modules/crypto_test.cpp b/tests/modules/crypto_test.cpp index dcf40b6a..d9cd6bb2 100644 --- a/tests/modules/crypto_test.cpp +++ b/tests/modules/crypto_test.cpp @@ -24,6 +24,13 @@ class CryptoTest : public Test return std::regex_match(input, regexExp); } + + static bool isSHA1Hash(const std::string& input) + { + const std::regex regexExp("^[0-9a-f]{40}$"); + + return std::regex_match(input, regexExp); + } }; TEST_F(CryptoTest, ShouldGenerateSHA256Hash) @@ -40,6 +47,13 @@ TEST_F(CryptoTest, ChecksSHA256Hash) ASSERT_TRUE(isSHA256Hash(generatedRandomHash)); } +TEST_F(CryptoTest, ChecksSHA1Hash) +{ + const auto generatedRandomHash = sha1("sample"); + std::cout<