在PHP中使用正确的身份验证来保护登录

时间:2022-12-13 12:30:43

How do I write/put together a secure login in PHP? The website developer guide said I shouldn't roll my own, so referring to samples available via Google is useless.

如何在PHP中编写/组合安全登录?网站开发者指南说我不应该卷我自己的,所以参考通过谷歌提供的样本是没有用的。

How do you pros do it? Lets say you're building a world-class app in rails, would the same libraries / techniques be usable here?

你的职业选手是怎么做到的?假设您正在rails中构建一个世界级的应用程序,那么同样的库/技术在这里可以使用吗?

Thanks

谢谢

6 个解决方案

#1


20  

In Rails, one would generally use a pre-existing library. Authentication is easy to do wrong, and the problem's been solved so many times that it's rarely worth the effort to solve it again. If you are interested in writing your own implementation, then I'll describe how modern authentication works.

在Rails中,通常使用预先存在的库。身份验证很容易出错,而且问题已经解决了很多次,因此不值得再去解决它。如果您有兴趣编写自己的实现,那么我将描述现代身份验证是如何工作的。

The naive method of authenticating a user is to store their password in a database and compare it to the password the user submits. This is simple but unbelievably insecure. Anyone who can read your database can view anyone's password. Even if you put in database access controls, you (and your users) are vulnerable to anyone who hacks around them.

对用户进行身份验证的简单方法是将用户的密码存储在数据库中,并将其与用户提交的密码进行比较。这很简单,但不太安全。任何能阅读你的数据库的人都可以查看任何人的密码。即使您放置了数据库访问控件,您(以及您的用户)也很容易受到周围任何黑客的攻击。

Proper form is to use a cryptographic hash function to process the password when it is chosen and then every time it is submitted. A good hash function is practically irreversible -- you can't take a hash and turn it back into a password. So when the user logs in, you take the submitted password, hash it, and compare it to the hash in the database. This way, you never store the password itself. On the downside, if the user forgets their password, you have to reset it rather than send it to them.

正确的形式是使用加密哈希函数来处理密码,当它被选中,然后每次提交它。一个好的散列函数实际上是不可逆的——你不能拿一个散列把它变成密码。因此,当用户登录时,您将提交的密码进行哈希,并将其与数据库中的哈希进行比较。这样,您就不会存储密码本身。缺点是,如果用户忘记了密码,你必须重新设置密码,而不是发送给他们。

Even this, however, is vulnerable to certain attacks. If an attacker gets hold of your password hashes, and knows how you hash your passwords, then he can make a dictionary attack: he simply takes every word in the dictionary and hashes that word, keeping it with the original. This data structure is called a rainbow table. Then, if any of the dictionary word hashes match a password hash, the attacker can conclude that the password is the dictionary word that hashes to that password. In short, an attacker who can read your database can still log in to accounts with weak passwords.

然而,即便如此,也容易受到某些攻击。如果攻击者掌握了你的密码哈希,并且知道你如何哈希你的密码,那么他就可以进行字典攻击:他只需要把字典里的每个单词都进行哈希,并保持原来的单词。这种数据结构称为彩虹表。然后,如果字典中的任何单词哈希与密码哈希匹配,攻击者可以推断密码是哈希到密码的字典单词。简而言之,能够读取数据库的攻击者仍然可以登录密码较弱的帐户。

The solution is that before a password is hashed, it is combined (usually concatenated or xor'd) with a value called the salt which is unique to each user. It may be randomly generated, or it may be an account creation timestamp or some such. Then, an attacker cannot use a rainbow table because every password is essentially hashed slightly differently; he would have to create a separate rainbow table for every single distinct salt (practically for each account), which would be prohibitively computationally expensive.

解决方案是,在对密码进行散列处理之前,将密码(通常是连接或xor - d)与一个名为salt的值组合在一起,该值对每个用户都是惟一的。它可能是随机生成的,也可能是帐户创建时间戳或类似的东西。然后,攻击者不能使用彩虹表,因为每个密码本质上都有细微的不同;他必须为每一种不同的盐(实际上是每个账户)创建一个单独的彩虹表,这在计算上是非常昂贵的。

I will echo the advice of the other answerers: this is not simple stuff, and you don't need to do it because it's been done before, and if you do it yourself you stand a very good chance of making a mistake and inadvertently compromising your system's security. But if, for whatever reason, you really, really want to write one yourself, I hope that I have provided an (incomplete!) outline of how it's done.

我将重复其他回答者的建议:这不是简单的事情,您不需要这样做,因为以前已经做过了,如果您自己做了,您很可能会犯错误,不经意地损害系统的安全性。但如果,出于某种原因,你真的,真的想自己写一个,我希望我已经提供了一个(不完整的!)概述它是如何完成的。

#2


4  

The Zend Framework has an 'Auth' module which would be a good place to start. Or, if your site will be hosting an install of WordPress or PHPBB, there are ways of leveraging those technologies' authentication modules to sign in to other pages of a site.

Zend框架有一个“Auth”模块,这将是一个很好的起点。或者,如果您的站点将安装WordPress或PHPBB,那么可以利用这些技术的身份验证模块登录到站点的其他页面。

#3


2  

One thing to look at when you are trying to authenticate is what is your real goal.

当您尝试验证时,需要注意的一件事是您的真正目标是什么。

For example, on SO I use my google login, and that works, as they just need to know who I am, and they can trust that Google has an idea. So, if that model will work for you, then look at using OpenID, as there are various tools for that.

例如,我使用谷歌登录,这很有效,因为他们只需要知道我是谁,他们可以相信谷歌有一个想法。因此,如果这个模型适合您,那么请使用OpenID,因为有各种工具可以实现这一点。

If you must do your own, then there will be various tests to ensure that it is secure, again, depending on how paranoid you want to be.

如果您必须自己做,那么将会有各种各样的测试来确保它是安全的,同样,这取决于您希望变得多偏执。

  • Never trust anything from the user, unless you have used some strict verification.
  • 不要信任用户的任何东西,除非您使用了一些严格的验证。
  • Use https to help protect the password of the user, you owe them that much.
  • 使用https来保护用户的密码,你欠他们那么多。

I will end my response here as Thom did a fantastic response.

我将在这里结束我的回答,就像汤姆所做的那样。

#4


0  

by Soulmerge:

Soulmerge:

I think the accepted answer in your other question states it pretty well. Hash the passwords with a salt. Other than that, there are some security ideas on the transport layer:

我认为你其他问题的公认答案很好地说明了这一点。对密码进行散列处理。除此之外,在传输层还有一些安全方面的想法:

  • Use https when sending passwords. This makes sure nobody can catch them on the wire (man-in-the-middle attack or the client uses an evil proxy)
  • 发送密码时使用https。这确保没有人能在网络上捕捉到它们(中间人攻击或者客户使用恶意代理)
  • An alternative is to hash the password using javascript when the login form is submitted. This makes sure that the password is never transported in plaintext. You should hash the hashed value again with a salt on the server. (md5($_POST['postedPwHash'] . $salt))
  • 另一种方法是在提交登录表单时使用javascript对密码进行哈希。这确保密码不会以明文传输。您应该再次使用服务器上的salt对散列值进行哈希。(md5($ _POST[' postedPwHash ']。美元的盐)

#5


-1  

a good method to somewhat secure the client-server transaction (if no ssl is available) is to use a one-time random key to create a unique hash from the credentials, then only send that unique hash to the server. the server then compares this hash to its own generated hash instead of comparing it to the real credentials. this would provide a good defense against the man-in-the-middle attack. the downside is that to do this the user must have JS enabled (at least i dont know of a good method to encrypt client-side data without it). this means that you will need a sufficient fallback when it isn't on. you can even create the form in JS to make sure its enabled.

在某种程度上保护客户端-服务器事务(如果没有ssl可用)的一个好方法是使用一个一次性的随机键从凭据创建一个惟一的散列,然后只将惟一的散列发送到服务器。然后服务器将这个散列与自己生成的散列进行比较,而不是将其与真正的凭证进行比较。这将为对付中间人攻击提供良好的防御。这样做的缺点是,用户必须启用JS(至少我不知道有什么好的方法可以在没有JS的情况下对客户端数据进行加密)。这意味着当它没有打开时,你需要一个足够的退路。您甚至可以在JS中创建表单,以确保它是启用的。

this library is a simple library i wrote once that does the procedure i described, though it probably needs some improvements.

这个库是我曾经编写过的一个简单的库,它完成了我描述的过程,尽管它可能需要一些改进。

note that this is in addition to using "salting" methods and other server-side security measures. it is also quite vulnerable to dictionary attacks as the entire hashing process is by definition procedural, predictable and visible to the user (as JS always is).

请注意,这是除了使用“salting”方法和其他服务器端安全措施之外的。它也很容易受到字典攻击,因为根据定义,整个哈希过程是过程性的、可预测的,并且对用户可见(就像JS一样)。

#6


-2  

My answer is "Don't do it"

我的回答是"别这么做"

This is a very complex area, full of potential security gotcha's. If you are not an expert in this field, then you are really just asking for trouble and problems down the road.

这是一个非常复杂的领域,充满了潜在的安全陷阱。如果你不是这一领域的专家,那么你真的只是在自找麻烦和麻烦。

I would recommend looking at getting an existing solution to do. Sadly I don't know any that I would be happy to recommend, other than openid. I'm sure you will get some good suggestions here though...

我建议考虑使用现有的解决方案。遗憾的是,除了openid之外,我不知道有什么可以推荐的。我相信你会得到一些好的建议……

#1


20  

In Rails, one would generally use a pre-existing library. Authentication is easy to do wrong, and the problem's been solved so many times that it's rarely worth the effort to solve it again. If you are interested in writing your own implementation, then I'll describe how modern authentication works.

在Rails中,通常使用预先存在的库。身份验证很容易出错,而且问题已经解决了很多次,因此不值得再去解决它。如果您有兴趣编写自己的实现,那么我将描述现代身份验证是如何工作的。

The naive method of authenticating a user is to store their password in a database and compare it to the password the user submits. This is simple but unbelievably insecure. Anyone who can read your database can view anyone's password. Even if you put in database access controls, you (and your users) are vulnerable to anyone who hacks around them.

对用户进行身份验证的简单方法是将用户的密码存储在数据库中,并将其与用户提交的密码进行比较。这很简单,但不太安全。任何能阅读你的数据库的人都可以查看任何人的密码。即使您放置了数据库访问控件,您(以及您的用户)也很容易受到周围任何黑客的攻击。

Proper form is to use a cryptographic hash function to process the password when it is chosen and then every time it is submitted. A good hash function is practically irreversible -- you can't take a hash and turn it back into a password. So when the user logs in, you take the submitted password, hash it, and compare it to the hash in the database. This way, you never store the password itself. On the downside, if the user forgets their password, you have to reset it rather than send it to them.

正确的形式是使用加密哈希函数来处理密码,当它被选中,然后每次提交它。一个好的散列函数实际上是不可逆的——你不能拿一个散列把它变成密码。因此,当用户登录时,您将提交的密码进行哈希,并将其与数据库中的哈希进行比较。这样,您就不会存储密码本身。缺点是,如果用户忘记了密码,你必须重新设置密码,而不是发送给他们。

Even this, however, is vulnerable to certain attacks. If an attacker gets hold of your password hashes, and knows how you hash your passwords, then he can make a dictionary attack: he simply takes every word in the dictionary and hashes that word, keeping it with the original. This data structure is called a rainbow table. Then, if any of the dictionary word hashes match a password hash, the attacker can conclude that the password is the dictionary word that hashes to that password. In short, an attacker who can read your database can still log in to accounts with weak passwords.

然而,即便如此,也容易受到某些攻击。如果攻击者掌握了你的密码哈希,并且知道你如何哈希你的密码,那么他就可以进行字典攻击:他只需要把字典里的每个单词都进行哈希,并保持原来的单词。这种数据结构称为彩虹表。然后,如果字典中的任何单词哈希与密码哈希匹配,攻击者可以推断密码是哈希到密码的字典单词。简而言之,能够读取数据库的攻击者仍然可以登录密码较弱的帐户。

The solution is that before a password is hashed, it is combined (usually concatenated or xor'd) with a value called the salt which is unique to each user. It may be randomly generated, or it may be an account creation timestamp or some such. Then, an attacker cannot use a rainbow table because every password is essentially hashed slightly differently; he would have to create a separate rainbow table for every single distinct salt (practically for each account), which would be prohibitively computationally expensive.

解决方案是,在对密码进行散列处理之前,将密码(通常是连接或xor - d)与一个名为salt的值组合在一起,该值对每个用户都是惟一的。它可能是随机生成的,也可能是帐户创建时间戳或类似的东西。然后,攻击者不能使用彩虹表,因为每个密码本质上都有细微的不同;他必须为每一种不同的盐(实际上是每个账户)创建一个单独的彩虹表,这在计算上是非常昂贵的。

I will echo the advice of the other answerers: this is not simple stuff, and you don't need to do it because it's been done before, and if you do it yourself you stand a very good chance of making a mistake and inadvertently compromising your system's security. But if, for whatever reason, you really, really want to write one yourself, I hope that I have provided an (incomplete!) outline of how it's done.

我将重复其他回答者的建议:这不是简单的事情,您不需要这样做,因为以前已经做过了,如果您自己做了,您很可能会犯错误,不经意地损害系统的安全性。但如果,出于某种原因,你真的,真的想自己写一个,我希望我已经提供了一个(不完整的!)概述它是如何完成的。

#2


4  

The Zend Framework has an 'Auth' module which would be a good place to start. Or, if your site will be hosting an install of WordPress or PHPBB, there are ways of leveraging those technologies' authentication modules to sign in to other pages of a site.

Zend框架有一个“Auth”模块,这将是一个很好的起点。或者,如果您的站点将安装WordPress或PHPBB,那么可以利用这些技术的身份验证模块登录到站点的其他页面。

#3


2  

One thing to look at when you are trying to authenticate is what is your real goal.

当您尝试验证时,需要注意的一件事是您的真正目标是什么。

For example, on SO I use my google login, and that works, as they just need to know who I am, and they can trust that Google has an idea. So, if that model will work for you, then look at using OpenID, as there are various tools for that.

例如,我使用谷歌登录,这很有效,因为他们只需要知道我是谁,他们可以相信谷歌有一个想法。因此,如果这个模型适合您,那么请使用OpenID,因为有各种工具可以实现这一点。

If you must do your own, then there will be various tests to ensure that it is secure, again, depending on how paranoid you want to be.

如果您必须自己做,那么将会有各种各样的测试来确保它是安全的,同样,这取决于您希望变得多偏执。

  • Never trust anything from the user, unless you have used some strict verification.
  • 不要信任用户的任何东西,除非您使用了一些严格的验证。
  • Use https to help protect the password of the user, you owe them that much.
  • 使用https来保护用户的密码,你欠他们那么多。

I will end my response here as Thom did a fantastic response.

我将在这里结束我的回答,就像汤姆所做的那样。

#4


0  

by Soulmerge:

Soulmerge:

I think the accepted answer in your other question states it pretty well. Hash the passwords with a salt. Other than that, there are some security ideas on the transport layer:

我认为你其他问题的公认答案很好地说明了这一点。对密码进行散列处理。除此之外,在传输层还有一些安全方面的想法:

  • Use https when sending passwords. This makes sure nobody can catch them on the wire (man-in-the-middle attack or the client uses an evil proxy)
  • 发送密码时使用https。这确保没有人能在网络上捕捉到它们(中间人攻击或者客户使用恶意代理)
  • An alternative is to hash the password using javascript when the login form is submitted. This makes sure that the password is never transported in plaintext. You should hash the hashed value again with a salt on the server. (md5($_POST['postedPwHash'] . $salt))
  • 另一种方法是在提交登录表单时使用javascript对密码进行哈希。这确保密码不会以明文传输。您应该再次使用服务器上的salt对散列值进行哈希。(md5($ _POST[' postedPwHash ']。美元的盐)

#5


-1  

a good method to somewhat secure the client-server transaction (if no ssl is available) is to use a one-time random key to create a unique hash from the credentials, then only send that unique hash to the server. the server then compares this hash to its own generated hash instead of comparing it to the real credentials. this would provide a good defense against the man-in-the-middle attack. the downside is that to do this the user must have JS enabled (at least i dont know of a good method to encrypt client-side data without it). this means that you will need a sufficient fallback when it isn't on. you can even create the form in JS to make sure its enabled.

在某种程度上保护客户端-服务器事务(如果没有ssl可用)的一个好方法是使用一个一次性的随机键从凭据创建一个惟一的散列,然后只将惟一的散列发送到服务器。然后服务器将这个散列与自己生成的散列进行比较,而不是将其与真正的凭证进行比较。这将为对付中间人攻击提供良好的防御。这样做的缺点是,用户必须启用JS(至少我不知道有什么好的方法可以在没有JS的情况下对客户端数据进行加密)。这意味着当它没有打开时,你需要一个足够的退路。您甚至可以在JS中创建表单,以确保它是启用的。

this library is a simple library i wrote once that does the procedure i described, though it probably needs some improvements.

这个库是我曾经编写过的一个简单的库,它完成了我描述的过程,尽管它可能需要一些改进。

note that this is in addition to using "salting" methods and other server-side security measures. it is also quite vulnerable to dictionary attacks as the entire hashing process is by definition procedural, predictable and visible to the user (as JS always is).

请注意,这是除了使用“salting”方法和其他服务器端安全措施之外的。它也很容易受到字典攻击,因为根据定义,整个哈希过程是过程性的、可预测的,并且对用户可见(就像JS一样)。

#6


-2  

My answer is "Don't do it"

我的回答是"别这么做"

This is a very complex area, full of potential security gotcha's. If you are not an expert in this field, then you are really just asking for trouble and problems down the road.

这是一个非常复杂的领域,充满了潜在的安全陷阱。如果你不是这一领域的专家,那么你真的只是在自找麻烦和麻烦。

I would recommend looking at getting an existing solution to do. Sadly I don't know any that I would be happy to recommend, other than openid. I'm sure you will get some good suggestions here though...

我建议考虑使用现有的解决方案。遗憾的是,除了openid之外,我不知道有什么可以推荐的。我相信你会得到一些好的建议……