最新消息:

QQ网站登录的RSA加密传输缺陷分析与用RSA加密实现Web登录密码加密传输

js代码 admin 3696浏览 0评论

感谢匿名人士的投递
QQ网站登录处没有使用https进行加密,而是采用了RSA非对称加密来保护传输过程中的密码以及敏感信息的安全性。 QQ是在javascript中实现整个过程的。这个想法非常新颖,但是也是存在严重缺陷的。如果被黑客利用,则可能被捕获明文密码。
分析报告如下:

Author: axis
Date: 2007-11-23
Team: http://www.ph4nt0m.org  (http://pstgroup.blogspot.com)
Corp: Alibaba B2B Corp / Infomation Security

这个想法非常新颖,详细可以参考云舒写过的 《RSA非对称加密的一些非常规应用》,地址为http://www.icylife.net/yunshu/show.php?id=471

这个原理简单描述为下:
1. 在server端生成一对RSA密钥,包括public key 和 private key
2. public key传输给客户端浏览器, 客户端浏览器用public key加密敏感数据,比如密码;加密后的密文传回给server,然后server用 private key解密。
3. 注意private key只保存在server端,而public key则分发给所有人。 由于 private key只有server知道,所以密文即使被截获了,也无法解开。

这个解决方案其实还是非常好的,至少他防住了大部分的攻击,但是为什么说它是无法替代https,是有缺陷的呢?

因为这个方案无法防止中间人攻击 (man-in-the-middle)。

攻击过程如下:
1. 攻击者通过MIM(比如arp欺骗等)劫持server与客户端浏览器之间的http包
2. 攻击者生成一对伪造的RSA密钥: fake public key/fake private key
3. 攻击者将js文件中的public key替换为fake public key,并传输给客户端浏览器
4. 客户端浏览器用 fake public key加密敏感数据,比如密码,并将加密后的数据传输给攻击者
5. 攻击者用fake private key解密,获得明文密码等
6. 攻击者用server的public key加密明文数据,并传送给server

整个过程中不会出现任何提示,而用户的明文数据则被窃取了!

而luoluo则提出来一个更邪恶的想法(顺便在这里祝luoluo今天生日快乐!),他提出可以直接将加密的介质修改。

比如,如果是用js在做加密,则修改js,如果是用flash或java applert做加密,则替换flash或applet,直接去掉这种加密机制,捕获明文密码。

那么为什么说https是不可替代的呢? 因为当实施中间人攻击的时候,浏览器会提示证书已改变(具体参考云舒的关于https安全性的文章),这种机制是内建在浏览器里的,攻击者无力改变它。所以这种报警是非常有意义的。

而如果像QQ一样使用js进行RSA加密传输,实施中间人攻击的时候,是不会有任何提示的,一切都会在用户不知情的情况下发生。

这种情况和以前windows的RDP中间人攻击情况一样: 当使用3389端口的rdp协议登录时候,证书改变的时候没有任何提示。

而相对设计比较安全的ssh协议,ssl协议等,则都会针对证书改变做出提示,防止中间人攻击。

所 以,QQ的这个方案只能保护传输过程中一般的sniffer攻击,但是考虑到当今网络环境下,大部分的sniffer都是基于arp欺骗的,所以这种保护 机制其实是非常脆弱的。它只能对抗目前已知的arp sniffer软件,而对专门开发的替换关键字的软件,则无法有效防御。一旦这种专门针对QQ网站登录的sniffer软件被开发出来并且提供下载,灾难 就不远了。

不过这个方案还是有积极意义的,除去不能抵抗中间人攻击的缺陷外,其他方面都比较完美,特别是成本低廉。如果与https结合使用来防止中间人攻击的话,整个方案就更完美了。

之前曾与朋友戏言QQ是否会因为我这一篇文章而多花费几百万的经费去购买https证书和https硬件加速服务器,现在让我们拭目以待,看看QQ是否是真正的用户至上。

希望QQ能越做越好。

 
转自:http://www.cnbeta.com/articles/43687.htm

用RSA加密实现Web登录密码加密传输

通常我们做一个Web应用程序的时候都需要登录,登录就要输入用户名和登录密码,并且,用户名和登录密码都是明文传输的,这样就有可能在中途被别人拦截,尤其是在网吧等场合。

这里顺带一个小插曲,我以前有家公司,办公室装修时候安排的网口相对较少,不太够用,于是我和另外一个同事使用了一个hub来共享一个网口,这就导 致了很有趣的现象:任何他的网络包我都能抓得到,当然了,我的他也能抓得到。这是不是有很大的安全隐患了?我有可能在不经意间会泄漏自己的密码。

所以,很多安全要求较高的网站都不会明文传输密码,它们会使用https来确保传输过程的安全,https是用证书来实现的,证书来自于证书颁发机 构,当然了,你也可以自己造一张证书,但这样别人访问你的网站的时候还是会遇到麻烦,因为你自己造的证书不在用户浏览器的信任范围之内,你还得在用户浏览 器上安装你的证书,来让用户浏览器相信你的网站,很多用户并不知道如何操作,就算会操作,也能也不乐意干;另一种选择是你向权威证书颁发机构申请一张证 书,但这样有一定的门槛,还需要付费,也不是我们乐意干的事。

所以,我打算自己实现一个密码加密传输方法。

这里使用了RSA非对称加密算法,对称加密也许大家都已经很熟悉,也就是加密和解密用的都是同样的密钥,没有密钥,就无法解密,这是对称加密。而非 对称加密算法中,加密所用的密钥和解密所用的密钥是不相同的:你使用我的公钥加密,我使用我的私钥来解密;如果你不使用我的公钥加密,那我无法解密;如果 我没有私钥,我也没法解密。

我设计的这个登录密码加密传输方法的原理图如下:

 

首先,先演练一下非对称加密:

 

static void Main(string[] args)
{
    //用于字符串和byte[]之间的互转
    UTF8Encoding utf8encoder = new UTF8Encoding();

    //产生一对公钥私钥
    RSACryptoServiceProvider rsaKeyGenerator = new RSACryptoServiceProvider(1024);
    string publickey = rsaKeyGenerator.ToXmlString(false);
    string privatekey = rsaKeyGenerator.ToXmlString(true);
            
    //使用公钥加密密码
    RSACryptoServiceProvider rsaToEncrypt = new RSACryptoServiceProvider();
    rsaToEncrypt.FromXmlString(publickey);
    string strPassword = "@123#abc$";
    Console.WriteLine("The original password is: {0}", strPassword);
    byte[] byEncrypted = rsaToEncrypt.Encrypt(utf8encoder.GetBytes(strPassword), false);
    Console.Write("Encoded bytes: ");
    foreach (Byte b in byEncrypted)
    {
        Console.Write("{0}", b.ToString("X"));
    }
    Console.Write("\n");
    Console.WriteLine("The encrypted code length is: {0}", byEncrypted.Length);

    //解密
    RSACryptoServiceProvider rsaToDecrypt = new RSACryptoServiceProvider();
    rsaToDecrypt.FromXmlString(privatekey);
    byte[] byDecrypted = rsaToDecrypt.Decrypt(byEncrypted, false);
    string strDecryptedPwd = utf8encoder.GetString(byDecrypted);
    Console.WriteLine("Decrypted Password is: {0}", strDecryptedPwd);
}

大家可以清楚看到,密码被加密成128字节长度的密文,为什么是固定128字节呢?这是因为我们的 RSACryptoServiceProvider默认生成的key的长度是1024,即1024位的加密,所以不管你要加密的密码有多长,它生成的密文 的长度肯定是128字节,也因为这样,密码的长度是有限制的,1024位的RSA算法,只能加密大约100个字节长度的明文,要提高可加密的明文的长度限 制,就得增加key的长度,比如把key改到2048位,这样能加密的明文的长度限制也就变为大概200出头这样……还是太少啊!而且这样会带来加密速度 的显著下降,RSA本来就很慢……是的,比同没有长度限制的对称加密,这种非对称加密的限制可真多,即便是200个字符,又能传输什么东西呢?——密码! 这个就够了,传输完密码之后,我们就使用对称加密,所以,RSA往往是用来“协商”一个对称加密的key的。

接下去,真正的难点在于用javascript实现一个和.net的RSA兼容的算法。密码学,对我来说真像天书一般,每次我一看就头大,这个工作 是没办法自己做的了,只能到网上找,那是相当的费力啊,找到许多js的RSA实现,但都和.net的这套东西不兼容,最后还是功夫不负有心人,终于找到了 一套。不多说,上代码:

 

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>RSA Login Test</title>
    <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script src="Scripts/jQuery.md5.js" type="text/javascript" ></script>
    <script src="Scripts/BigInt.js" type="text/javascript"></script>
    <script src="Scripts/RSA.js" type="text/javascript"></script>
    <script src="Scripts/Barrett.js" type="text/javascript"></script>
    <script type="text/javascript">
        function cmdEncrypt() {
            setMaxDigits(129);
            var key = new RSAKeyPair("<%=strPublicKeyExponent%>", "", "<%=strPublicKeyModulus%>");
            var pwdMD5Twice = $.md5($.md5($("#txtPassword").attr("value")));
            var pwdRtn = encryptedString(key, pwdMD5Twice);
            $("#encrypted_pwd").attr("value", pwdRtn);
            $("#formLogin").submit();
            return;
        }
    </script>

</head>
<body>
    <form action="Default.aspx" id="formLogin" method="post">
    <div>
        <div>
            User Name:
        </div>
        <div>
            <input id="txtUserName" name="txtUserName" value="<%=postbackUserName%>" type="text" maxlength="16" />
        </div>
        <div>
            Password:
        </div>
        <div>
            <input id="txtPassword" type="password" maxlength="16" />
        </div>
        <div>
            <input id="btnLogin" type="button" value="Login" onclick="return cmdEncrypt()" />
        </div>
    </div>
    <div>
        <input type="hidden" name="encrypted_pwd" id="encrypted_pwd" />
    </div>
    </form>
    <div>
        <%=LoginResult%>
    </div>
</body>
</html>

 

这是客户端代码,大家可以看到,基本没有什么服务器端代码,<%=postbackUserName%>用于回显输入的用户名,& lt;%=LoginResult%>用于显示登录结果,<%=strPublicKeyExponent%>和& lt;%=strPublicKeyModulus%>则用来告诉客户端RSA公钥。需要的javascript文件说明:

  • jQuery.md5.js –  用于对密码进行两次md5加密;(我通常在数据库中保存的用户密码是两次MD5后的结果)
  • BigInt.js – 用于生成一个大整型;(这是RSA算法的需要)
  • RSA.js – RSA的主要算法;
  • Barrett.js – RSA算法所需要用到的一个支持文件;

对于密码学,我几乎一无所知,所以没办法跟大家解释清楚RSA算法的原理,抱歉,我只知道怎么用。关于javascript中这行代 码:“setMaxDigits(129);”具体表示什么我也不清楚,我只知道,把参数改为小于129的数之后会导致客户端的javascript执行 进入死循环。服务器端代码也很简单:

protected void Page_Load(object sender, EventArgs e)
{
    LoginResult = "";
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    if (string.Compare(Request.RequestType, "get", true)==0)
    {
        //将私钥存Session中
        Session["private_key"] = rsa.ToXmlString(true);
    }
    else
    {
        bool bLoginSucceed = false;
        try
        {
            string strUserName = Request.Form["txtUserName"];
            postbackUserName = strUserName;
            string strPwdToDecrypt = Request.Form["encrypted_pwd"];
            rsa.FromXmlString((string)Session["private_key"]);
            byte[] result = rsa.Decrypt(HexStringToBytes(strPwdToDecrypt), false);
            System.Text.ASCIIEncoding enc = new ASCIIEncoding();
            string strPwdMD5 = enc.GetString(result);
            if (string.Compare(strUserName, "user1", true)==0 && string.Compare(strPwdMD5, "14e1b600b1fd579f47433b88e8d85291", true)==0)
                bLoginSucceed = true;
        }
        catch (Exception)
        {

        }
        if (bLoginSucceed)
            LoginResult = "登录成功";
        else
            LoginResult = "登录失败";
    }

    //把公钥适当转换,准备发往客户端
    RSAParameters parameter = rsa.ExportParameters(true);
    strPublicKeyExponent = BytesToHexString(parameter.Exponent);
    strPublicKeyModulus = BytesToHexString(parameter.Modulus);
}
 

转载请注明:jinglingshu的博客 » QQ网站登录的RSA加密传输缺陷分析与用RSA加密实现Web登录密码加密传输

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址