6

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>
3

the theoretical breakability of SHA-1 may still be the least of my worries

SHA-1 is only theoretically broken via collision resistance. The method you are using does not rely on this cryptographic property.

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?

If there's the possibility that a software keylogger is installed, then security wise you've already lost - all bets are off. The reason is that although keyloggers are a common, simple way to gain credentials, they are only employed in isolation by unsophisticated attackers. An attacker that can deploy malware onto your machine could, with a bit more effort and skill, deploy a RAT to your machine which could easily intercept or enable viewing of anything entered or transmitted by the machine (e.g. even if credentials themselves aren't captured, they could capture login session cookies and take over your browser session remotely by duplicating the session to their own browser).

Also, as you've already typed your master password into your Javascript program, being concerned about copying and pasting the result is the least of your worries.

3) Have I missed any big gotchas?

It seems a reasonable way of generating passwords on the face of it, so long as the generation technique is secret (of which your post is alluding to that it will not be any longer). As there's no way to reverse the hash, this appears to be just as good as using a password manager with a strong master password. The only concerns I have are about incorporating minimum password strength rules into your generated passwords. For example, if you are required to have symbols and a mixture of upper and lower case characters and your hash is only outputting alphanumerics, how do you meet these requirements in your generation technique?

This also leads me onto the password change mechanism with the iteration count. It does solve this problem, but it also adds additional complexity. What if you lose the paper? You may lock yourself out of an account and you may have to revert to password guessing if there is no reset functionality on the site. If there is then you would need to start reiteration from a value you have not used before, otherwise you are making things less secure by reusing an old password.

4) Why haven't I seen this type of password generator + rememberer talked about more, what is wrong with it?

There are some about, like this one. There was another, more commercial service I have heard of also, but the name escapes me.

The trouble is that once the password generation is known, and it is known that a user could be using such a generation technique (i.e. if a certain method becomes popular), then if there is a single leaked password on any system anywhere, then the hash value could be brute forced revealing not only the master password, but the password of any system the user might have had access to. And the trouble in your case of using SHA-1 is that this is a cryptographic hash function, meaning it is fast - an attacker can try millions or billions of password guesses per second should the leaked password hash not have been stored correctly on the compromised site. You should be using a Key Derivation Function to strengthen (stretch) your master password. For example, bcrypt or PBKDF2.

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?

You could compare generated hashes to a known good version of the algorithm - this will ensure the algorithm is correct.

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?

For cryptographic hashes: No - the opposite. The security of an cryptographic hash algorithm relies on it being published. This way cryptographers can review it and try to break it. If all is good in ten years then it may be OK.

For your specific password generation technique: Yes, as above. This is because it is using a deterministic method to derive a hash from some known, and unknown values. If the hash output is known, then the unknown values can be determined by guessing them and finding out if they result in the known hash.

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.

If you have a hash function generating random strings with letters and numbers why limit it to so little entropy when based on a brute force of this keyspace? Generate 26 characters of numbers and letters and this will give you 128 bits of entropy instead (based on brute force of keyspace).

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 depends on the license of the 3rd party, although this is really off topic here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.