how twitter detects password strength & weak password list

I was on the fence of whether to sign up a twitter account, one message caught my eyes during sign up: Too obvious, when I typed 1234567 in the password field. It made me wonder how twitter detects password strength.

Below are (stub) its client-side (in javascript) implementation detecting password strength,

In short, its password policies are:

  • minimal length 6
  • your typed password (after concerted to lower case) is not in their weak password list
  • a simple, custom function (function D) to test your password strength, that looks up for a list of meta characters like #,$,%,^,&,*,?,_,~,

The most important interesting part is it pre-fetches a list of weak password, I am not sure where it is from, probably base upon their analysis of failed attempts. You can find this list the bottom of this page.

function I(K) {
    var M = 0;
    var L = B.minlength ? B.minlength : 6;
    <!-- 
   // If less than 6 characters, display too short -->
    if (K.length < L) {
        return {
            score: K.length,
            message: _("Too short"),
            className: "password-invalid"
        }
    }
     <!-- 
    // convert password into lower case, and marks as invalid if it equals username -->
    if (B.username) {
        var N = (typeof(B.username) == "function") ? B.username() : B.username;
        if (N && (K.toLowerCase() == N.toLowerCase())) {
            return {
                score: 0,
                message: _("Too obvious"),
                className: "password-invalid"
            }
        }
    }
    
    <!-- 
    // Checks against its list of BANNED_PASSWORDS and marks input password as invalid if it matches any of the banned password -->
    if ($.inArray(K.toLowerCase(), twttr.BANNED_PASSWORDS) != -1) {
        return {
            score: 0,
            message: _("Too obvious"),
            className: "password-invalid"
        }
    }
   
    <!-- //enlarges length to 4 times larger, stick into a variable -->
    M += K.length * 4;

    <!-- //Removes duplicated chars -->
    M += (D(1, K).length - K.length) * 1;
    M += (D(2, K).length - K.length) * 1;
    M += (D(3, K).length - K.length) * 1;
    M += (D(4, K).length - K.length) * 1;

    <!-- //start to detect password strength-->
  
    <!-- number: increases 5 to the password strength -->
    if (K.match(/(.*[0-9].*[0-9].*[0-9])/)) {
        M += 5
    }

    <!-- //meta char: increases 5 to the password strength -->
    if (K.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) {
        M += 5
    }

     <!-- //lower, upper char: increases 5 to the password strength -->
    if (K.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {
        M += 10
    }
    if (K.match(/([a-zA-Z])/) && K.match(/([0-9])/)) {
        M += 15
    }
    <!-- //Both meta char and number, add 15 -->
    if (K.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && K.match(/([0-9])/)) {
        M += 15
    }

    <!-- //meta char & alpha, add 15 -->
    if (K.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && K.match(/([a-zA-Z])/)) {
        M += 15
    }
    
     <!-- //alpha or digits only, minus 10 -->
    if (K.match(/^\w+$/) || K.match(/^\d+$/)) {
        M -= 10
    }
    <!-- //reset to 0 if strength less than 0 -->
    if (M < 0) {
        M = 0
    }
    <!-- //reset to 100 if strength great than 0 -->
    if (M > 100) {
        M = 100
    }

    <!-- //Assign different messages according to the strength level -->
    if (M < 34) {
        return {
            score: M,
            message: _("Weak"),
            className: "password-weak"
        }
    }
    if (M < 50) {
        return {
            score: M,
            message: _("Good"),
            className: "password-good"
        }
    }
    if (M < 75) {
        return {
            score: M,
            message: _("Strong"),
            className: "password-strong"
        }
    }
    return {
        score: M,
        message: _("Very Strong"),
        className: "password-verystrong"
    }
}

function D(L, O) {
    var K = "";
    for (var N = 0; N < O.length; N++) {
        var P = true;
        for (var M = 0; M < L && (M + N + L) < O.length; M++) {
            P = P && (O.charAt(M + N) == O.charAt(M + N + L))
        }
        if (M < L) {
            P = false
        }
        if (P) {
            N += L - 1;
            P = false
        } else {
            K += O.charAt(N)
        }
    }
    return K
}

Here is its list of weak password, looks fairly interesting! From the development and performance perspective, it does't make any sense to that pre-loading this password list rather than trying similar approach like checking username availability, that makes an ajax call to the server. Anyway, I think it serve as a good list of weak passwords to security guys to add in their arsenal.

twttr.BANNED_PASSWORDS = ["111111", "11111111", "112233", "121212", "123123", "123456", "1234567", "12345678", "131313", "232323", "654321", "666666", "696969", "777777", "7777777", "8675309", "987654", "aaaaaa", "abc123", "abc123", "abcdef", "abgrtyu", "access", "access14", "action", "albert", "alexis", "amanda", "amateur", "andrea", "andrew", "angela", "angels", "animal", "anthony", "apollo", "apples", "arsenal", "arthur", "asdfgh", "asdfgh", "ashley", "asshole", "august", "austin", "badboy", "bailey", "banana", "barney", "baseball", "batman", "beaver", "beavis", "bigcock", "bigdaddy", "bigdick", "bigdog", "bigtits", "birdie", "bitches", "biteme", "blazer", "blonde", "blondes", "blowjob", "blowme", "bond007", "bonnie", "booboo", "booger", "boomer", "boston", "brandon", "brandy", "braves", "brazil", "bronco", "broncos", "bulldog", "buster", "butter", "butthead", "calvin", "camaro", "cameron", "canada", "captain", "carlos", "carter", "casper", "charles", "charlie", "cheese", "chelsea", "chester", "chicago", "chicken", "cocacola", "coffee", "college", "compaq", "computer", "cookie", "cooper", "corvette", "cowboy", "cowboys", "crystal", "cumming", "cumshot", "dakota", "dallas", "daniel", "danielle", "debbie", "dennis", "diablo", "diamond", "doctor", "doggie", "dolphin", "dolphins", "donald", "dragon", "dreams", "driver", "eagle1", "eagles", "edward", "einstein", "erotic", "extreme", "falcon", "fender", "ferrari", "firebird", "fishing", "florida", "flower", "flyers", "football", "forever", "freddy", "freedom", "fucked", "fucker", "fucking", "fuckme", "fuckyou", "gandalf", "gateway", "gators", "gemini", "george", "giants", "ginger", "golden", "golfer", "gordon", "gregory", "guitar", "gunner", "hammer", "hannah", "hardcore", "harley", "heather", "helpme", "hentai", "hockey", "hooters", "horney", "hotdog", "hunter", "hunting", "iceman", "iloveyou", "internet", "iwantu", "jackie", "jackson", "jaguar", "jasmine", "jasper", "jennifer", "jeremy", "jessica", "johnny", "johnson", "jordan", "joseph", "joshua", "junior", "justin", "killer", "knight", "ladies", "lakers", "lauren", "leather", "legend", "letmein", "letmein", "little", "london", "lovers", "maddog", "madison", "maggie", "magnum", "marine", "marlboro", "martin", "marvin", "master", "matrix", "matthew", "maverick", "maxwell", "melissa", "member", "mercedes", "merlin", "michael", "michelle", "mickey", "midnight", "miller", "mistress", "monica", "monkey", "monkey", "monster", "morgan", "mother", "mountain", "muffin", "murphy", "mustang", "naked", "nascar", "nathan", "naughty", "ncc1701", "newyork", "nicholas", "nicole", "nipple", "nipples", "oliver", "orange", "packers", "panther", "panties", "parker", "password", "password", "password1", "password12", "password123", "patrick", "peaches", "peanut", "pepper", "phantom", "phoenix", "player", "please", "pookie", "porsche", "prince", "princess", "private", "purple", "pussies", "qazwsx", "qwerty", "qwertyui", "rabbit", "rachel", "racing", "raiders", "rainbow", "ranger", "rangers", "rebecca", "redskins", "redsox", "redwings", "richard", "robert", "rocket", "rosebud", "runner", "rush2112", "russia", "samantha", "sammy", "samson", "sandra", "saturn", "scooby", "scooter", "scorpio", "scorpion", "secret", "sexsex", "shadow", "shannon", "shaved", "sierra", "silver", "skippy", "slayer", "smokey", "snoopy", "soccer", "sophie", "spanky", "sparky", "spider", "squirt", "srinivas", "startrek", "starwars", "steelers", "steven", "sticky", "stupid", "success", "suckit", "summer", "sunshine", "superman", "surfer", "swimming", "sydney", "taylor", "tennis", "teresa", "tester", "testing", "theman", "thomas", "thunder", "thx1138", "tiffany", "tigers", "tigger", "tomcat", "topgun", "toyota", "travis", "trouble", "trustno1", "tucker", "turtle", "twitter", "united", "vagina", "victor", "victoria", "viking", "voodoo", "voyager", "walter", "warrior", "welcome", "whatever", "william", "willie", "wilson", "winner", "winston", "winter", "wizard", "xavier", "xxxxxx", "xxxxxxxx", "yamaha", "yankee", "yankees", "yellow", "zxcvbn", "zxcvbnm", "zzzzzz"];

I also noticed there are some duplications in this list, which I don't have a clue of why. Let's see

<!-- From http://www.martienus.com/code/javascript-remove-duplicates-from-array.html -->
function unique(a) {
    var r = new Array();
    o: for (var i = 0, n = a.length; i < n; i++) {
        for (var x = 0, y = r.length; x < y; x++) {
            if (r[x] == a[i]) continue o;
        }
        r[r.length] = a[i];
    }
    return r;
}

>>> unique(BANNED_PASSWORDS).length
381
>>> BANNED_PASSWORDS.length
386

>>> show_duplicated(BANNED_PASSWORDS)
abc123
asdfgh
letmein
monkey
password

So, I wonder, if someone write a brute force script by loading this list, how many of early twitter user accounts will be comprised?

Comments

Interesting breakdown - very

Interesting breakdown - very useful too ;) However, am I the only one confused as to what function D is attempting to achieve? It certainly isn't looking for the characters you mention - that's done afterwards. It appears to be looking for the number of duplicated letters - is that right?

Thanks for dropping by.

Thanks for dropping by. Interesting question. function D looks like, what you said, attempts to locate duplicated letters. But I also don't have a clue where this function is called :(

It's called 4 times in lines

It's called 4 times in lines 29-32 for different values of L

@mrben: I finally got some

@mrben:

I finally got some time to play around this, here are my breakdown:
D(1, "aaabcdef") returns abcdef
D(2, "aaabcdef") returns aaabcdef

K = "aaabcdef";
D(1, K).length - K.length) * 1;
this will return abdef

The way these two functions work are it first do basic detection (length <= 6, whether your password equals user name, whether it is in the array). Then call D(), that it basically checks for duplications, four times to calculate the strength of M (0 - 100), it returns different messages (0-34, 35-50, 51 -75, 76 -100) per the level of strength, which is an interesting approach.

P.S: I added comments to the original code, let me know if I am mistaken any part of them.

^ Top of Page