客户端的密码加密

85

可能是重复问题:
关于客户端密码哈希系统的内容

我需要保护我的网站用户的密码。我在服务器端使用了MD5 加密 哈希。但问题是密码在到达服务器之前仍然以明文形式存在,这意味着密码可以通过流量监控被捕获。所以我想使用客户端密码加密/哈希机制并发送加密/哈希密码。 有人能告诉我如何做吗?


40
MD5不是加密。根据定义,加密的内容可以被解密。 - Gareth
没错,Gareth。MD5是一种单向的加密哈希算法,它不是加密,因为正如你所说,它不能使用公式进行解密。它只能被暴力攻击或与已知哈希值表进行比对。 - Mike Bethany
是的,当您存储密码的MD5哈希(或任何其他类型的哈希)时,永远不要忘记使用盐!(http://www.owasp.org/index.php/Hashing_Java#Why_add_salt_.3F) - Pascal Qyy
4
请记住,MD5也已被攻破。请参见此处http://www.win.tue.nl/hashclash/rogue-ca/,可以使用不同的数据创建相同的MD5。 - m.edmondson
这不是重复的问题,另一个问题是关于哈希,而这个问题是关于加密的。尽管一些不懂技术的人建议使用哈希作为解决方案,但它们非常不同。 - Pushpendra
@Pushpendra 这个问题是关于MD5哈希算法,而不是加密。在这种情况下,嘲笑“一些不懂技术的人”是不明智的。 - user207421
9个回答

135

这种方式并不安全,原因很简单:

如果在客户端对密码进行哈希处理并使用该令牌代替密码,则攻击者将不太可能发现密码是什么。

但是,攻击者不需要知道密码,因为您的服务器不再期望密码 - 它期望令牌。而攻击者可以知道令牌,因为它通过未加密的HTTP发送!

现在,可能会有一些应对挑战/响应形式的加密的方法,这意味着同一个密码会在每个请求中产生不同的令牌。然而,这需要在服务器上以可解密的格式存储密码,这并不理想,但可能是一种合适的折衷方案。

最后,你真的想要要求用户在登录您的网站之前开启JavaScript吗?

无论如何,SSL现在既不昂贵也不难设置


36
在解释为什么这个想法基本上是有缺陷的时候,我给它加了一个赞。SSL / TLS 在这种情况下是最好的选择。 - vcsjones
17
不,攻击者并不一定要使用登录页面。Web表单确实是构建要发送到服务器的Web请求的一种方式,但它并不是唯一的方式。有很多连接到服务器并模拟Web浏览器执行请求的方法。 - Gareth
4
可以在服务器上使用 HTTP 摘要认证(http://en.wikipedia.org/wiki/Digest_access_authentication#HTTP_digest_authentication_considerations / http://tools.ietf.org/html/rfc2617#section-3.3)来“拼凑出某种挑战/响应”,而无需以可解密的格式存储密码。RFC 对此有所说明:“请注意,HTTP 服务器实际上不需要知道用户的明文密码。” - Pascal Qyy
3
啊..那么客户端加密机制不会是一个好的解决方案.. - dinesh senartne
8
确实,这并不是对您的服务器进行保护。但是这是对您的客户(即您的用户或顾客)进行保护,这个保护同样重要,因为大多数用户使用糟糕的密码,并且经常重复使用它们。因此,他们会面临各种攻击的风险。在我看来,这使上述批评变得不太重要了。 - Kebman
显示剩余9条评论

118

11
+1 是因为你是唯一一个提供 OP 所需解决方案的答案(即客户端加密)的回答者。 - ine
2
谢谢大家 :) 我试图表达重点。 - Herr
6
很遗憾,使用JavaScript进行客户端加密或哈希将使这种系统相当不安全。请参见:http://www.matasano.com/articles/javascript-cryptography/ 。 - Bruno
@Bruno,听起来很有趣。我没有考虑过那些问题。 - Herr
@artjomb 谢谢,现在看起来更好了。啊,你可以给它点赞,alda :D - Herr
显示剩余3条评论

61

我会选择这个简单的解决方案

总结如下:

  • 客户端:“我想要登录”
  • 服务器生成随机数#S并将其发送到客户端
  • 客户端
    • 读取用户输入的用户名和密码
    • 计算密码哈希值,得到h(pw)(存储在数据库中)
    • 生成另一个随机数#C
    • 连接h(pw) + #S + #C并计算其哈希值,称之为h(all)
    • 向服务器发送username#Ch(all)
  • 服务器
    • 从数据库中检索指定username对应的h(pw)'
    • 现在它有所有元素来计算h(all'),就像客户端一样
    • 如果h(all)= h(all'),那么几乎可以确定h(pw) = h(pw)'

没有人能够重复请求以指定用户身份登录。 #S每次都会添加一个变量组件到哈希中(这是基本的)。#C在其中添加额外的噪音。


2
兄弟,我得说这个线程太棒了,我非常激动,你的答案却没有得到足够的赞赏,尽管它非常简单,但说明了一个基本的概念。我遇到了与原作者类似的情况,我想问一下关于你的解决方案的问题。 - jigzat
12
如果没有SSL/TLS,它仍然容易受到中间人攻击。这种情况下无法达到100%的安全性。如果你可以截取包含“用户名”、“#C”和“h(all)”的最终查询,你可以将其代替客户端发送并登录。 - singe3
1
你能否在哈希过程中重复加盐,并设置时间窗口(即 HTTP 请求的“分钟”),以减少中间人攻击的机会,因为服务器会根据超时拒绝无效的哈希吗? - Stephen
3
没有SSL保护,中间人可以发送他们自己的号码给你,甚至提供完全不同的定制JavaScript代码,这些代码将以明文方式传递密码。简单地说,如果没有SSL,所有的尝试只是为了防止初级脚本小子看到数据。我认为这可能比发送纯文本好一些,但在HTTP连接上,没有办法向决心攻击者隐藏信息。 - vgru
1
这个答案经过深思熟虑,黑客将不得不花费大量时间来反向工程创新算法。如果服务器每周扭曲和更新算法,黑客几乎不可能欺骗或愚弄服务器。现在我们拥有的是一种更像有机生物的算法,它能自适应地保护和对抗黑客(病毒)。 - S To
显示剩余5条评论

16

一般使用HTTPS来提供此类保护,以便Web服务器和客户端之间的所有通信都是加密的。

如何实现这一点的确切说明将取决于您的Web服务器。

Apache文档中有一个SSL配置指南,这可能会对您有所帮助。(感谢用户G. Qyy提供的链接)


3
@Mike他在哪里说过?我在任何编辑或评论中都看不到这个。 - Justin
3
并不是说他不能使用HTTPS,只是目前他还没有使用。 - Gareth
5
SSL目前是网络基础架构的重要组成部分。如果您不能使用SSL,则需要解决基础架构问题。在忽略明文流量的根本问题的情况下,进行一些客户端加密的花哨操作并不能解决任何问题。因此,“获取SSL”是一个确切的答案。 - deceze
5
我完全同意你们的看法,他应该使用SSL连接,但这不是他的问题。他的问题是,“如何在不安全的连接下保护密码?”我认为正确回答问题的方法是实际回答问题并解释为什么这是一个不好的主意。当你只告诉别人,“那是个坏主意。”时,他们就会停止倾听。但如果你给他们一些建议,然后解释为什么让他们自己陷入麻烦是不好的,他们倾向于听从你的建议,因为他们感觉你没有忽视他们的问题。 - Mike Bethany
1
@dinesh 如果需要添加HTTPS,不需要进行任何重大的重新工作,甚至可能根本不需要。只需使用代码,您可以添加一个非常简单的检查,以确保您正在使用HTTPS,并在不是的情况下进行重定向。根据您使用的服务器软件,您可能能够在服务器配置中完成此操作,因此根本不需要更改代码。最多,您可能需要添加一个从HTTP到HTTPS站点的单个重定向页面,但同样,这可能可以在服务器配置中处理。话虽如此,让您的代码也检查HTTPS无妨。 - Mike Bethany
显示剩余11条评论

10

我在底部列出了一个完整的JavaScript用于创建MD5,但如果没有安全连接,它就毫无意义。

如果你对密码进行MD5处理并将该MD5存储在数据库中,那么MD5就是密码。人们可以准确地知道你的数据库中有什么。实际上,你只是让密码变成了一个更长的字符串,但如果你在数据库中存储的是这个字符串,它仍然不安全。

如果你说,“好吧,我会对MD5再进行一次MD5”,那你就错了。通过查看网络流量或查看你的数据库,我可以欺骗你的网站并发送MD5。尽管这比重复使用明文密码要困难得多,但它仍然存在安全漏洞。

最重要的是,你不能在客户端加盐哈希而不发送未加密的盐到网络上,因此使加盐变得毫无意义。没有盐或已知盐的情况下,我可以暴力攻击哈希并找出密码。

如果您要使用未加密的传输方式进行此类操作,您需要使用公钥/私钥加密技术。客户端使用您的公钥进行加密,然后您使用您的私钥进行解密然后您使用MD5(使用用户唯一盐)对密码进行加密并将其存储在数据库中。这里有一个JavaScript GPL公钥/私钥库

无论如何,这是用于在客户端创建MD5的JavaScript代码(不是我的代码):

/**
*
*  MD5 (Message-Digest Algorithm)
*  http://www.webtoolkit.info/
*
**/

var MD5 = function (string) {

    function RotateLeft(lValue, iShiftBits) {
        return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
    }

    function AddUnsigned(lX,lY) {
        var lX4,lY4,lX8,lY8,lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
        if (lX4 & lY4) {
            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        }
        if (lX4 | lY4) {
            if (lResult & 0x40000000) {
                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            } else {
                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
            }
        } else {
            return (lResult ^ lX8 ^ lY8);
        }
    }

    function F(x,y,z) { return (x & y) | ((~x) & z); }
    function G(x,y,z) { return (x & z) | (y & (~z)); }
    function H(x,y,z) { return (x ^ y ^ z); }
    function I(x,y,z) { return (y ^ (x | (~z))); }

    function FF(a,b,c,d,x,s,ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function GG(a,b,c,d,x,s,ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function HH(a,b,c,d,x,s,ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function II(a,b,c,d,x,s,ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function ConvertToWordArray(string) {
        var lWordCount;
        var lMessageLength = string.length;
        var lNumberOfWords_temp1=lMessageLength + 8;
        var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
        var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
        var lWordArray=Array(lNumberOfWords-1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while ( lByteCount < lMessageLength ) {
            lWordCount = (lByteCount-(lByteCount % 4))/4;
            lBytePosition = (lByteCount % 4)*8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount-(lByteCount % 4))/4;
        lBytePosition = (lByteCount % 4)*8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
        lWordArray[lNumberOfWords-2] = lMessageLength<<3;
        lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
        return lWordArray;
    };

    function WordToHex(lValue) {
        var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
        for (lCount = 0;lCount<=3;lCount++) {
            lByte = (lValue>>>(lCount*8)) & 255;
            WordToHexValue_temp = "0" + lByte.toString(16);
            WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
        }
        return WordToHexValue;
    };

    function Utf8Encode(string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    };

    var x=Array();
    var k,AA,BB,CC,DD,a,b,c,d;
    var S11=7, S12=12, S13=17, S14=22;
    var S21=5, S22=9 , S23=14, S24=20;
    var S31=4, S32=11, S33=16, S34=23;
    var S41=6, S42=10, S43=15, S44=21;

    string = Utf8Encode(string);

    x = ConvertToWordArray(string);

    a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;

    for (k=0;k<x.length;k+=16) {
        AA=a; BB=b; CC=c; DD=d;
        a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
        d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
        c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
        b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
        a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
        d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
        c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
        b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
        a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
        d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
        c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
        b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
        a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
        d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
        c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
        b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
        a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
        d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
        c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
        b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
        a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
        d=GG(d,a,b,c,x[k+10],S22,0x2441453);
        c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
        b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
        a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
        d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
        c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
        b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
        a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
        d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
        c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
        b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
        a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
        d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
        c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
        b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
        a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
        d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
        c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
        b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
        a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
        d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
        c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
        b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
        a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
        d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
        c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
        b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
        a=II(a,b,c,d,x[k+0], S41,0xF4292244);
        d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
        c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
        b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
        a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
        d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
        c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
        b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
        a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
        d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
        c=II(c,d,a,b,x[k+6], S43,0xA3014314);
        b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
        a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
        d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
        c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
        b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
        a=AddUnsigned(a,AA);
        b=AddUnsigned(b,BB);
        c=AddUnsigned(c,CC);
        d=AddUnsigned(d,DD);
    }

    var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);

    return temp.toLowerCase();
}

没问题。当别人不回答问题时,即使答案是个坏主意,这是我很反感的事情。我甚至写了一篇博客文章来谈论它:http://picklepumpers.com/wordpress/?p=673 - Mike Bethany
@Mike,请允许我指出,您正对缺乏沟通技巧进行抱怨,但完全误解了OP对SSL的要求和知识。人类沟通是一个非常有缺陷且脆弱的事情,沟通技巧需要双向发展。 :) - deceze
4
感谢你的回复,你是正确的。我之前以为他不能使用 SSL 是因为他说他没有使用它。我的错。评判他人时也不妨自我审视一下。 - Mike Bethany
+1 对于建设性的自省。 :o) - deceze
其实并没有所谓的坏答案。尽管有些评论并不是我期望的解决方案,但它们仍然大大提高了我的知识水平。所以请继续发布您认为是解决方案的任何内容。再次感谢Mike :) - dinesh senartne

9

您在这个问题上打了标签,而SSL就是答案。好奇。


4

谢谢。实现起来更容易的是HTTPS还是HTTP Digest? - dinesh senartne
HTTP Digest机制中是否涉及证书,就像HTTPS一样? - dinesh senartne
1
对我来说,摘要(Digest)可能很有趣,因为你不需要操作证书,这是一项繁重且/或昂贵的工作:支付批准CA的费用,或在客户端部署您的CA证书,并且会过载您的服务器(加密对处理器来说是一项成本)。平均而言,一个SSL客户端的负载相当于10个非SSL客户端。但是,摘要并不加密任何内容!客户端和服务器之间交换的所有其他数据都是明文的。实现的复杂性不应该是您主要担心的问题,而应该是您处理的数据的机密级别。 - Pascal Qyy
例如:仅在身份验证时使用SSL保护您的密码,当您登录到CMS的后端以上传公共数据时是无用的(因为它还会在上传时加密数据,而这些数据将是公开的...)。但是对于处理您公司的私人数据的Web应用程序,这是非常有意义的! - Pascal Qyy
@G.Qyy:我想知道它在实践中的表现如何。看看XMLHttpRequest是否通过基本身份验证在第一个请求中发送用户名/密码会很有趣(当然,你不希望这样),或者后续的浏览器请求是否使用相同的凭据。此外,如果您提供了错误的用户名/密码并且服务器回复401,则会出现标准浏览器对话框,而不是您的页面。不过,感谢您指向那篇文章。 - Peter Štibraný
显示剩余8条评论

2

已有可用于JavaScript的MD5库。请注意,如果您需要支持没有JavaScript的用户,则此解决方案将无法使用。

更常见的解决方案是使用HTTPS。通过HTTPS,SSL加密在您的Web服务器和客户端之间协商,透明地加密所有流量。


1
他无法使用HTTPS。他在回答中说了这个。 - Mike Bethany
1
@MikeBethany他在他的回答或其他任何地方都没有这样说过。 - user207421
服务器解密SSL加密,将乱码文本转换回纯文本。当然,SSL可以防止外部窥探,但无法阻止服务器获取明文密码。 - Jarett Lloyd

0

对于类似的情况,我使用了 RSA 实验室的 PKCS #5:基于密码的加密标准。您可以通过将密码替换为仅能从密码生成的内容来避免存储密码(一句话)。有一些 JavaScript 实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接