Hacker News new | past | comments | ask | show | jobs | submit login

Having recently had to implement improved password security for a customer who wouldn't read XKCD #936, the internationalization thing was a pain in the ass.

They wanted at least 10 characters, and at least one uppercase, one lower, one digit, and one special character. Easy enough with .NET's built-in membership stuff by setting:


The "\p{Lu}" part handles uppercase characters even in Unicode chars, but Javascript has no equivalent, so I couldn't do client-side validation of that. Should be validating on both ends anyway, but it's still a pain.

The real part I hated was having to keep track of users' last N passwords to make sure they didn't re-use them. Since everything's hashed and salted, I just kept a table of previous hashes by user. Seems simple, but MS didn't see fit to include a HashPassword(string plainTextPassword, byte[] userSalt) method in the membership provider, so I had to reverse engineer their password-hashing method to check when they change passwords if it's something that's been used before.

Then I realized that they could just change their password N+1 times in about a minute, then re-use their expired password anyway, so we wound up having to set a minimum age of N weeks before a password could be reused as well.

The whole problem is an exploding requirements nightmare that could easily be solved by saying "Must be >32 characters and don't write it down anywhere, idiot."

The worst part is as much as I hate these types of requirements, I now perfectly understand why these systems are the way that they are.

How did you handle password++ iteration? For example P@ssword01, P@ssword02, P@ssword03 hash differently.

There's an easy way to measure entropy non-heuristically: see how well the password compresses. Then, to measure whether a new password is a variation of an old one, you just compress their concatenation and see if you get less entropy than expected.

That requires storing the old password in plain text (or something reversible to plain text, like lossless compressed formats). That is typically not recommended as old passwords can give clues to the current password.

Nah; you can require that they enter the old password to change their password, check that they've entered the right one, and then use that (while you're still holding onto it) to do the cross-compression check.

If you have both the old and new password in plain text you don't need to do a compression-based check, just a raw iterative approach would accomplish the same thing with very low overhead (in either CPU or memory accesses).

Compression systems typically need a lookup table of some kind and have more overhead than just the raw comparison.

As mentioned in another comment, I'd probably do a Levenshtein distance between the old and new passwords, and reject if they crossed some threshold. However, only knowing the plaintext of the immediately-preceding password as they enter it to authorize the change, it wouldn't do much to stop them from doing:

PasswordA SomeOtherPassword1 PasswordB SomeOtherPassword2 PasswordC SomeOtherPassword3

Just iterate on every other change, and you've beaten the requirement.

That's assuming what you want is to find passwords that are "the same" in a literal sense, rather than "the same" as in sharing a common generation algorithm that biases certain outputs.

For example, if a user's first password is "first123!@#" and their second password is "second456$%^", there are no letters in common—but those two passwords, when joined together, are very intracompressible (by an ideal compressor)—and likewise, an attacker who knew that the first was a previously-used password would be very likely to try the second. That both properties apply is not a coincidence of this particular password; the intra-compressibility of a set of plaintexts, and the predictability of unknown plaintexts of the set from known ones, are equivalent measures of informational entropy.

I would guess that it didn't.. just another example of the exploding requirements.

How do you handle (Do you?):

* not more than 2 identical characters in a row (e.g., 111 not allowed) * Name/Username in password (Name: Chuck Norris, username: ChuckNorris, Password: ChuckNorris#1)

These are reasons why I don't look forward to doing this and also why I'm leaning towards G+/FB/twitter/etc authentication in an app I'm planning.

Didn't in either case, because they weren't in the requirements, and the first one, while well-meaning, just further decreases entropy. I got into an email fight with our network security over trying to use a 40-character password LastPass generated that happened to have 2 identical chars in a row, and not being allowed. Not more than one identical character in a row is more secure than not more than 2, apparently.

For the second, I'd probably just do something like compute the Levenshtein distance between the username and password, and reject it if it passed some threshold.

> and at least one uppercase, one lower, one digit, and one special character

Not to nitpick, but they wanted at least 3 of those 4. Is that possible with a regular expression or are we now into the custom validator territory?

It's possible with a regex. Here's one spectacularly awful way: enumerate through all the permutations of 3 out of 4 character classes. Assume the classes are A, B, C, and D just to simplify the syntax here:


Well, they wanted 4 of 4, actually, and yes, I had to do custom validation. Everything except the upper-case character worked in JS, so everything "just worked" if I took that part out, but that wasn't an option.

My bad, I thought you were referencing the OWASP site specifically.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact