对 cookie 进行编码,使其不会被欺骗或读取等

Posted

技术标签:

【中文标题】对 cookie 进行编码,使其不会被欺骗或读取等【英文标题】:Encoding cookies so they cannot be spoofed or read etc 【发布时间】:2011-06-27 22:30:01 【问题描述】:

我正在编写一个 php 应用程序。我想将用户登录信息存储在 cookie 中,这样用户就不必在每次访问时都登录。

我想对它们进行编码或混淆,以便它们无法被读取或篡改。

最好的方法是什么?

更新:

我不会在 cookie 中存储密码,只是一个用户 ID,以便我知道他们是谁,但我希望对其进行编码或加密,这样任何人都无法欺骗其他用户

【问题讨论】:

魔法? ...任何必须破译的东西都可以并且将(最终)被其他人阅读破译。显然,您可以使用盐,但如果用户可以访问 cookie(而且,你知道,他们这样做),他们可以按照自己的意愿进行解密。 如果您只存储一个 ID,那么这基本上就是一个 PHP 会话 cookie。那么有什么问题呢? 【参考方案1】:

简短的回答

不要这样做。从长远来看,你会后悔的。当然,你可以加密它,但是当有人找出你的加密密钥时会发生什么。现在您只需将每个人的凭据放在盘子上交给他们(嗯,不是真的,但足够接近)。

更好的方法

为什么不创建一个随机令牌并将其与用户名一起存储,而不是加密存储用户名和密码?你想要一些相当大的东西,所以像 sha256 散列这样的东西就足够了。

$randomToken = hash('sha256',uniq_id(mt_rand(), true).uniq_id(mt_rand(), true));

然后,将其与用户一起存储在数据库中,并将 cookie 发送给客户端(我还建议对令牌进行签名以防止篡改:

$randomToken .= ':'.hash_hmac('md5', $randomToken, $serverKey);

现在,当您验证时,首先检查哈希是否匹配:

list($token, $hmac) = explode(':', $_COOKIE['remember_me'], 2);
if ($hmac != hash_hmac('md5', $token, $serverKey)) 
    die('tampered token!');

从那里,只需通过令牌查找用户。如果找到,请将该用户登录。

我还建议在每次更改密码时更改令牌。

直接回答您的问题

注意:请勿在实时生产代码中执行此操作。你永远不能完全信任离开你的网络服务器的数据。所以不要这样暴露你的用户信息。这不值得。但是,我确实添加了一些额外的检查(例如签署 cookie)以使其更安全一些,但您已被警告...

为了对其进行编码,我会使用mcrypt 将数据加密到cookie 中。然后,我会制作一个随机盐并将其存储在用户行中,然后使用该唯一盐使用hash_hmac 对加密数据进行签名。这样,如果有人截获了 cookie并且找出了 crypt 的密钥,你仍然可以检测到无效的 hmac,从而找到篡改者。

function generateCredentialsCookie($user_id, $password) 
    $encrypted = encrypt($user_id.':'.$password, $secretkey);
    $salt = uniq_id(mt_rand(), true);
    $encrypted .= ':'.hash_hmac('sha256', $encrypted, $salt);
    storeSaltForUser($user_id, $salt);
    set_cookie('credentials', $encrypted);


function readCredentialsCookie() 
    $parts = explode(':', $_COOKIE['credentials']);
    $salt = array_pop($parts);
    $encrypted = implode(':', $parts); //needed incase mcrypt added `:`
    $raw = decrypt($encrypted, $secretkey);
    list ($user_id, $password) = explode(':', $raw, 2);
    if ($salt == getSaltForUser($user_id)) 
        return array($user_id, $password);
     else 
        return die('Invalid Cookie Found');
    

注意 - 这是伪代码。你需要更多的东西来保证安全(例如检查无效值,确保它成功解密等)..

不要使用长时间运行的会话!

您应该尽可能缩短会话过期时间(我通常使用 30 分钟会话,但有些网站会更短)。过期时间是在最后一次使用之后,因此只要该站点正在被积极使用,就没有关系。

至于为什么不使用长时间运行的会话,这里有一些缺点:

DOS (Denial Of Service 漏洞被创建

磁盘空间 - 每个会话使用相当少量的磁盘空间。但是当你有一个长时间运行的会话时,每个新会话只会增加之前的总数。因此,对于长时间运行的会话,某人只需要使用新的会话 ID 一遍又一遍地访问您的网站,然后突然间您的磁盘空间不足(假设磁盘空间正常)。

文件夹空间 - 每个会话将一个文件放在一个文件夹中。大多数流行的文件系统会因为单个文件夹中的大量文件而变慢。所以如果你放 100 万个会话文件,读取或写入一个会话文件会很慢(非常慢)。垃圾收集(清理旧文件)会非常非常非常慢(如果它甚至可以运行的话)。

Session Hijacking 漏洞已打开。这是因为您在网站上打开的会话越多,猜出有效标识符就越容易(感谢the birthday attack)。您放置的会话越少,就越难猜出一个有效的会话。

可能还有其他人,但这是一个快速概述。如上所述,使用签名的记住我令牌,而不是长时间运行的会话。你会过得更好,更安全......

【讨论】:

好答案。 +1 但是,为什么不直接使用会话呢? 是的,很好的答案 +1 - 请注意,虽然我不是 那个 愚蠢的,并且永远不会在 cookie 中存储密码 :) 我喜欢存储哈希随机令牌的想法虽然并使用它,非常感谢! @Stephen:因为长时间运行的会话只是自找麻烦。您打开了 DOS(拒绝服务)漏洞(都是由于会话存储的大小和条目数)。更不用说由于生日攻击而导致的会话劫持漏洞(因为默认情况下会话 cookie 未签名)......保持会话简短,并经常轮换标识符。如果您想要长时间运行的身份验证,请使用记住我功能... 非常有用的答案!顺便说一句,$serverKey 是服务器的单个唯一密钥,还是每个用户都是唯一的,就像与用户一起存储的盐一样? 我也想知道这个。究竟在哪里可以获得唯一的服务器密钥?我最近才开始研究登录系统。【参考方案2】:

在 cookie 中存储用户数据是错误的方式。你应该使用PHP Sessions。唯一会存储在客户端上的是简单 cookie 中的会话 id(PHP 会自动为您处理)。已经是加密字符串了。

PHP 将在服务器端跟踪用户数据。

这将实现您所描述的预期效果,同时比使用 cookie 更安全。

【讨论】:

但是浏览器窗口关闭后会话不会过期吗?那么我必须完全依赖临时会话吗? 这不能回答问题! 不,可以设置过期时间。单击我的答案中的链接并很好地解析文档。那里有很多好东西。 @steven,我相信 crond-job 会清除 debian 上未触及的会话,对吗?设置生命周期无济于事。 您可以通过在配置中传递盐或随机数轻松配置 PHP 会话 ID 以具有更高的熵。如果您担心碰撞攻击,那么为什么要使用 SHA256 而不是 SHA512?这个恕我直言是最好的答案,但需要注意的是,您需要将会话长度显式设置为两天左右,以最大限度地减少服务器上的空间使用并且不会造成安全风险。【参考方案3】:

绝对在任何情况下都不应将用户凭据存储在 cookie 中。您应该始终假设所有用户输入的 cookie 都是不可信的。

几种方法:

如果用户选择“记住我”,那么只需在初始化会话之前进行以下设置。

session_set_cookie_params(60 * 60 * 24 * 7); //save cookie session for 7 days

否则,将唯一令牌存储在数据库中,并查询该令牌以查看它是否属于用户,如果是,则填充他们的会话。

我会推荐第一个选项。

【讨论】:

否 -1,但一般来说长时间运行的会话是个坏主意。有关更多信息,请参阅我在此页面上对我的回答的评论...【参考方案4】:

保存前加盐饼干。添加用户代理、ip 以扩展安全性。

$cookie_to_save = sha1($pwd . $user_agent . $yourSalt);

自动登录时

if($cookie_saved == sha1($pwd . $user_agent . $yourSalt) ok...

【讨论】:

为什么是-1?精心制作的!代码不完整,但从概念上来说相当安全和实用。 我不同意这个答案应该是-1,因为它从技术上回答了这个问题。 +1 来平衡它。另一方面,我认为它并不能解决整体问题。 我不是 -1,但要验证结果,您必须遍历数据库中的每个密码组合以验证 cookie。虽然它消除了潜在的信息泄露是件好事,但它对性能不太好(但您可以将它存储在数据库中,然后进行简单的查找。如果这就是您的意思,您可能需要稍微扩展它)。 ..【参考方案5】:

生成一个 16 或更长的加密安全随机数并将其存储在 cookie 中更简单。无需加密/解密。 使用在服务器端检查的有限有效时间(不要依赖 MaxAge 或 Expires)。这是一个会话 cookie。

这是非常安全的,因为没有关键信息离开服务器。

这种技术的缺点是您需要为该会话 ID 创建一个数据库索引并为每个请求进行数据库查找。这对于大多数网站来说可能已经足够好了,只要延迟是不可察觉的并且服务器负载可以接受。

为了最大程度地减少服务器负载和响应延迟,将用户 ID、他的角色和 cookie 创建标记存储在安全的 cookie 值中是一个有效的选项。清除无效请求不需要数据库或缓存命中,如果用户 ID 紧凑,则索引将更小且查找速度更快。但是除非您知道自己在做什么,否则不要发明自己的安全值编码。

这种方法的缺点是如果有人找到了密钥,他就可以伪造cookie。然后,他可以创建具有不同用户 ID 的 cookie 并访问他们的帐户。因此,在风险有限且对密钥的访问受到良好控制的情况下,请使用此方法。执行加密/解密并隐藏密钥的专用硬件设备可能是一种解决方案。但这目前并不常见且昂贵。它的延迟可能比在缓存中查找随机会话 ID 更大。后者是更简单、更安全的解决方案。

使用公钥/私钥来保护 cookie 中的值,可用于保护私钥。但要做到这一点要复杂得多,而且延迟可能与具有简单随机缓存会话 ID 的系统相当。

另请注意,任何使用 cookie 的方法都需要使用 HTTPS。否则,中间人或监视交换数据的人很容易获得 cookie 的副本并冒充用户。

【讨论】:

以上是关于对 cookie 进行编码,使其不会被欺骗或读取等的主要内容,如果未能解决你的问题,请参考以下文章

cookie欺骗

编码转码加密解密的方法

Cookie 不会持续存在

C++ 中的凯撒密码编码:字符不会循环回“a”或“A”

IDF-cookie欺骗

网站攻击的方式有哪几种