0

I am working on a JS function for generating password strings, it takes four parameters for a-z lowercase, A-Z uppercase, 0-9 and punctuations. I put together a base string, like this:

function genpwd(azlc,azuc,num,pun,len) {
    var chars = "";
    if (azlc) chars += "abcdefghijklmnopqrstuvwxyz";
    if (azuc) chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    if (num)  chars += "012345678901234567890123";
    if (pun)  chars += "!@#%&()=?+-_.:,;*{}[]^$/";

Then I loop through the base string (given length of the password) and randomly picking out chars to a new string and returns this as the output password.

    for(i=0;i<len;i++) {
        nextChar = chars.charAt(Math.floor(Math.random()*charsN));
        password += nextChar;
    }
    return password;
}

This is a really simple way of generating a random string, but it does not guarantee that at least one char from each "char group" actually is included in the output string.

I have looked at some other examples on how to use regexps, but can't figure out how to modify them to work the way I want. I'm thinking the "easiest" way to solve this is probably by using a regexp - something like this:

if (password.magic_regexp_stuff) {
    return password;
} else {
    password = genpwd(azlc,azuc,num,pun,len);
}
  1. Am I on the right track?
  2. Can anyone help me with the regexp?

UPDATE:

Ok, so after some valuable input from all of you, I ended up with this function.

I also switched from mVChr suggestion (thanks man!) to the one posted by Py, so I'm pretty sure the "statistics problem" (don't have any other word for it) pointed out by NobRuked won't be a problem any more. Can someone confirm this please? :)

I also had to modify the function's parameters and approach to the sets of chars to be used. Any suggestions on improvements?

function passwd(len, azlc, azuc, num, pun) {
    var len = parseInt(len),
        ar = [],
        checker = [],
        passwd = '',
        good = true,
        num, num2,
        sets = 0;
    if(!len || len < 4) len = 12;
    if(!azlc && !azuc && !num && !pun) { azlc = 1; azuc = 1; num = 1; pun = 1; }    

    if (azlc) {
        ar.push("abcdefghijklmnopqrstuvwxyz");
        checker.push(0);
        sets++;
    }
    if (azuc) {
        ar.push("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        checker.push(0);
        sets++;
    }
    if (num) {
        ar.push("0123456789");
        checker.push(0);
        sets++;
    }
    if (pun) {
        ar.push("!@#%&()=?+-_.:,;*{}[]^$/");
        checker.push(0);
        sets++;
    }
    for (var i=0;i<len;i++){
        num=rand(0,sets);
        num2=rand(0,ar[num].length);
        passwd+=ar[num][num2];
        checker[num]=1;
    }
    for (var i=0;i<sets;i++){
        if(checker[i]===0)
            good=false;
    }
    if (good){
        return passwd;
    }
    else{
        return passwd(len);
    }
}

Many thanks to everyone for your help, it's appreciated!

José
  • 391
  • 3
  • 14
  • Why do you want 0, 1, 2, and 3 to be 1,5 times more likely to be part of the password than the other digits? – Tim Pietzcker Sep 02 '11 at 16:18
  • thats just a temporary "fix" for making it more likely to get a number in the output string at all :) It will of course be changed when/if I can validate the output. – José Sep 02 '11 at 16:20
  • First. You should use a while, not if. Because the second time you generate a password, it can still not contain all chars. Second. The regExp has the be variable based on the values of azlc,azuc,num,pun. So you can't use one single regExp. Why not pick one random character from every category that is selected; then fill the string to the length required with all available characters; and than shuffle the characters around. – Gerben Sep 02 '11 at 16:26

6 Answers6

4

I don't think regular expressions are the right tool for the job. Your attempt could theoretically loop forever (though highly unlikely) since there is no guarantee a generated password will ever match the regular expression.

I guess the easy way of making sure one character from each group is included is to explicitly include it. So, assuming your password is to be at least 4 characters long, I would suggest the following pseudo-code:

chars = charFrom(azlc) + charFrom(azuc) + charFrom(num) + charFrom(pun)
do the following, length_of_password - 4 times:
    chars += charFrom(azlc + azuc + num + pun)
chars = shuffle(chars)

Implementation of charFrom() and shuffle() are left as an exercise to the reader.

NobRuked
  • 593
  • 4
  • 12
  • 2
    And to randomize the order, i'd say look at this topic: http://stackoverflow.com/questions/962802/is-it-correct-to-use-javascript-array-sort-method-for-shuffling – Py. Sep 02 '11 at 16:28
2

To do this with regular expressions, you really just need (up to) four regular expressions:

function genpwd(azlc,azuc,num,pun,len) {
    var chars = "", regexs = [],

    testRegexs = function(pw) {
        var ii;
        for (ii = 0; ii < regexs.length; ii += 1) {
            if (!regexs[ii].test(pw)) {
                return false;
            }
            return true;
        }
    };

    if (azlc) { 
        chars += "abcdefghijklmnopqrstuvwxyz"; 
        regexs.push(/[a-z]/); 
    }
    if (azuc) { 
        chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
        regexs.push(/[A-Z]/); 
    }
    if (num) { 
        chars += "012345678901234567890123"; 
        regexs.push(/0-9/); 
    }
    if (pun) { 
        chars += "!@#%&()=?+-_.:,;*{}[]^$/"; 
        regexs.push(/[\!\@\#\%\&\(\)\=\?\+\-\_\.\:\,\;\*\{\}\[\]\^\$\/]/); 
    }

    do
    {
        // Generate a password...
    }
    while (!testRegexs(password));

    return password;
}
FishBasketGordo
  • 22,904
  • 4
  • 58
  • 91
  • You forgot the character class brackets around the last two (and you don't need to escape all those characters). And, of course, you can do it in a single regex. – Tim Pietzcker Sep 02 '11 at 16:43
  • Yeah, I knew I would mess up the punctuation regular expression. Also, I think it's better not to do it in a single regex because it's easier to maintain/modify four simple regexes than one more complex one. – FishBasketGordo Sep 02 '11 at 16:53
1

I would take another approch.

I'd have 4 strings

lower = "abcdefghijklmnopqrstuvwxyz";
upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
num = "0123456789";
other = "!@#%&()=?+-_.:,;*{}[]^$/";

Then to generate the password, i'll take a random integer between 0 and 3, and then another integer between 0 and the choosen string to take a character.

To check if everything was taken, I'd just have to check that the 4 integer have been taken in the first random choice.

the code would look like

function psswd(len){
    var ar=[lower,upper,num,other],
        checker=[0,0,0,0],
        passwd="",
        good=true,
        num,num2;
    for (var i=0;i<len;i++){
        num=randomInt(0,3);
        num2=randomInt(0,ar[num].length);
        passwd+=ar[num][num2];
        checker[num]=1;
    }
    for (var i=0;i<3;i++){
        if(checker[i]===0)
            good=false;
    }
    if (good){
        return passwd;
    }
    else{
        return psswd(len);
    }
}

Might not be optimal, but no regexp needed.

Py.
  • 3,499
  • 1
  • 34
  • 53
1
function genpwd(azlc,azuc,num,pun,len) {
  var sets = [], 
      pw = [],
      i, j, t;
  if (azlc) sets.push("abcdefghijklmnopqrstuvwxyz");
  if (azuc) sets.push("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  if (num)  sets.push("0123456789");
  if (pun)  sets.push("!@#%&()=?+-_.:,;*{}[]^$/");
  while (len) {
   pw.push(sets[len%sets.length][~~(Math.random()*sets[len%sets.length].length)]);
   len--;
  }
  i = pw.length;
  while (--i) {
    j = ~~(Math.random()*(i+1));
    t = pw[j];
    pw[j] = pw[i];
    pw[i] = t;
  }
  return pw.join('');
}

EDIT: added shuffle

mVChr
  • 49,587
  • 11
  • 107
  • 104
  • After trying all of the above examples, I choose to go with this one. Thanks everyone helping me! – José Sep 02 '11 at 16:53
  • While this answer is quite impressive, the resulting password is less random than one should strive for. Each group (here: set) is represented at least floor(len / sets.length) times and at most ceiling(len / sets.length) times. Given the amount of effort OP is putting into generating a random password, this (at least partially) defeats the purpose. (e.g. Assuming all 4 classes used and password length of 8, I **know** the password will contain 2 digits. Without the shuffling, I even know **where** those digits will be.) – NobRuked Sep 02 '11 at 17:15
  • NobRuked: Thanks for the input! If I am understanding the other examples right, this is a problem for them too? – José Sep 02 '11 at 18:22
  • @NobRuked good point, just satisficing. Jonas, added shuffle. – mVChr Sep 02 '11 at 18:48
0

Just for the record, validation can be done in a single regex:

/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#%&()=?+_.:,;*{}\[\]^$\/-])/.test(mypassword)

As for your password generation scheme, see this.

Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
  • Thanks for the info. I've read the cartoon before :) But words like "horse", "battery" and "correct" are all in the English dictionary. So a word list based brutforce attack could be quite effective against passwords like that. – José Sep 02 '11 at 17:02
  • Quite the opposite. A dictionary attack won't work because `"correct horse battery staple"` is not in the dictionary. You have to look at it as the entire string, not separate words. – Tim Pietzcker Sep 02 '11 at 17:12
  • [Troy Hunt](http://www.troyhunt.com/2011/08/im-sorry-but-were-you-actually-trying.html) has an interesting explanation as to why the XKCD solution suggested may not be as effective as one might think. – NobRuked Sep 02 '11 at 17:23
0

This is the final version of my function (I also updated the question post with some info).

Thanks again to everyone helping me! :)

/**
 * Generate Password
 * ---
 * Generates a random text string containing characters types according to given 
 * parameters. Uses all character types by default. Output string length will at 
 * minimum be the number of character types currently in use.
 *
 * @param   int     Output string lenght
 * @param   bool    Use letters a-z lower case
 * @param   bool    Use letters A-Z upper case
 * @param   bool    Use numbers 0-9
 * @param   bool    Use punctuations (!@#$?*...)
 * @return  string  Generated password string
 */

function genPwd(l,w,x,y,z){
    var a=[],c=[],p='',g=true,n1,n2,s=0,i=0;
    if(!w&&!x&&!y&&!z){w=1,x=1,y=1,z=1;}
    if(w){c.push(0);s++;a.push("abcdefghijklmnopqrstuvwxyz");}
    if(x){c.push(0);s++;a.push("ABCDEFGHIJKLMNOPQRSTUVWXYZ");}
    if(y){c.push(0);s++;a.push("012345678901234567890123456789");}
    if(z){c.push(0);s++;a.push("!@#%&/(){}[]=?+*^~-_.:,;");}
    if(l<s){l=s;}for(i=0;i<l;i++){n1=Math.floor(Math.random()*(s-0));
    n2=Math.floor(Math.random()*(a[n1].length-0));p+=a[n1][n2];c[n1]=1;}
    for(i=0;i<s;i++){if(c[i]===0)g=false;}
    if(g){return p;}else{return genPwd(l,w,x,y,z);}
}
José
  • 391
  • 3
  • 14