【问题标题】:SSL Over Javascript基于 Javascript 的 SSL
【发布时间】:2014-03-28 22:51:35
【问题描述】:

我看到了一些类似的问题,这些问题似乎并不能完全解决我的确切用例,我想我已经找到了答案,但在安全性、RSA、几乎所有与之相关的东西。我对这些概念有基本的了解,但到目前为止我所做的所有实际实现都是关于编辑别人的代码而不是生成我自己的代码。无论如何,这就是我所在的地方:

我知道 Javascript 本身就是一个不适合进行加密的地方。有人可能会在您的响应中充当中间人并破坏 JS,因此您最终将通过网络发送未加密的数据。它应该通过 HTTPS SSL/TLS 连接来完成,但这种托管需要花钱,而且实际上应该与连接一起使用的官方签名证书也是如此。

话虽如此,我认为我将这样做的方式绕过了 JS 加密的中间人弱点,因为我只加密一件事(密码哈希)对于一个 RESTful 服务调用,然后仅使用该密码哈希对来自客户端的请求进行签名,以便将它们验证为来自请求声明的用户。这意味着 JS 只负责在创建用户帐户时加密一次密码哈希,如果服务器无法解码该密码,那么它就知道它已经拥有。

我还将保存一些客户信息,特别是 $_SERVER['REMOTE_ADDR'],以保证不会有人对注册交换本身进行 M-i-t-M。

我正在使用 PHP 的 openssl_pkey_ 函数来生成非对称密钥,并在客户端使用 Cryptico 库。我的计划是让用户向 REST 服务发送“预注册”请求,这将导致服务器生成密钥,将私钥和客户端信息存储在电子邮件地址索引的数据库中,然后响应使用公钥。

然后,客户端将使用公钥加密用户的密码哈希,并将其作为另一种请求类型发送到 REST 服务以完成注册。服务器将解密并保存密码哈希,使客户端信息和私钥无效,因此无法使用该信息进行进一步的注册,然后以200 状态码进行响应。

要登录,用户将输入他们的电子邮件地址和密码,密码将在注册期间进行哈希处理,附加到请求正文,然后再次哈希以签署登录端点的请求,该端点将尝试附加将哈希存储到请求正文并对其进行哈希处理,以根据请求中的签名验证签名,从而对用户进行身份验证。对服务的进一步数据请求将遵循相同的身份验证过程。

我是否遗漏了任何明显的漏洞?是否可以将 $_SERVER['REMOTE_ADDR'] 值欺骗为特定的东西?我不需要IP地址准确或与用户登录时相同,我只需要知道“预注册”并获得公钥的同一台机器跟进并完成注册而不是劫机者使用窥探的公钥为他们完成注册。当然,我想如果他们能做到这一点,他们已经在创建时劫持了无法恢复的帐户,并且合法用户将无法使用自己的密码完成注册,这也可以。

底线,除非我购买真正的 SSL 主机,否则有人还能破解我的服务吗?我是否绕过了 Javascript 作为加密工具的弱点?


当我编写和调试我的代码时,如果有人想使用它,我会在这里发布。如果我让我的网站受到任何形式的攻击,请告诉我。

这些函数根据标头中的哈希验证客户端请求、生成私钥、将其保存到数据库、使用公钥响应、解密并检查密码哈希。

        public function validate($requestBody = '',$signature = '',$url = '',$timestamp = '') {
            if (is_array($requestBody)) {
                if (empty($requestBody['signature'])) { return false; }
                if (empty($requestBody['timestamp'])) { return false; }
                if ($requestBody['requestBody'] === null) { return false; }

                $signature = $requestBody['signature'];
                $timestamp = $requestBody['timestamp'];
                $requestBody = $requestBody['requestBody'];
            }

            if (($requestBody === null) || empty($signature) || empty($timestamp)) { return false; }

            $user = $this->get();

            if (count($user) !== 1 || empty($user)) { return false; }
            $user = $user[0];

            if ($signature !== md5("{$user['pwHash']}:{$this->primaryKey}:$requestBody:$url:$timestamp")) { return false; }

            User::$isAuthenticated = $this->primaryKey;
            return $requestBody;
        }

        public function register($emailAddress = '',$cipher = '') {
            if (is_array($emailAddress)) {
                if (empty($emailAddress['cipher'])) { return false; }
                if (empty($emailAddress['email'])) { return false; }

                $cipher = $emailAddress['cipher'];
                $emailAddress = $emailAddress['email'];
            }

            if (empty($emailAddress) || empty($cipher)) { return false; }

            $this->primaryKey = $emailAddress;
            $user = $this->get();

            if (count($user) !== 1 || empty($user)) { return false; }
            $user = $user[0];

            if (!openssl_private_decrypt(base64_decode($cipher),$user['pwHash'],$user['privateKey'])) { return false; }
            if (md5($user['pwHash'].":/api/preRegister") !== $user['session']) { return false; }

            $user['session'] = 0;
            if ($this->put($user) !== 1) { return false; }

            $this->primaryKey = $emailAddress;
            User::$isAuthenticated = $this->primaryKey;
            return $this->getProfile();
        }

        public function preRegister($emailAddress = '',$signature = '') {
            if (is_array($emailAddress)) {
                if (empty($emailAddress['signature'])) { return false; }
                if (empty($emailAddress['email'])) { return false; }

                $signature = $emailAddress['signature'];
                $emailAddress = $emailAddress['email'];
            }

            if (empty($emailAddress) || empty($signature)) { return false; }

            $this->primaryKey = $emailAddress;

            $response = $this->makeUserKey($signature);
            if (empty($response)) { return false; }

            $response['emailAddress'] = $emailAddress;
            return $response;
        }

        private function makeUserKey($signature = '') {
            if (empty($signature)) { return false; }

            $config = array();
            $config['digest_alg'] = 'sha256';
            $config['private_key_bits'] = 1024;
            $config['private_key_type'] = OPENSSL_KEYTYPE_RSA;

            $key = openssl_pkey_new($config);
            if (!openssl_pkey_export($key,$privateKey)) { return false; }
            if (!$keyDetails = openssl_pkey_get_details($key)) { return false; }

            $keyData = array();
            $keyData['publicKey'] = $keyDetails['key'];
            $keyData['privateKey'] = $privateKey;
            $keyData['session'] = $signature;

            if (!$this->post($keyData)) { return false; }

            $publicKey = openssl_get_publickey($keyData['publicKey']);
            $publicKeyHash = md5($keyData['publicKey']);

            if (!openssl_sign($publicKeyHash,$signedKey,$privateKey)) { return false; }
            if (openssl_verify($publicKeyHash,$signedKey,$publicKey) !== 1) { return false; }

            $keyData['signedKey'] = base64_encode($signedKey);
            $keyData['rsa'] = base64_encode($keyDetails['rsa']['n']).'|'.bin2hex($keyDetails['rsa']['e']);
            unset($keyData['privateKey']);
            unset($keyData['session']);

            return $keyData;
        }

【问题讨论】:

  • 我想我不满意的两个方面是: 1. 不,我们不能信任 $_SERVER['REMOTE_ADDR'],在公司的反向防火墙中,所有用户都可以拥有相同的 IP(外部),具体取决于其配置。 2. 在非 SSL 场景中,您不能相信线路没有被监听(特别是现在我们知道“攻击”可能是他们防火墙网络中的某个人)。这并不是说我已经掌握了完整的攻击场景。
  • 最终,一切都在平淡无奇,因此无论您要保护的是什么,都在网络上“可见”。网络上的流氓用户可以简单地收听/重复合法用户发出的相同请求并传递相同的散列值 - 还是我错过了一步?
  • 时间戳计数器很简单而且很有帮助。用户必须在每个请求中进行身份验证?他们是键入他们的用户名/密码还是浏览器可能在本地存储中记住它?假设你有正确的散列;黑客无法执行他们想要的任何请求,但仍然可以“看到”所有内容,这取决于应用程序的有用程度,尽管一旦您建立了信任和共享“密钥”,您就可以开始加密内容。嗯..不错的项目。
  • 为什么不直接创建一个自签名证书并在 http 级别进行呢?然后只需对您的客户端进行编程以接受您的证书,即使它没有由 CA 签名。
  • MitM 可以充当所有请求的代理,告诉客户端在将散列值传递给服务器时不要散列 - 因此帐户注册成功。没有人会知道它已被破坏。因为我认为我们“信任”客户端,但客户端不能相信服务器是真实的。如果我们继续前进,我们将重新发明 SSL。在 JavaScript 中。

标签: php ssl openssl ssl-certificate cryptico


【解决方案1】:

您尝试做的是用自定义 JavaScript 替换对由证书颁发机构签名的 SSL 证书的需求。我不是安全专家,但据我所知,简单的答案是这是不可能的。

基本事实是,在公共互联网上,服务器无法信任客户端所说的内容,而客户端也无法信任服务器所说的内容,这正是由于中间人攻击造成的。之所以需要证书颁发机构,首先是为了建立某种公正的信任基础。浏览器供应商的 CA 是 carefully vetted,它是目前公共互联网上唯一可用的信任,尽管它肯定是 not perfect

【讨论】:

    【解决方案2】:

    我很想知道为什么相对便宜的 SSL 证书(例如 Digicert 的 1 年期 175 美元)是不可能的。特别是对于企业而言,175 美元/年是一笔合理的费用(相当于每月 12.60 美元左右)。

    【讨论】:

    • 这不是为了生意,我是为一个破产的政党写的。坦率地说,这是一个非营利组织的志愿者工作,也是我的学习经历。我承认 SSL 证书更可取,但 175 美元/年可以为我们购买大量文献和展位空间。我们也没有真正做任何需要安全的事情,比如电子商务或策划推翻麦莉赛勒斯,所以与其说是阻止坚定的网络犯罪分子,不如说是阻止恶意脚本小子的问题,而且预算非常紧张。
    • 另外,我很确定我在这里设计的流程仅在帐户注册时容易受到相当特定类型的攻击,并且大多数安全性甚至接近真正重要的功能for 将受到其他形式的身份验证(例如电子邮件和个人对个人代码验证)的保护,以便这些功能甚至可以弥补注册漏洞。无需额外花费一毛钱,更不用说 12.60 美元/月了。如果我们谈论机会成本,这是一个指向候选人的免费 Blogger 网站的域名。
    • 这不回答问题,应该在评论中
    猜你喜欢
    • 2014-12-11
    • 2013-03-01
    • 2016-03-31
    • 2011-02-10
    • 2019-07-04
    • 2016-02-20
    • 1970-01-01
    • 2017-02-28
    • 2011-12-23
    相关资源
    最近更新 更多