I use a lot of different sites, and my number one security issue is to never reuse a password. As it seems that every company out there like TalkTalk is as leaky as a sieve, I figure that's the right priority. However, it places a premium on generating random passwords, and remembering them. I am not keen on writing them down, and mistrust any closed source software method of storing them ('would you like the browser to remember your password for this site?', yeah, like heck!). I don't like the idea of one piece of hardware to store my passwords. I don't like the idea of writing a program in C or Python or something, that I can't run on every device I may want to use, say at work or visiting a friend. I guess I'm pretty fussy.
So I came up with this method nearly a decade ago. It slightly surprises me that I've not seen more of this type around or even talked about, because as far as I can see, it ticks many boxes. Which has got me concerned that it isn't such a good idea after all? Are there any security flaws that I don't mention, and are the ones I mention more serious than I think?
Anyhow, my reasoning was that any time I need a new password in a hurry, I am probably on a browser, which means I can run javascript. I keep a copy of the file in my online email archive, so can get to the source quickly on any new device. There is no personal information stored in the source, all the cryptographic security comes from the properties of the SHA-1 hash. It could obviously be improved by using later/better versions like SHA-2, but as I'm not protecting government secrets, the theoretical breakability of SHA-1 may still be the least of my worries.
The principle is to implement a random oracle, using SHA-1. Obviously, this approximation to it gives a finite length pseudo random answer, rather than the infinite length truly random answer of the theoretical oracle.
I am therefore relying on the ability of the hash to produce a string that's not predictable. As this is one of the design goals of cryptographic hashes, that much I trust.
When I go onto a new site, I type the site address into one field, and my hugely long master pass phrase into another, and a sequence into a third. The javascript concatenates the strings, submits them to SHA-1, and then translates the hash to an alphabet more suitable for the type of password I want. I assume no extra security from this last translation. This gives me a password keyed to the particular site. When I need to change the site password, I can simply increment the sequence number. I can therefore write down the sequence versus site on a scrap of paper, which is completely meaningless without the master pass phrase.
Obviously the other thing I am relying on for security is the privacy of my pass phrase.
This is the only program I have written with javascript, and written isn't really the word, it's just modified from something that looked similar. Besides maintaining this one, I don't intend to write any others. So while some bits may be clumsy and agricultural, I am not looking for comments on the style/quality of the code, only the security and functionality aspects.
So, questions ...
1) As my security relies on the secrecy of the master pass phrase, it can be subverted by a key-logger. I notice that when I log in to my bank, I have to enter a number of items using the mouse and a drop-down. Does this foil key-loggers? I notice Javascript has a .dropdown method, is it worth getting my head round this, or is the extra security that brings just theatre? How can I detect whether any device has been compromised by a key-logger? I tend to use Linux, though my wife insists on using windows.
2) I tend to copy the generated passwords, and paste into the box on the website (because I'm lazy). Is this a bad thing to do, or a really bad thing to do. Does not typing the password improve security against keyboard loggers, or does using the cut/paste buffer offer an easier to subvert channel and so degrades security?
3) Have I missed any big gotchas?
4) Why haven't I seen this type of password generator + rememberer talked about more, what is wrong with it?
5) I assume that my copy of SHA-1 doesn't have a back-door in it. While I haven't reverse engineered the version I found on the web to check compliance with the government published algorithm, I have run the published test input/output pairs on it, and it complies. It also doesn't seem to have an 'if input==test_case, output valid result' construct in it like VW cars have in their emission control systems. Is my reasoning valid?
6) Publishing the algorithm must reduce security, though the real security is in the master phrase secrecy and the properties of the hash. Is my reasoning valid?
7) I use passwords with significantly lower entropy than my master pass phrase. This should make it impossible to brute force my master phrase from any single password compromise. Is this right?
8) While my 3 letter / 2 digit combinations only have 12 bits of true entropy per set, using several of these groups is much better than most people do.
9) I'm not sure whether I have got a valid copyright combination given the 3rd party software I've included? My preferred license would be beer-ware.
This html runs in my browser as is, even with the leading spaces.
<head>
<title>NeilPass</title>
<style type="text/css">
</style>
<script type="text/javascript">
</script>
</head>
<body onLoad="document.mypass.site.focus()">
<script type="text/javascript">
// functions sha1Hash, f, ROTL and the Number class extension are unaltered from
// JavaScript Implementation SHA-1 Cryptographic Hash Algorithm (© 2005 Chris Veness)
// used here under the terms of the LGPL
// functions hex2base, fthomaform and my() are copyright Neil Thomas 2008
// the whole work is released under GPL
function hex2base(sin,aout)
{
// takes input string in hex
// converts it to quads of output defined in alphabet aout
// it reads enough hex digits from the input string to get at least
// 4 bits + 4*bitsout of significance into the fraction
// it's wasteful of bits, but hey, there are 160 in the hash, how many do you need?
// rinse and repeat until input string is exhausted
// double precision means it will happily cope with a 256 alphabet output
// it nods in the direction of arithmetic coding, but wasting bits instead of sliding a window
// with only "at least" 4 bits excess, the statistics of the least significant character
// of each quad is distorted by up to 6% from uniform probability, this is irrelevant for
// this application, but it should not be used as part of a random number generator
// it uses floating point arithmetic rather than integer. Implementations other than IEEE754
// might give slightly different results in the LSB.
var baseout=aout.length;
var bitsout = Math.log(baseout)/Math.log(2);
var ain="0123456789abcdef";
var hexin = Math.ceil(bitsout+1); // character quads get 4 input bits excess
var sout="";
var Nquads=Math.floor(sin.length/hexin);
var quad,frac,scale,digit,Nout;
for(quad=0;quad<Nquads;quad++)
{
frac=0;
scale=1/16;
for(digit=0;digit<hexin;digit++) //form the fraction from the hex input
{
frac += ain.indexOf(sin.charAt(quad*hexin+digit))*scale;
scale /= 16;
}
for(digit=0; digit<4; digit++) //split the fraction into the base output
{
Nout=Math.floor(frac*baseout);
frac = frac*baseout-Nout;
sout += aout.charAt(Nout);
}
sout += " "; //seperate the quads for legibility
}
return sout;
}
function fthomaform(sin)
{
// takes an input string in hex and Thomaforms it
// so each group of 3 hex digits is expanded into
// one 3 character word + a 2 digit number
// for ease of typing and memory
// There's only 12 bits of entropy per quint, but hey, it's a lot better than using
// your pet's name for all sites, and easier to use than mixed upper, lower and digits
// You just have to use enough quints to get up to your required total entropy.
var ain="0123456789abcdef";
var Nquints = Math.floor(sin.length/3); // one quint for each whole 3 digit hex
var words = new Array(4)
words[0] = "AceAgeAxeBagBarBatBedBetBoxBugBusCarCatCowCupDog";
words[1] = "PitFatFaxFogGapGasGetHamHenHogMixHugHutJamJarJaw";
words[2] = "PigJetBigManMatMaxMenMudMugNetNutPadPatPetPotRag";
words[3] = "RatRedRugRumSawSetSunTagTapToyTugVetWarWaxWebWet";
// nothing magic about these words, change at will, except notice that there are no
// lower case L or upper case O to confuse with digits
// and there are all reasonably distinct single syllable hard sounds, mostly concrete nouns
var quint,chno,inputN,hexin,sno,wno,d1,d2;
var result=""; //initialise the results string
for(quint=0;quint<Nquints;quint++) // for each input triple
{
inputN=0; // zero the accumulator
for(chno=0;chno<3;chno++) // count through chars in triple
{ // there's probably a hex read func that does this!
hexin=ain.indexOf(sin.charAt(quint*3+chno));
inputN=16*inputN+hexin;
}
sno=inputN &0x00000003; // get bottom 2 bits to choose the string
wno=(inputN>>2) & 0x0000000f; // get next 4 bits to index within it
d1=(inputN>>6)&0x00000007; // pull the digits out
d2=(inputN>>9)&0x00000007;
result+=words[sno].slice(3*wno,3*wno+3); // get the word
result+=ain.charAt(d1+2); // build the digits (offset by 2 to
result+=ain.charAt(d2+2); // avoid my 0 and 1 betes noirs)
//result += " "; // don't add a space between quints
}
return result;
}
function my()
{
var ssite=document.mypass.site.value;
var sseq=document.mypass.seq.value;
var spwd=document.mypass.pwd.value;
var msg=ssite.toLowerCase()+sseq+spwd;
var hs=sha1Hash(msg);
var pwh=sha1Hash(spwd);
// now reformat the hash into other alphabets
// you can edit the script to change character selections
// note my alphanumerics do not contain the characters 0, 1, I, O and l
// if you can't tell which is which easily, then you've just found out why I avoid them
// I raise the probability of the numerics by repeating the numbers several times
document.mypass.pwhash.value=pwh.slice(0,4);
document.mypass.pin.value=hex2base(hs.slice(0,16),"0123456789");
document.mypass.alpha.value=hex2base(hs.slice(0,20),"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
document.mypass.anum.value=hex2base(hs.slice(0,24),"2345678923456789abcdefghijkmnopqrstuvwxyz");
document.mypass.mixed.value=hex2base(hs.slice(0,28),"234567892345678923456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
document.mypass.hash.value=hs;
document.mypass.thomaformpass.value= fthomaform(hs.slice(0,12));
document.mypass.thomaformuser.value= fthomaform(hs.slice(12,18));
// document.mypass.seq.value = (+document.mypass.seq.value)+1; // DON't change sequence in a predictable way
}
function sha1Hash(msg)
{
// constants [§4.2.1]
var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
// PREPROCESSING
msg += String.fromCharCode(0x80); // add trailing '1' bit to string [§5.1.1]
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
var l = Math.ceil(msg.length/4) + 2; // long enough to contain msg plus 2-word length
var N = Math.ceil(l/16); // in N 16-int blocks
var M = new Array(N);
for (var i=0; i<N; i++) {
M[i] = new Array(16);
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
}
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
// note: most significant word would be ((len-1)*8 >>> 32, but since JS converts
// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
// set initial hash value [§5.3.1]
var H0 = 0x67452301;
var H1 = 0xefcdab89;
var H2 = 0x98badcfe;
var H3 = 0x10325476;
var H4 = 0xc3d2e1f0;
// HASH COMPUTATION [§6.1.2]
var W = new Array(80); var a, b, c, d, e;
for (var i=0; i<N; i++) {
// 1 - prepare message schedule 'W'
for (var t=0; t<16; t++) W[t] = M[i][t];
for (var t=16; t<80; t++) W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
// 2 - initialise five working variables a, b, c, d, e with previous hash value
a = H0; b = H1; c = H2; d = H3; e = H4;
// 3 - main loop
for (var t=0; t<80; t++) {
var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
var T = (ROTL(a,5) + f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
e = d;
d = c;
c = ROTL(b, 30);
b = a;
a = T;
}
// 4 - compute the new intermediate hash value
H0 = (H0+a) & 0xffffffff; // note 'addition modulo 2^32'
H1 = (H1+b) & 0xffffffff;
H2 = (H2+c) & 0xffffffff;
H3 = (H3+d) & 0xffffffff;
H4 = (H4+e) & 0xffffffff;
}
return H0.toHexStr() + H1.toHexStr() + H2.toHexStr() + H3.toHexStr() + H4.toHexStr();
}
//
// function 'f' [§4.1.1]
//
function f(s, x, y, z)
{
switch (s) {
case 0: return (x & y) ^ (~x & z); // Ch()
case 1: return x ^ y ^ z; // Parity()
case 2: return (x & y) ^ (x & z) ^ (y & z); // Maj()
case 3: return x ^ y ^ z; // Parity()
}
}
//
// rotate left (circular left shift) value x by n positions [§3.2.5]
//
function ROTL(x, n)
{
return (x<<n) | (x>>>(32-n));
}
//
// extend Number class with a tailored hex-string method
// (note toString(16) is implementation-dependant, and
// in IE returns signed numbers when used on full words)
//
Number.prototype.toHexStr = function()
{
var s="", v;
for (var i=7; i>=0; i--) { v = (this>>>(i*4)) & 0xf; s += v.toString(16); }
return s;
}
</script>
<form name="mypass">
<table>
<tr>
<td>Site Name:</td>
<td><input type="text" name="site" value="" onkeypress="if(event.keyCode==13)javascript:my();" /></td>
</tr>
<tr>
<td>Master Password:</td>
<td><input type="password" name="pwd" value="" onkeypress="if(event.keyCode==13)javascript:my();" /></td>
</tr>
<tr>
<td>Sequence:</td>
<td><input type="text" name="seq" value="000" onkeypress="if(event.keyCode==13)javascript:my();" /></td>
</tr>
</table>
<button type="button" onclick="javascript:my();">Generate Passwords</button>
<table>
<tr>
<td>Verification:</td>
<td><input type="text" name="pwhash" size="12" /></td>
</tr>
<tr>
<td>Numeric only:</td>
<td><input type="text" name="pin" size="48" /></td>
</tr>
<tr>
<td>ALPHA only:</td>
<td><input type="text" name="alpha" size="48" /></td>
</tr>
<tr>
<td>Alpha Numeric:</td>
<td><input type="text" name="anum" size="48" /></td>
</tr>
<tr>
<td>Mixed:</td>
<td><input type="text" name="mixed" size="48" /></td>
</tr>
<tr>
<td>Thomaform password:</td>
<td><input type="text" name="thomaformpass" size="48" /></td>
</tr>
<tr>
<td>Thomaform username:</td>
<td><input type="text" name="thomaformuser" size="48" /></td>
</tr>
<tr>
<td>SHA-1 Hash:</td>
<td><input type="text" name="hash" size="48" /></td>
</tr>
</table>
</form>
<p> To create a unique password/username for a site, enter the name of the site, eg Amazon or eBay (not case sensitive) into the <b>Site Name</b> box. Include the .com or not, but <i>be consistent</i>. The site box starts with focus, then tab down through the fields. Enter your long master password, eg "Your NotGuessable Frase" (case sensitive) into the <b>Master Password</b> box. Hit return in any entry box (only tested on IE6 and FF2, so YMMV), or click the "Generate" button, and a selection of password types will be generated. Use as many characters of the type you require. They are presented in quads (quints for Thomaform) to aid transcription and counting.
<p> The Master Password echos as * when typing, so how do you know you've entered it correctly? It would be a drag to have to type it twice, so the 16 bit hash of the password is also displayed to give confidence. A longer string is not used to mitigate against your password being brute-forced against the string, should you store the verification
<p> The <b>sequence</b> box is automatically filled in with "000". This string is simply concatenated between the site and master password. It can be changed to generate a new password with the same site and master credentials, should the site require a new password for any reason.
<p>
</ul>
<p> The raw hexadecimal hashes of the inputs are shown to aid verification. The hash of "abc" (site=a, Master=c, seq=b) should be <i>a9993e364706816aba3e25717850c26c9cd0d89d</i>. See the entry in Wikipedia for other input/hash test pairs.
<p> This script contains SHA-1 functions, copyright 2005 <a href="http://www.movable-type.co.uk/scripts/sha1.html" rel="external">Chris Veness</a> used under a <a href="http://www.fsf.org/licensing/licenses/lgpl.html" rel="external">LGPL</a> license).
</body>
</html>