我使用很多不同的网站,我的第一个安全问题是永远不要重复使用密码。似乎每家像 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>