Search code examples
c++c++11randomsrand

How do I get two different random numbers in the same srand() seed? C++


I am trying to create a random password generator. When I call the function multiple times it returns the same character value. QQpQ;7Q7pQ;p

I have tried adding srand(time(0)); or srand((unsigned int) time(NULL)); in my main function.

Looking at other stack over flow posts I see that srand helps create different characters each time I run the program. However it still returns multiples of the same character.

How can I change srand() seed during run time to get different random numbers?

#include <string>
#include <iostream>
#include <time.h>

using namespace std;

string randomGenerator(int numberOfCharacters);
char randomCapitalCharacter();
char randomLowerCharacter();
char randomSpecialCharacter();
char randomInt();

int main(){
    srand((unsigned int) time(NULL));
    int passwordLength = 12;
    string password = randomGenerator(passwordLength);
    cout << password << endl;
    return 0;
}

string randomGenerator(int numberOfCharacters){
    char randomCharacterTypes[4] = {randomLowerCharacter(),randomInt(), randomCapitalCharacter(), randomSpecialCharacter()};
    std::string password;

    while (numberOfCharacters > 0){
        int random = rand()%4;
        password += randomCharacterTypes[random];
        numberOfCharacters--;
    }
    return password;
}
char randomInt(){
    std::string numberAsString = to_string(std::rand()% 10);
    char randomNumberChar = numberAsString.at(0);
    return randomNumberChar;
}
char randomLowerCharacter(){
    return 97 + rand()%26; //97 = a
}
char randomCapitalCharacter(){
    return 65 + rand()%26; //65 = A
}
char randomSpecialCharacter(){
    /** Special characters are split by numbers so we pick a random from one of the 2 groups.*/
    return (rand()%2) ? char(33 + rand()%14) : char(58 + rand()%8); 
}

Solution

  • randomCharacterTypes is an array of 4 characters, for each character class you'll always get the same character. If you want to generate a new character each time you could use function pointers instead:

    string randomGenerator(int numberOfCharacters){
        char (*randomCharacterTypes[])() = {randomLowerCharacter,randomInt, randomCapitalCharacter, randomSpecialCharacter};
        std::string password;
    
        while (numberOfCharacters > 0){
            int random = rand()%4;
            password += randomCharacterTypes[random]();
            numberOfCharacters--;
        }
        return password;
    }
    

    randomInt can be simplified to return a character between '0' and '9' the same as your other functions:

    char randomInt(){
        return '0' + rand() % 10;
    }
    

    Your other functions are more readable if you use character rather than numbers which you then have to add a comment to remind you which character they represent:

    char randomLowerCharacter(){
        return 'a' + rand()%26;
    }
    char randomCapitalCharacter(){
        return 'A' + rand()%26;
    }
    char randomSpecialCharacter(){
        /** Special characters are split by numbers so we pick a random from one of the 2 groups.*/
        return (rand()%2) ? char('!' + rand()%14) : char(':' + rand()%8); 
    }
    

    note that rand() % x is not a good random number generator and you should use the standard library random functions instead (especially if you are using these passwords for anything important).

    Your code could possibly be further simplified using strings to contain your character classes:

    #include <string>
    #include <string_view>
    #include <iostream>
    #include <random>
    #include <array>
    
    class PasswordGenerator
    {
    public:
        PasswordGenerator()
        {
            for (size_t i = 0; i < characterTypes.size(); i++)
            {
                characterTypeDistributions[i] = std::uniform_int_distribution<int>(0, characterTypes[i].size() - 1);
            }
        }
    
        static constexpr std::array<std::string_view, 4> characterTypes = {
                "abcdefghijklmnopqrstuvwxyz",
                "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
                "0123456789",
                "!\"#$%&'()*+,-./:;<=>?@"
            };
        std::array<std::uniform_int_distribution<int>, characterTypes.size()> characterTypeDistributions;
        std::uniform_int_distribution<int> typeDistribution{0, characterTypes.size() - 1};
        std::mt19937 eng{std::random_device{}()};
    
        std::string generate(int numberOfCharacters){
            std::uniform_int_distribution<int> dist{0, 5};
            std::string password;
            password.reserve(numberOfCharacters);
            for (int i = 0; i < numberOfCharacters; i++)
            {
                auto type = typeDistribution(eng);
                password += characterTypes[type][characterTypeDistributions[type](eng)];
            }
            return password;
        }
    };
    
    int main(){
        int passwordLength = 12;
        PasswordGenerator gen;
        std::string password = gen.generate(passwordLength);
        std::cout << password << "\n";
        return 0;
    }
    

    Note that this code may still not meet high security standards for generating passwords.