Lowercasing the first character--by itself alone--does not address inverted case scenario (e.g. "PaSsWoRd" and "pAsSwOrD" evaluate to "paSsWoRd" and "pAsSwOrD" respectively).
One way to resolve that issue is to create two versions of the password (one with normal casing and one with inverted casing, but both with the first character lowercased), sort them, and pick the first result.
["pAsSwOrD", "PaSsWoRd", "PAsSwOrD"].map(function canonicalize(pwd) {
function invert(str) {
return (str == str.toUpperCase()
? str.toLowerCase()
: str.toUpperCase());
}
var head = pwd.substring(0, 1).toLowerCase();
var tail = pwd.substring(1);
var vers = [head + tail,
head + tail.split("").map(invert).join("")];
return vers.sort()[0];
});
Which gives the following result:
["pAsSwOrD", "pAsSwOrD", "pAsSwOrD"]
The three different variations get canonicalized into one version. With it, you can just store one hash instead of three.
One way to resolve that issue is to create two versions of the password (one with normal casing and one with inverted casing, but both with the first character lowercased), sort them, and pick the first result.
Which gives the following result: The three different variations get canonicalized into one version. With it, you can just store one hash instead of three.