这个问题的根源是,你不能所有的账户都用一样的密码,如果你胆敢那么干,并且你曾经注册过 CSDN 之类的网站,那么黑客拿到你的用户名和密码后,会去所有可能的网站,碰碰运气。 负责任的说,中奖的机率要比彩票高。 你也不用担心黑客劳累过度、积劳成疾,这些操作都可以自动化,保质保量,准时准点。 (如果实在想不出激情四射的密码,起码换个理性低调的马甲)
所以我最近读了多篇文章,大概是普通青年想摆脱二逼的困境,向着文艺升华的节奏,讲怎么置办一系列比较安全的密码。 首先,所有密码都高度相似,这肯定是非常二逼的;如果所有密码完全不同,怎么记住又是个问题。 普通的解决方案,密码分为不同的安全级别,不同的安全级别采取不同的策略。不重要的密码,可以随便用身份证号,你朋友的母亲的隔壁邻居的小孩的那一条狗的生日什么的糊弄过去;重要的密码,要用 1Password、LastPass之类的工具生成,然后保存在云端(所以这个其实并不适合存储{敏感词}密码)。 这里的问题是,密码都存储在云端,可能需要联网才能使用;并且还要依赖于服务商的安全管理水平和职业操守,LastPass 经常爆安全漏洞,并且大面积丢失过密码(如果密码是随机生成的,丢失了后,恐怕也只能一边哼着“密码哪去了”,一边设法重置了)。 所以……我的意思不是说文艺青年应当用 1Password,它们本质上是一样的。 我们的目标是什么呢?
其实 hash 加密很容易达成这样的目标,以 SHA1 为例: doyouknowmypasswd?1 -> 740845e8ddf457a518a4c0ce3d44a95924e80497 doyouknowmypasswd?2 -> b28ab233ba23dc844bf1095606f80962b244e019 它的特点有:
相信很多文艺青年也想到了这种方式,但是为什么没有人采用呢? 废话,我也不这用这种方式的,因为:
现在我们已经从本质上解决了密码的问题,只需要一个这样的函数: genpwd primarypwd desc rev :: str -> str -> int -> int (三个参数为 主密码,描述,修订——有的时候你只是想单纯的更新一下密码,但是规则不变,怎么办呢?在待生成的字符串里加个修订版本就可以了。) 但是对于这个密码,还需要润色一下,让它能根据我们的要求变形,所以需要这样一个函数: fmtpwd genpwd rule size retry :: int -> str -> int -> int -> str (把刚才生成的密码,和规则、长度作为输入,输出美观的新密码。还有一个参数 retry 稍后再说) 先生成一个密码 def genseed desc, rev (Digest::SHA1.hexdigest @primary + desc + rev.tos).to_i(16) end
现在有了一个类似 b28ab233ba23dc844bf1095606f80962b244e019 这样的密码,假设需要 10 位,应该怎么办呢? 想到取前 10 位数的,拉出去毙了…… 平均分成 10 等分,然后每4个数对应一位? 现在我需要一个 1000 位的密码,怎么分 假使真的把这个密码切成一段一段的,我可以保证,非常麻烦,是个体力活。 我觉得最方便的办法,是线性同余,这是一个生成伪随机数列的方法。感受下,伪~随机,“伪”表示它的输出是固定的, 如果每次是真的随机的话,你只能唱“密码去哪儿了……”,根本停不下来的节奏;“随机”意味着你生成的密码看起来就像掷骰子掷出来的一样。 def lcg seed seed * 630360016 % 2147483647 end 用这个方法生成指定长度的密码: def gen_pwd seed, size, try [*1..size].inject([seed + try]){|n, x| n << (lcg n[-1])}[1..size] end 假设需要 10 位密码, gen_pwd b28ab233ba23dc844bf1095606f80962b244e019 , 10 , 0 后,结果是这样的 [339466767, 236817397, 1269734706, 159396745, 121834885, 865587496, 446364382, 144844773, 1659694774, 1631169047] 参数里为什么要有个 try 呢? 线性同余虽然有优良的周期分布,但是不能保证任何情况下都符合要求—— 生成一个只有特殊字符,而没有大小写和数字的密码,尽管概率极小,但不是完全没有可能。 这就需要对生成的密码进行校验,不符合要求的话,把 try 这个值递增上去重新生成,直到符合要求为止。 生成最终的密码: def fmt_pwd pwd, rule, custom rl = rule.flatten + custom.split('') pwd.map{|x| rl[x % rl.size] } end
校验是否符合要求: def verify pwd, rule return false unless pwd rst = rule.map do |r| lambda do for x in pwd return true if r.include? x end return false end[] end rst.inject{|n, x| n and x } end
最终的接口: def show desc, rule=false, rev:0, size:10, try:0, custom:@custom rl = unless rule then @@rule else rule.split('').map {|x| @@rules[x].toa } end seed = genseed desc, rev result = nil until verify result, rl pwd = genpwd seed, size, try result = fmtpwd pwd, rl, custom try += 1 end PassWord.new result.join, try end
最终生成的密码差不多是这个样子的 : "k1irM9wt0V", "l69WHePAj4", "lwVsF2TyLG" 最后,如果想用的舒服一点,可以自己设计一个用户级别的接口;另外可以把每条密码除主密码外的所有信息保存起来,JSON 或者数据库什么的;还可以在浏览器里把每条密码对应一个网址,实现自动填写……总之,你可以好好享受 DIY 的乐趣了 上面代码的 https://github.com/ran9er/diy_pwd |