用于密码生成和召回的 Javascript 中的随机 Oracle

信息安全 密码管理 javascript
2021-09-11 04:52:40

我使用很多不同的网站,我的第一个安全问题是永远不要重复使用密码。似乎每家像 TalkTalk 这样的公司都像筛子一样漏水,我认为这是正确的优先事项。但是,它非常重视生成随机密码并记住它们。我不热衷于将它们写下来,并且不相信任何存储它们的封闭源代码软件方法(“你想让浏览器记住你的这个网站的密码吗?”,是的,就像见鬼!)。我不喜欢用一个硬件来存储我的密码的想法。我不喜欢用 C 或 Python 或其他东西编写程序的想法,因为我无法在我可能想使用的所有设备上运行,比如在工作或拜访朋友时。我想我很挑剔。

所以我在近十年前想出了这个方法。让我有点惊讶的是,我还没有看到更多这种类型,甚至没有谈论过,因为据我所见,它打勾了很多框。这让我担心这毕竟不是一个好主意?有没有我没有提到的安全漏洞,我提到的安全漏洞是否比我想象的更严重?

无论如何,我的理由是,每当我急需一个新密码时,我可能在浏览器上,这意味着我可以运行 javascript。我在我的在线电子邮件存档中保留了一份文件副本,因此可以在任何新设备上快速找到源。源中没有存储个人信息,所有的加密安全都来自于 SHA-1 哈希的属性。显然可以通过使用 SHA-2 等更高/更好的版本来改进它,但由于我没有保护政府机密,SHA-1 的理论上的可破坏性可能仍然是我最不担心的问题。

原理是使用 SHA-1 实现随机预言机。显然,它的这种近似给出了一个有限长度的伪随机答案,而不是理论预言的无限长度的真正随机答案。

因此,我依靠散列产生不可预测的字符串的能力。因为这是加密哈希的设计目标之一,所以我非常信任。

当我进入一个新站点时,我在一个字段中输入站点地址,在另一个字段中输入我非常长的主密码短语,在第三个字段中输入一个序列。javascript 连接字符串,将它们提交给 SHA-1,然后将哈希转换为更适合我想要的密码类型的字母。我认为最后一次翻译没有额外的安全性。这给了我一个特定站点的密码。当我需要更改站点密码时,我可以简单地增加序列号。因此,我可以在一张纸上写下序列与站点,如果没有主密码短语,这完全没有意义。

显然,我依赖的另一件事是我的密码的隐私。

这是我用 javascript 编写唯一程序,写的不是这个词,它只是从看起来相似的东西修改而来。除了维护这个,我不打算写任何其他的。因此,虽然有些部分可能很笨拙和农业,但我并不是在寻找对代码风格/质量的评论,而只是在安全性和功能方面。

所以,问题...

1)由于我的安全依赖于主密码的保密性,它可以被键盘记录器破坏。我注意到当我登录我的银行时,我必须使用鼠标和下拉菜单输入一些项目。这会挫败键盘记录器吗?我注意到 Javascript 有一个 .dropdown 方法,是否值得我考虑一下,或者只是带来戏剧性的额外安全性?如何检测是否有任何设备被键盘记录器入侵?我倾向于使用 Linux,尽管我的妻子坚持使用 Windows。

2)我倾向于复制生成的密码,并粘贴到网站上的框中(因为我很懒)。这是一件坏事,还是一件非常糟糕的事。不输入密码会提高对键盘记录器的安全性,还是使用剪切/粘贴缓冲区更容易破坏通道,从而降低安全性?

3)我错过了什么大问题吗?

4)为什么我没有看到这种密码生成器+记忆器多讲,它有什么问题?

5) 我假设我的 SHA-1 副本中没有后门。虽然我没有对我在网上找到的版本进行逆向工程以检查是否符合政府发布的算法,但我已经在其上运行了发布的测试输入/输出对,并且它符合要求。它似乎也没有像大众汽车在其排放控制系统中那样具有“如果输入==测试用例,输出有效结果”结构。我的推理有效吗?

6) 发布算法必然会降低安全性,尽管真正的安全性在于主短语的保密性和散列的属性。我的推理有效吗?

7) 我使用的密码比我的主密码短语的熵低得多。这应该使我不可能从任何单个密码泄露中暴力破解我的主短语。这是正确的吗?

8) 虽然我的 3 个字母/2 位数字组合每组只有 12 位真熵,但使用其中几个组比大多数人做的要好得多。

9) 考虑到我包含的第 3 方软件,我不确定我是否获得了有效的版权组合?我首选的许可证是啤酒器具。

这个 html 在我的浏览器中按原样运行,即使有前导空格。

<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>
1个回答

SHA-1 的理论可破坏性可能仍然是我最不担心的问题

SHA-1 理论上只能通过抗碰撞性来破坏。您使用的方法不依赖此加密属性。

1)由于我的安全依赖于主密码的保密性,它可以被键盘记录器破坏。我注意到当我登录我的银行时,我必须使用鼠标和下拉菜单输入一些项目。这会挫败键盘记录器吗?我注意到 Javascript 有一个 .dropdown 方法,是否值得我考虑一下,或者只是带来戏剧性的额外安全性?如何检测是否有任何设备被键盘记录器入侵?我倾向于使用 Linux,尽管我的妻子坚持使用 Windows。

2)我倾向于复制生成的密码,并粘贴到网站上的框中(因为我很懒)。这是一件坏事,还是一件非常糟糕的事。不输入密码会提高对键盘记录器的安全性,还是使用剪切/粘贴缓冲区更容易破坏通道,从而降低安全性?

如果有可能安装了软件键盘记录器,那么在安全方面你已经输了——所有的赌注都没有了。原因是尽管键盘记录器是一种常见的、简单的获取凭据的方法,但它们仅被不成熟的攻击者单独使用。可以将恶意软件部署到您的机器上的攻击者可以通过更多的努力和技巧,将RAT部署到您的机器上,这可以轻松拦截或查看机器输入或传输的任何内容(例如,即使凭据本身没有被捕获,他们可以捕获登录会话 cookie 并通过将会话复制到他们自己的浏览器来远程接管您的浏览器会话)。

此外,由于您已经在 J​​avascript 程序中输入了主密码,因此不必担心复制和粘贴结果。

3)我错过了什么大问题吗?

从表面上看,这似乎是一种生成密码的合理方式,只要生成技术是秘密的(您的帖子暗示它将不再是秘密)。由于无法反转哈希,这似乎与使用具有强主密码的密码管理器一样好。我唯一担心的是将最低密码强度规则合并到您生成的密码中。例如,如果您需要符号和大小写字符的混合,并且您的哈希仅输出字母数字,那么您如何在生成技术中满足这些要求?

这也使我进入了具有迭代计数的密码更改机制。它确实解决了这个问题,但也增加了额外的复杂性。纸丢了怎么办?如果网站上没有重置功能,您可能会将自己锁定在帐户之外,并且您可能不得不恢复密码猜测。如果有,那么您需要从以前未使用过的值开始重复,否则通过重用旧密码会使事情变得不那么安全。

4)为什么我没有看到这种密码生成器+记忆器多讲,它有什么问题?

有一些关于,比如这个我还听说过另一种更商业化的服务,但这个名字让我忘记了。

问题在于,一旦知道密码生成,并且知道用户可能正在使用这种生成技术(即,如果某种方法变得流行),那么如果在任何地方的任何系统上都有一个泄露的密码,那么哈希值可能会被暴力破解,不仅会泄露主密码,还会泄露用户可能有权访问的任何系统的密码。在您使用 SHA-1 的情况下,问题在于这是一个加密散列函数,这意味着它速度很快 - 如果泄露的密码散列没有正确存储在受感染的设备上,攻击者可以每秒尝试数百万或数十亿次密码猜测地点。您应该使用密钥派生函数加强(拉伸)您的主密码. 例如,bcrypt 或 PBKDF2。

5) 我假设我的 SHA-1 副本中没有后门。虽然我没有对我在网上找到的版本进行逆向工程以检查是否符合政府发布的算法,但我已经在其上运行了发布的测试输入/输出对,并且它符合要求。它似乎也没有像大众汽车在其排放控制系统中那样具有“如果输入==测试用例,输出有效结果”结构。我的推理有效吗?

您可以将生成的哈希与算法的已知良好版本进行比较——这将确保算法是正确的。

6) 发布算法必然会降低安全性,尽管真正的安全性在于主短语的保密性和散列的属性。我的推理有效吗?

对于加密哈希:否 - 相反。加密哈希算法的安全性依赖于它的发布。通过这种方式,密码学家可以审查它并尝试破解它。如果十年后一切都好,那可能就OK了。

对于您的特定密码生成技术:是的,如上所述。这是因为它使用确定性方法从一些已知和未知的值中派生散列。如果散列输出已知,则可以通过猜测它们并找出它们是否导致已知散列来确定未知值。

7) 我使用的密码比我的主密码短语的熵低得多。这应该使我不可能从任何单个密码泄露中暴力破解我的主短语。这是正确的吗?

8) 虽然我的 3 个字母/2 位数字组合每组只有 12 位真熵,但使用其中几个组比大多数人做的要好得多。

如果您有一个哈希函数生成带有字母和数字的随机字符串,为什么在基于此键空间的蛮力时将其限制为如此小的熵?生成 26 个数字和字母字符,这将为您提供 128 位熵(基于键空间的蛮力)。

9) 考虑到我包含的第 3 方软件,我不确定我是否获得了有效的版权组合?我首选的许可证是啤酒器具。

这取决于第 3 方的许可,尽管这在此处确实是题外话。