# 前言
最近对密码的加密比较感兴趣, 但是对于比较全面的文章, 只找到了这一章, 英文版, 我稍微翻译了一下, 这里记录一下
原文:[Salted Password Hashing - Doing it Right](https://crackstation.net/hashing-security.htm)
# 正文
## 序言[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%BA%8F%E8%A8%80)
如果你是一个web开发人员,你可能不得不建立一个用户帐户系统。用户帐户系统最重要的方面是如何保护用户密码。用户帐户数据库经常被黑客入侵,所以如果你的网站被入侵,你绝对必须采取措施保护你用户的密码。保护密码的最好方法是使用salt密码散列(**salted password hashing**)。本文将解释为什么它是这样做的。
关于如何正确地进行密码散列有很多相互矛盾的想法和误解,可能是因为网络上有大量的错误信息。密码散列其实是很简单的事情之一,但是很多人都会犯错。通过这一篇文章,我希望不仅解释正确的方法,而且解释为什么应该这样做。
> 重要警告: 如果您正在考虑编写自己的密码哈希代码,**请立即停止**, 这太容易搞砸了。不,你在大学上的密码学课程并不能使你免除这个警告。这适用于每一个人,**不要编写自己的 CRYPTO**(**DO NOT WRITE YOUR OWN CRYPTO!**), 存储密码的问题已经得到解决, 许多语言都有现成的包和模块供你使用, 他将比你自己实现的更加完美和稳定
需要说明的是,本篇文章并不是要指导你如何编写自己的存储系统,而是要解释为什么密码应该以某种方式存储。
## 什么是密码散列[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E4%BB%80%E4%B9%88%E6%98%AF%E5%AF%86%E7%A0%81%E6%95%A3%E5%88%97)
~~~go
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542
~~~
哈希算法是单向函数。他们把任何数量的数据转换成一个固定长度的“指纹”,不能逆转。它们还有一个特性,即如果输入发生一点点的变化,那么产生的散列就完全不同了(参见上面的示例)。这对于保护密码非常有用,因为我们希望以一种即使密码文件本身被泄露也能保护密码的形式存储密码,但同时,我们需要能够验证用户的密码是否正确。
在基于哈希的帐户系统中,帐户注册和身份验证的一般工作流程如下:
1. 用户创建一个帐户
2. 他们的密码被散列并存储在数据库中。任何时候都不会有写入硬盘的纯文本(未加密)密码
3. 当用户尝试登录时,将根据其真实密码(从数据库中检索)的哈希值与他们输入的密码的哈希值进行比对
4. 如果散列匹配,用户将被授予访问权限。如果不匹配,用户将被告知输入了无效的登录凭证
5. 当用户每次登录时, 重复步骤3和步骤4
在第4步中,千万不要告诉用户是不是用户名或密码错了。始终显示“无效用户名或密码”这样的通用消息。这可防止攻击者在不知道其密码的情况下枚举有效用户名。
应该注意的是,用于保护密码的哈希函数与您在数据结构课程中看到的哈希函数不同。用于实现哈希表等数据结构的哈希函数被设计为快速的,而不是安全的。只能使用加密哈希函数来实现密码哈希。像SHA256、SHA512、RipeMD和WHIRLPOOL这样的散列函数都是加密散列函数。
很容易认为你所要做的就是通过一个加密散列函数来运行密码,你的用户的密码将是安全的。这与事实相去甚远。有很多方法可以很快地从普通散列中恢复密码。有几种易于实现的技术使这些“攻击”的效果大大降低。为了激发对这些技术的需求,考虑一下这个网站。在首页上,您可以提交一个要破解的哈希列表,并在不到一秒钟的时间内收到结果。显然,简单地对密码进行哈希运算并不能满足我们对安全性的需求。
## 如何破解哈希[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%A6%82%E4%BD%95%E7%A0%B4%E8%A7%A3%E5%93%88%E5%B8%8C)
### 字典和暴力攻击[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%AD%97%E5%85%B8%E5%92%8C%E6%9A%B4%E5%8A%9B%E6%94%BB%E5%87%BB)
破解散列的最简单方法是尝试猜测密码,对每个猜测进行散列,并检查猜测的散列是否等于被破解的散列。如果哈希值相等,则猜测到了密码。猜测密码最常见的两种方法是字典攻击和暴力攻击。
字典攻击使用包含单词、短语、常用密码和其他可能用作密码的字符串的文件。对文件中的每个单词进行哈希运算,并将其哈希值与密码哈希值进行比较。如果他们匹配,这个词就是密码。这些字典文件是通过从大量文本中提取单词,甚至从真实的密码数据库中提取单词来构建的。进一步的处理通常应用于字典文件,例如用“leet speak”等价词替换单词(“hello”变成“h3110”),以使它们更有效。
暴力攻击会尝试所有可能的字符组合,长度不超过给定的长度。这些攻击在计算上非常昂贵,而且通常在每个处理器时间破解哈希方面效率最低,但它们最终总会找到密码。密码应该足够长,搜索所有可能的字符串以找到它将花费太长的时间是值得的。
我们无法防止字典攻击或暴力攻击。它们可以变得不那么有效,但是没有一种方法可以完全阻止它们。如果密码散列系统是安全的,破解散列的唯一方法就是对每个散列运行字典或暴力攻击, 但是对于破解者来说要花费很大的代价和很长的时候才能破解其中一个密码.
### 查找表[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%9F%A5%E6%89%BE%E8%A1%A8)
查找表是一种非常有效的方法,可以快速地破解许多相同类型的哈希。其基本思想是在密码字典中预先计算密码的哈希值,并将其和相应的密码存储在查找表数据结构(数据库)中。使用者提交需要破解的哈希, 由数据库去查找有没有对应的密码, 一个好的查找表实现可以每秒处理数百个哈希查找,即使它们包含数十亿个哈希。
如果您想更好地了解查找表的速度,请尝试使用CrackStation的[免费哈希破解程序](https://crackstation.net/)破解以下sha256哈希(需要FQ)
~~~go
c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc
11083b4b0a7743af748c85d343dfe9fbb8b2576c05f3a7f0d632b0926aadfc
08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7
e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904
4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904
5206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd
~~~
### 反向查找表[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%8F%8D%E5%90%91%E6%9F%A5%E6%89%BE%E8%A1%A8)
此攻击允许攻击者对多个哈希同时应用字典或暴力攻击,而无需预先计算查找表。
首先,攻击者创建一个查找表,将每个密码哈希值从受损用户帐户数据库映射到拥有该哈希值的用户列表。然后,攻击者对每个密码猜测进行哈希运算,并使用查找表获取其密码是攻击者猜测的用户列表。这种攻击特别有效,因为许多用户都有相同的密码。
> 这段比较晦涩, 我使用大白话来解释一下, 其实是攻击者注册一个账号到被攻击网站, 然后进入该网站的密码数据库, 查找到刚才注册的帐号的加密后的密码, 再通过数据库查找有没有和该加密密码一致的加密密码, 一致则代表某个用户使用了和你相同的密码, 即可达到破解的效果
### 彩虹表[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%BD%A9%E8%99%B9%E8%A1%A8)
彩虹表是一种时间记忆折衷技术。它们类似于查寻表,只是它们牺牲了哈希破解的速度,以使查寻表更小。因为它们更小,所以可以将更多散列的解决方案存储在相同的空间中,从而使它们更有效。彩虹表,可以快速破解任何8个字符生成的md5。
[彩虹表](https://freerainbowtables.com/)
## 加盐(salt)[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%8A%A0%E7%9B%90salt)
~~~go
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007
~~~
查找表和彩虹表起作用,因为每个密码的散列方式完全相同。如果两个用户有相同的密码,他们将有相同的密码哈希。我们可以通过随机化每个散列来防止这些攻击,这样当相同的密码被散列两次时,散列就不一样了。
我们可以通过在散列之前在密码中添加或预先添加一个随机字符串(称为**salt**)来随机散列。如上面的示例所示,每次都会将相同的密码散列成完全不同的字符串。为了检查密码是否正确,我们需要这个密码加密时的salt,因此它通常与散列一起存储在用户帐户数据库中,或者作为散列字符串本身的一部分。
Salt不需要加密。因为salt是随机的, 随机化了哈希值,查找表、反向查找表和彩虹表就变得无效了。攻击者不会预先知道salt是什么,因此无法预先计算查找表或彩虹表。如果每个用户的密码都用不同的salt散列,那么反向查找表攻击也不会起作用。
## 加盐的错误使用[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%8A%A0%E7%9B%90%E7%9A%84%E9%94%99%E8%AF%AF%E4%BD%BF%E7%94%A8)
### 重复使用一个盐[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E9%87%8D%E5%A4%8D%E4%BD%BF%E7%94%A8%E4%B8%80%E4%B8%AA%E7%9B%90)
一个常见的错误是在每个散列中使用相同的盐。salt要么硬编码到程序中,要么随机生成一次。这是无效的,因为如果两个用户有相同的密码,他们仍然会有相同的哈希。攻击者仍然可以使用反向查找表攻击同时对每个哈希运行字典攻击。他们只需对每个密码猜测应用salt,然后再对其进行哈希运算。如果将salt硬编码到一个流行的产品中,那么可以为该salt构建查找表和彩虹表,以便更容易地破解产品生成的哈希。
> 每次用户创建帐户或更改密码时,都必须生成一个新的随机salt。
### 短盐[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E7%9F%AD%E7%9B%90)
如果盐太短,攻击者可以为每个可能的盐建立一个查找表。例如,如果salt只有三个ASCII字符,则只有95x95x95=857375个可能的salt。这看起来可能很多,但是如果每个查找表只包含1MB最常用的密码,那么它们总共只有837GB,考虑到现在1000 GB的硬盘售价低于100美元,这并不算多。
出于同样的原因,用户名不应该用作salt。用户名对于单个服务可能是唯一的,但是它们是可预测的,并且经常被其他服务上的帐户重用。攻击者可以为常见用户名构建查找表,并使用它们来破解用户名作为salt的哈希。
> 为了使攻击者无法为每个可能的salt创建一个查找表,salt必须很长。一个好的经验法则是使用与哈希函数输出大小相同的salt。例如,SHA256的输出是256位(32字节),因此salt应该至少是32个随机字节。
## 散列算法的错误使用[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%95%A3%E5%88%97%E7%AE%97%E6%B3%95%E7%9A%84%E9%94%99%E8%AF%AF%E4%BD%BF%E7%94%A8)
本节介绍另一个常见的密码散列误解:散列算法的古怪组合。人们很容易忘乎所以地尝试组合不同的散列函数,希望结果更安全。但实际上,这样做并没有什么好处。它只会造成互操作性问题,有时甚至会降低哈希的安全性。永远不要试图发明你自己的密码,总是使用一个由专家设计的标准。有些人会认为使用多个散列函数会使计算散列的过程变慢,所以破解会变慢,但是有一个更好的方法可以让破解过程变慢,我们稍后会看到。
下面是一些我在互联网论坛上看到的糟糕的古怪哈希函数的例子
* md5(sha1(password))
* md5(md5(salt) + md5(password))
* sha1(sha1(password))
* sha1(str\_rot13(password + salt))
* md5(sha1(md5(md5(password) + sha1(password)) + md5(password)))
不要使用这些。
注:本节已被证明是有争议的。我收到过很多邮件,认为古怪的散列函数是件好事,因为如果攻击者不知道使用的是哪一个散列函数就更好了,攻击者为古怪的散列函数预先计算彩虹表的可能性更小,计算散列函数的时间也更长。
攻击者在不知道算法的情况下无法攻击哈希,但请注意[Kerckhoffs](https://en.wikipedia.org/wiki/Kerckhoffs%27s_principle)的原理,即攻击者通常可以访问源代码(尤其是免费或开源软件),并且给定目标系统中的一些密码哈希对,对算法进行反向工程并不困难。计算古怪的散列函数确实需要更长的时间,但只需要一个小的常数因子。最好使用一个设计得非常难以并行化的迭代算法(这些将在下面讨论)。而且,适当地对散列进行盐分可以解决彩虹表问题。
如果你真的想使用一个标准化的“古怪”散列函数,比如HMAC,那就没关系了。但是,如果这样做是为了降低哈希计算的速度,请先阅读下面关于密钥扩展(**key stretching**)的部分。
将这些小的好处与意外实现完全不安全的散列函数的风险以及古怪的散列所产生的互操作性问题进行比较。显然,最好使用标准的、经过良好测试的算法。
## 散列碰撞[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%95%A3%E5%88%97%E7%A2%B0%E6%92%9E)
因为散列函数将任意数量的数据映射到固定长度的字符串,所以必须有一些输入散列到相同的字符串中。加密散列函数的设计使这些冲突难以找到。有时,密码学家会发现对散列函数的“攻击”,从而更容易找到冲突。最近的一个例子是MD5散列函数,实际上已经找到了它的冲突。
> 就是有可能字符串 abc 和 bbc 生成的散列是相同的
冲突攻击是一种迹象,表明除用户密码以外的字符串更有可能具有相同的哈希值。然而,即使是在MD5这样的弱哈希函数中发现冲突,也需要大量专用的计算能力,因此在实践中,这些冲突“偶然”发生的可能性非常小。使用MD5和salt散列的密码在所有实际用途中都是安全的,就像使用SHA256和salt散列一样。不过,如果可能的话,最好使用更安全的散列函数,如SHA256、SHA512、RipeMD或WHIRLPOOL。
## 如何正确的散列[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E6%95%A3%E5%88%97)
本节详细描述了密码应该如何散列。第一小节涵盖了基本的一切,这是绝对必要的。下面的小节将解释如何扩充基础知识,使哈希更难破解。
### 用盐做散列[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E7%94%A8%E7%9B%90%E5%81%9A%E6%95%A3%E5%88%97)
警告:不要只阅读本节。您绝对必须实现下一节中的内容:“使密码破解更困难:慢哈希函数”
我们已经看到恶意黑客如何使用查找表和彩虹表快速破解普通哈希。我们已经了解到使用salt随机散列是问题的解决方案。但是如何生成salt,以及如何将其应用于密码?
Salt应该使用加密安全的伪随机数生成器(**CSPRNG**)生成。csprng与普通的伪随机数生成器非常不同,比如“C”语言的rand()函数。顾名思义,csprng被设计成密码安全的,这意味着它们提供了高度的随机性,并且完全不可预测。我们不希望盐是可预测的,所以我们必须使用CSPRNG。每个语言都有对应的函数供你使用, 例如 python 的`os.urandom`等
每个用户每个密码的salt必须是唯一的。每次用户创建帐户或更改密码时,都应该使用新的随机salt对密码进行哈希运算。不要重复使用盐。盐也应该足够长, 以保证有足够多的盐用于哈希加密。根据经验,使salt至少与哈希函数的输出一样长。salt应该与hash一起存储在用户账户表中。
### 保存密码[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E4%BF%9D%E5%AD%98%E5%AF%86%E7%A0%81)
1. 使用 CSPRNG 生成足够长的随机盐
2. 将salt预先混入密码中,并使用标准密码哈希函数(如Argon2、bcrypt、scrypt或PBKDF2)对其进行哈希运算。
3. 在用户的数据库记录中保存salt和hash。
### 校验密码[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%A0%A1%E9%AA%8C%E5%AF%86%E7%A0%81)
1. 从数据库中检索用户的salt和hash。
2. 将salt混入用户提交的密码,并使用相同的哈希函数对其进行哈希运算。
3. 将给定密码的哈希值与数据库中的哈希值进行比较。如果匹配,则密码正确。否则,密码不正确。
### 在 Web 应用程序中,始终在服务器上进行散列[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%9C%A8-web-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%B8%AD%E5%A7%8B%E7%BB%88%E5%9C%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E8%BF%9B%E8%A1%8C%E6%95%A3%E5%88%97)
如果您正在编写一个web应用程序,您可能想知道在哪里散列。密码应该在用户浏览器中用JavaScript散列,还是应该发送将明文密码发送到服务器再进行散列呢?
即使用JavaScript散列用户密码,也必须在服务器上散列。考虑一个网站,它在用户浏览器中散列用户密码,而不在服务器上散列。若要验证用户身份,此网站将接受来自浏览器的哈希值,并检查该哈希值是否与数据库中的哈希值完全匹配。这似乎比在服务器上散列更安全,因为用户的密码从未发送到服务器,但事实并非如此。
问题是客户端的哈希值逻辑上变成了用户的密码。用户需要做的只是告诉服务器他们密码的散列。如果一个坏人得到了一个用户的散列,他们可以用它来验证服务器,而不知道用户的密码!**因此,如果坏人不知何故从这个假想的网站窃取了哈希数据库,他们就可以立即访问每个人的帐户,而不必猜测任何密码**。
这并不是说你不应该在浏览器中散列,但如果你这样做了,你绝对也必须在服务器上散列。在浏览器中进行散列当然是一个好主意,但在实现时请考虑以下几点:
* 客户端密码哈希不能替代HTTPS(SSL/TLS)。如果浏览器和服务器之间的连接不安全,中间人可以在下载JavaScript代码时修改它,以删除散列功能并获取用户的密码。
* 有些web浏览器不支持JavaScript,有些用户在浏览器中禁用JavaScript。因此,为了获得最大的兼容性,你的应用程序应该检测浏览器是否支持JavaScript,如果不支持,就在服务器上模拟客户端散列。
* 你也需要在客户端散列的过程中中加盐。显而易见的解决方案是让客户端脚本向服务器请求用户的salt。不要这样做,因为它让攻击者在不知道密码的情况下检查用户名是否有效。由于您也在服务器上进行散列和加盐(使用一个合格的salt),因此可以使用用户名(或电子邮件)与特定于站点的字符串(例如域名)连接作为客户端salt(这个客户端salt并不是最终的salt, 客户端生成的加密密码也不是最终的密码, 只是为了将明文密码加密而防止中间人攻击, 并且,当黑客拿到了客户端加密后的密码, 他也能通过请求登录接口模拟客户端已经加密密码然后登录的情况, 此时则与客户端不加密的效果一致)。
### 使得密码破解更难: 缓慢的散列函数[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E4%BD%BF%E5%BE%97%E5%AF%86%E7%A0%81%E7%A0%B4%E8%A7%A3%E6%9B%B4%E9%9A%BE-%E7%BC%93%E6%85%A2%E7%9A%84%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B0)
Salt确保攻击者不能使用专门的攻击,如查找表和彩虹表来快速破解大量散列集合,但它不能阻止攻击者单独对每个散列运行字典或暴力攻击。高端图形卡(GPU)和定制硬件每秒可以计算数十亿个哈希,因此这些攻击仍然非常有效。为了降低这些攻击的效果,我们可以使用一种称为密钥扩展(**key stretching**)的技术。
其想法是使哈希函数非常慢,因此即使使用快速的GPU或定制硬件,字典和暴力攻击也很慢,从而让攻击者放弃或者失败。目标是使散列函数足够慢以阻止攻击,但仍然足够快以避免给用户造成明显的延迟。
密钥扩展是使用一种特殊类型的CPU密集型哈希函数实现的。不要试图创造你自己的加密函数, 简单地对密码的散列进行迭代散列是不够的,因为它可以在硬件中并行化,并且可以像普通散列一样快速执行。使用标准算法,如[PBKDF2](https://en.wikipedia.org/wiki/PBKDF2)或[bcrypt](https://en.wikipedia.org/wiki/Bcrypt)。
这些算法以安全因子或迭代计数作为参数。此值确定哈希函数的速度。对于桌面软件或智能手机应用程序,选择此参数的最佳方法是在设备上运行一个简短的基准测试,以找到计算哈希值的耗时约半秒的参数。这样,您的程序就可以在不影响用户体验的情况下尽可能安全。
如果您在web应用程序中使用密钥扩展哈希,请注意您将需要额外的计算资源来处理大量身份验证请求,并且密钥扩展可能会使您的网站更容易受到拒绝服务(DoS)攻击。我仍然建议使用密钥扩展,但是迭代次数要少一些。您应该根据计算资源和预期的最大身份验证请求速率来设置迭代次数。通过让用户在每次登录时输入验证码,可以消除DoS威胁。系统在设计时要将迭代次数设置为可配置的,以便将来可以增加或减少迭代次数。
如果您担心计算负担,但仍希望在web应用程序中使用密钥扩展,请考虑使用JavaScript在用户浏览器中运行密钥扩展算法。[Stanford Javascript Crypto Library](http://bitwiseshiftleft.github.io/sjcl/)加密库包括PBKDF2。迭代次数应该设置得足够低,这样系统就可以在移动设备等速度较慢的客户端上使用,如果用户的浏览器不支持JavaScript,那么系统应该返回到服务器端来模拟客户端计算。客户端密钥扩展并不能消除服务器端散列的需要。你必须对客户端传回来的哈希值再次进行哈希加密, 也就是说客户端生成的加密密码并不是最终的加密密码, 以此来增加安全性, 因为前端的代码是透明的, 不能将完整的算法放置在前端
### 不可能破解的哈希: 密钥哈希和密码哈希设备[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E4%B8%8D%E5%8F%AF%E8%83%BD%E7%A0%B4%E8%A7%A3%E7%9A%84%E5%93%88%E5%B8%8C-%E5%AF%86%E9%92%A5%E5%93%88%E5%B8%8C%E5%92%8C%E5%AF%86%E7%A0%81%E5%93%88%E5%B8%8C%E8%AE%BE%E5%A4%87)
只要攻击者可以使用哈希来检查密码猜测是否正确,他们就可以对哈希运行字典或暴力攻击。下一步是将密钥添加到哈希中,这样只有知道密钥的人才能使用哈希来验证密码。这可以通过两种方式实现。可以使用类似AES的算法对散列进行加密,或者使用类似[HMAC](https://en.wikipedia.org/wiki/HMAC)的密钥散列算法将密钥包括在散列中。
这并不像听起来那么容易。密钥必须对攻击者保密,即使在出现漏洞的情况下也是如此。如果攻击者获得对系统的完全访问权限,则无论密钥存储在何处,他们都可以窃取密钥。密钥必须存储在外部系统中,例如专门用于密码验证的物理上独立的服务器,或者连接到服务器的特殊硬件设备,例如[YubiHSM](https://www.yubico.com/YubiHSM)。
对于任何大规模(超过10万个用户)的服务,我强烈推荐这种方法。我认为有必要为任何托管超过100万个用户帐户使用该方法。
如果您负担不起多个专用服务器或特殊硬件设备,您仍然可以在标准web服务器上获得密钥哈希的一些好处。大多数数据库都是通过SQL注入攻击被破坏的,在大多数情况下,SQL注入攻击不允许攻击者访问本地文件系统(如果SQL server具有此功能,则禁用对本地文件系统的访问)。如果生成一个随机密钥并将其存储在无法从web访问的文件中,并将其包含到salt哈希中,那么如果使用简单的SQL注入攻击破坏数据库,哈希就不会受到攻击。不要将密钥硬编码到源代码中,而是在安装应用程序时随机生成密钥。这不如使用单独的系统进行密码的管理安全,因为如果web应用程序中存在SQL注入漏洞,攻击者可能会使用其他类型(如本地文件包含)来读取密钥文件。但是,总比什么都没有强。
请注意,密钥哈希并不能消除对盐的需要。聪明的攻击者最终会找到破解密钥的方法,因此密码哈希依然需要加盐和密钥扩展, 这是很重要的。
### 其他安全措施[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%85%B6%E4%BB%96%E5%AE%89%E5%85%A8%E6%8E%AA%E6%96%BD)
密码哈希在发生安全漏洞时保护密码。它并不能使整个应用程序更加安全。为了防止密码散列(和其他用户数据)在第一时间被窃取,还必须做更多的工作。
即使是有经验的开发人员也必须接受安全教育,才能编写安全的应用程序。了解web应用程序漏洞的一个重要资源是openweb应用程序安全项目[OWASP](https://owasp.org/)。一个很好的介绍是[OWASP十大漏洞列表](https://owasp.org/www-project-proactive-controls/)。除非您了解列表中的所有漏洞,否则不要尝试编写处理敏感数据的web应用程序。雇主有责任确保所有开发人员都接受过安全应用程序开发方面的充分培训。
对您的应用程序进行第三方"渗透测试"是一个好主意。即使是最好的程序员也会犯错,因此让安全专家检查代码中的潜在漏洞总是有意义的。找一个值得信赖的组织(或雇佣员工)定期检查您的代码。安全审查过程应该在应用程序生命周期的早期开始,并在整个开发过程中持续进行。
同样重要的是要监测你的网站,以发现入侵行为。我建议至少雇佣一个全职工作是检测和处理安全漏洞的人。如果一个漏洞未被发现,攻击者可以通过你的网站利用恶意软件感染访问者,所以检测漏洞并及时作出反应是非常重要的。
## 常见问题[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
### 我应该使用什么散列算法?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%88%91%E5%BA%94%E8%AF%A5%E4%BD%BF%E7%94%A8%E4%BB%80%E4%B9%88%E6%95%A3%E5%88%97%E7%AE%97%E6%B3%95)
**使用**
* 精心设计并经过大量测试的密钥扩展算法比如[PBKDF2](https://en.wikipedia.org/wiki/PBKDF2),[Bcrypt](https://en.wikipedia.org/wiki/Bcrypt),[Scrypt](http://www.tarsnap.com/scrypt.html)
* 很好的开源项目比如[Portable PHP password hashing framework](https://www.openwall.com/phpass/)
* PBKDF2在各个语言的实现
* [crypt](https://en.wikipedia.org/wiki/Crypt_%28Unix%29#Library_Function_crypt.283.29)的安全版本
**不要使用**
* 快速密码散列函数,如 MD5、 SHA1、 SHA256、 SHA512、 RipeMD、 WHIRLPOOL、 SHA3等
* 不安全版本的 crypt
* 任何你自己设计的算法。只能使用公共领域的技术,并且经过经验丰富的密码学家的良好测试。
### 当用户忘记密码时,我应该如何允许他们重置密码?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%BD%93%E7%94%A8%E6%88%B7%E5%BF%98%E8%AE%B0%E5%AF%86%E7%A0%81%E6%97%B6%E6%88%91%E5%BA%94%E8%AF%A5%E5%A6%82%E4%BD%95%E5%85%81%E8%AE%B8%E4%BB%96%E4%BB%AC%E9%87%8D%E7%BD%AE%E5%AF%86%E7%A0%81)
我个人认为,现在广泛使用的所有密码重置机制都是不安全的。如果你有很高的安全要求,比如加密服务,不要让用户重置密码。
大多数网站使用向忘记密码的用户发送邮件来验证身份。为此,生成一个与帐户紧密关联的随机一次性令牌(Token)。将其包含在发送到用户电子邮件地址的密码重置链接中。当用户单击包含有效令牌的密码重置链接时,提示他们输入新密码。确保令牌与用户帐户紧密绑定,以便攻击者不能使用发送到自己电子邮件地址的令牌重置其他用户的密码。
令牌必须设置为在15分钟内或使用后过期,一旦使用过就立即作废。当用户登录(他们想起来了他们的密码)或请求一个新的重置密码时,使该用户任何现有的密码令牌过期。如果一个令牌没有过期,它可以永远被用来入侵用户的帐户。电子邮件(SMTP)是一种纯文本协议,互联网上存在截取电子邮件的恶意路由。用户的电子邮件帐户(包括重置链接)可能会在其修改密码后很长一段时间内被泄露出去。而使令牌尽快过期可以减少用户将信息暴露给攻击者的风险。
攻击者将能够修改令牌,因此不要在其中存储用户帐户信息或失效时间。它们应该是不可猜测的随机二进制数据,仅用于标识数据库表中的某条用户记录。
千万不要通过电子邮件向用户发送新密码。当用户重置密码时,请记住选择一个新的随机盐。不要重复使用他们的旧盐。
### 如果我的用户帐户资料库被泄漏/入侵,我应该怎么办?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%A6%82%E6%9E%9C%E6%88%91%E7%9A%84%E7%94%A8%E6%88%B7%E5%B8%90%E6%88%B7%E8%B5%84%E6%96%99%E5%BA%93%E8%A2%AB%E6%B3%84%E6%BC%8F%E5%85%A5%E4%BE%B5%E6%88%91%E5%BA%94%E8%AF%A5%E6%80%8E%E4%B9%88%E5%8A%9E)
您的首要任务是确定系统是如何受到危害的,并修补攻击者用来进入的漏洞。如果你没有应对漏洞的经验,我强烈建议你聘请第三方安全公司。
可能会有人试图掩盖漏洞,希望没人注意到。然而,试图掩盖漏洞会让你变得更糟,因为你没有通知用户密码和其他个人信息可能会被泄露,这会让你的用户面临更大的风险。你必须尽快通知你的用户,即使你还没有完全明白发生了什么。在你的网站首页放一个通知,链接到一个有更详细信息的页面,如果可能的话,通过电子邮件向每个用户发送一个通知。
向你的用户解释他们的密码是如何被盐哈希保护的,即使他们被盐哈希保护,恶意黑客仍然可以对哈希运行字典和暴力攻击。恶意黑客会使用他们找到的任何密码试图登录到另一个网站上的用户帐户,希望他们在两个网站上使用相同的密码。将此风险告知您的用户,并建议他们在使用类似密码的任何网站或服务上更改密码。强制他们在下次登录时更改您服务的密码。大多数用户会尝试将自己的密码“更改”为原始密码,以快速绕过强制更改。你需要使用当前密码散列来校验新旧密码是否相同来确保他们不能这样做。
很可能,即使使用盐哈希,攻击者也能很快破解一些弱密码。为了减少攻击者使用这些密码的机会,在登录时除了当前密码之外,还应该要求通过电子邮件进行身份验证,直到用户更改了密码。请参阅前面的问题,“当用户忘记密码时,我应该如何允许用户重置密码?”有关实现电子邮件验证身份的提示。
同时告诉你的用户网站上储存了什么样的个人信息。如果你的数据库包含信用卡号码,你应该指示你的用户仔细检查他们最近和将来的账单,并注销他们的信用卡。
### 我的密码政策应该是什么? 我应该强制使用强密码吗?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%88%91%E7%9A%84%E5%AF%86%E7%A0%81%E6%94%BF%E7%AD%96%E5%BA%94%E8%AF%A5%E6%98%AF%E4%BB%80%E4%B9%88-%E6%88%91%E5%BA%94%E8%AF%A5%E5%BC%BA%E5%88%B6%E4%BD%BF%E7%94%A8%E5%BC%BA%E5%AF%86%E7%A0%81%E5%90%97)
如果您的服务没有严格的安全要求,那么不要限制您的用户。我建议在用户键入密码时向其显示有关密码强度的信息,让他们决定密码的安全性。如果您有特殊的安全需求,请强制执行至少12个字符的长度,并且至少需要两个字母、两个数字和两个符号。
不要强迫用户每六个月更改一次以上的密码,因为这样做会造成“用户疲劳”,使用户不选择复杂的密码。相反,教育用户在感到密码被泄露时主动更改密码,并且永远不要把密码告诉任何人。如果是商业环境,鼓励员工利用带薪时间熟记和使用密码。
### 如果攻击者可以访问我的数据库,难道他们不能用自己的散列和登录来替换密码的散列吗?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E5%A6%82%E6%9E%9C%E6%94%BB%E5%87%BB%E8%80%85%E5%8F%AF%E4%BB%A5%E8%AE%BF%E9%97%AE%E6%88%91%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93%E9%9A%BE%E9%81%93%E4%BB%96%E4%BB%AC%E4%B8%8D%E8%83%BD%E7%94%A8%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%A3%E5%88%97%E5%92%8C%E7%99%BB%E5%BD%95%E6%9D%A5%E6%9B%BF%E6%8D%A2%E5%AF%86%E7%A0%81%E7%9A%84%E6%95%A3%E5%88%97%E5%90%97)
是的,但是如果有人可以访问你的数据库,他们可能已经可以访问你服务器上的所有内容,所以他们不需要登录到你的帐户就可以得到他们想要的。密码散列(在网站中)的目的不是保护网站不被破坏,而是在确实发生入侵时保护数据库中的密码。
通过使用两个具有不同权限的用户连接到数据库,可以防止哈希在SQL注入攻击期间被替换。一个用于“创建帐户”代码,另一个用于“登录”代码。“create account”代码操作的用户应该能够读取和写入用户表,但是“login”代码使用的用户应该只能读取。
### 为什么我必须使用像 HMAC 这样的特殊算法?为什么我不能把密码附加到密钥上呢?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E5%BF%85%E9%A1%BB%E4%BD%BF%E7%94%A8%E5%83%8F-hmac-%E8%BF%99%E6%A0%B7%E7%9A%84%E7%89%B9%E6%AE%8A%E7%AE%97%E6%B3%95%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E4%B8%8D%E8%83%BD%E6%8A%8A%E5%AF%86%E7%A0%81%E9%99%84%E5%8A%A0%E5%88%B0%E5%AF%86%E9%92%A5%E4%B8%8A%E5%91%A2)
像 MD5、 SHA1和 SHA2这样的散列函数使用[Merkle-Damgård](https://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_construction)结构,这使它们容易受到所谓的长度扩展攻击(length extension attack)。这意味着给定一个 哈希 h (x) ,对于任意的字符串 Y,攻击者可以计算出 h(pad(X)+Y) 的值,而无需知道 X 的值。其中, pad(X) 是哈希函数的填充函数。
这意味着,攻击者不知道密钥的情况下,仍然可以根据给定的哈希值 H(key+message) 计算出 H(pad(key+message)+extension) 。如果该哈希值用于身份认证,并依靠其中的密钥来防止攻击者篡改消息,这方法已经行不通。因为攻击者无需知道密钥也能构造出包含 message+extension 的一个有效的哈希值。
目前尚不清楚攻击者如何利用此攻击更快地破解密码哈希。但是,由于受到攻击,使用普通哈希函数进行密钥哈希加密被认为是不好的做法。一个聪明的密码学家也许有一天会想出一个聪明的方法来使用这些攻击来加快破解速度,所以还是使用HMAC 较好。
### 盐应该放在密码的前面还是后面?[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E7%9B%90%E5%BA%94%E8%AF%A5%E6%94%BE%E5%9C%A8%E5%AF%86%E7%A0%81%E7%9A%84%E5%89%8D%E9%9D%A2%E8%BF%98%E6%98%AF%E5%90%8E%E9%9D%A2)
这不重要,但是为了互操作性,选择一个并保持风格一致即可。把盐放在密码之前似乎更为常见。
### 需要保证对比密码时的操作时间相同[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E9%9C%80%E8%A6%81%E4%BF%9D%E8%AF%81%E5%AF%B9%E6%AF%94%E5%AF%86%E7%A0%81%E6%97%B6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%97%B6%E9%97%B4%E7%9B%B8%E5%90%8C)
> 原文这里很晦涩, 理解了一会才明白是什么意思
>
> 标题的意思是, 假设用户的密码哈希是 'abcde', 当服务端接收到了用户提交的密码, 哈希后假如是 'adcca' 或者是 '121333' 或者是其他的, 要保证判断他们与数据库中的是否一致的代码在处理所有密码时都要有一致的处理时间
比较哈希值的函数遵循 "时间一致" 可确保攻击者无法在使用计时攻击的在线系统中提取密码的哈希值,然后离线破解密码。
检查两个字节(字符串)是否相同的标准方法是比较第一个字节、第二个字节、第三个字节,依此类推。一旦发现两个字符串的字节不一样,就知道它们是不同的,可以立即返回false。如果您在两个字符串中都没有找到任何不同的字节,那么您就知道字符串是相同的,并且可以返回一个true。这意味着比较两个字符串可能需要不同的时间,具体取决于匹配的字符串数量。
例如,对字符串“xyzabc”和“abcxyz”进行标准比较,可以立即看到第一个字符是不同的,不必检查字符串的其余部分。另一方面,当比较字符串“aaaaaaaab”和“aaaaaaaaaaz”时,比较算法在扫描到 z 时才会确认这两个字符串是不一样的, 这代表他已经检查了前面几位, 显而易见的, 一般的, 比较算法在处理这两个判断时用时是不一样的, 后面的会比前面的处理慢一些。
假设攻击者想要侵入一个在线系统,该系统将身份验证尝试的速率限制为每秒一次。另外,假设攻击者知道密码散列的所有参数(salt、散列类型等),但密码和散列后的密码除外。如果攻击者能够精确测量在线系统将真实密码的散列值与攻击者提供的密码的散列值进行比较所需的时间,那么他可以使用定时攻击提取部分散列值,并使用脱机攻击进行破解,从而绕过系统的速率限制。
首先,攻击者找到256个字符串,其哈希值以每个可能的字节开始。他将每个字符串发送到在线系统,记录系统响应所需的时间。耗时最长的字符串将是哈希的第一个字节与实际哈希的第一个字节匹配的字符串。攻击者现在知道第一个字节,可以以类似的方式继续攻击第二个字节,然后攻击第三个字节,依此类推。一旦攻击者对散列有足够的了解,他就可以使用自己的硬件来破解它,而不受系统的速率限制。
> 这里指的是: 我不知道你的密码, 但是密码的每一位肯定是在256个字符串中间的某一个, 假如代码不遵循 '操作时间相同' 原则, 那我试这256次, 耗时最长的那一个肯定是第一位是对的, 然后我试第二个字符, 一直到我试出来密码或者我根据试出来的猜测出密码
在网络上运行定时攻击似乎是不可能的, 因为网络本身具有延迟。然而,[有人](https://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf)已经做到了,并且被证明是切实可行的。这就是为什么要遵循 '操作时间相同' 原则
### 怎么编写遵循 '操作时间相同' 的代码[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E6%80%8E%E4%B9%88%E7%BC%96%E5%86%99%E9%81%B5%E5%BE%AA-%E6%93%8D%E4%BD%9C%E6%97%B6%E9%97%B4%E7%9B%B8%E5%90%8C-%E7%9A%84%E4%BB%A3%E7%A0%81)
前面的问题解释了为什么操作时间相同是必要的,这里解释了代码实际上是如何工作的。
~~~go
private static boolean slowEquals(byte[] a, byte[] b)
{
int diff = a.length ^ b.length;
for(int i = 0; i < a.length && i < b.length; i++)
diff |= a[i] ^ b[i];
return diff == 0;
}
~~~
代码使用 XOR“ ^”运算符来比较整数是否相等,而不是使用“ = =”运算符。原因如下。当且仅当两个整数完全相同时,XORing 的结果才为0。这是因为0 XOR 0 = 0,1 XOR 1 = 0,0 XOR 1 = 1,1 XOR 0 = 1。如果我们对两个整数中的所有位都应用这个函数,那么只有当所有位都匹配时,结果才是0。
所以, 如上面代码所示, 先判断这两个哈希值的长度, 如果其不一致, diff为 1, 但是并不直接退出, 而是在进行 for 循环, 结束后统一返回, 也就是说, 就算在代码中能够提前判断不正确也不立刻返回而是同样遍历一遍达到耗时相同的效果
我们需要使用XOR而不是“= =”运算符来比较整数的原因是“= =”通常被翻译/编译/解释为一个branch。例如,C代码“”可能编译为以下x86程序集:diff &= a == b
~~~go
MOV EAX, [A]
CMP [B], EAX
JZ equal
JMP done
equal:
AND [VALID], 1
done:
AND [VALID], 0
~~~
分支使代码根据整数的相等性和CPU的内部分支预测状态以不同的时间量执行。
C代码应编译为以下内容,其执行时间不依赖于整数的相等性:diff |= a ^ b
~~~go
MOV EAX, [A]
XOR EAX, [B]
OR [DIFF], EAX
~~~
### 为什么要为你的网站设置密码加密[#](https://www.cnblogs.com/chnmig/p/14475648.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%B8%BA%E4%BD%A0%E7%9A%84%E7%BD%91%E7%AB%99%E8%AE%BE%E7%BD%AE%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86)
您的用户正在您的网站中输入密码。证明他们相信你的安全。
如果你的数据库遭到黑客攻击,而你的用户密码没有受到保护,那么恶意黑客就可以利用这些密码危害你在其他网站和服务上的用户帐户(大多数人在任何地方都使用相同的密码)。
风险不仅仅在于你的安全,还在于你的用户。
**你要对你的用户的安全负责**
- php开发
- 常用技巧
- 字符数组对象
- php换行替换,PHP替换回车换行符的三种方法
- PHP 数组转字符串,与字符串转数组
- php将img中的宽高删除,PHP删除HTML中宽高样式的详解
- php去除换行(回车换行)的三种方法
- php 过滤word 样式
- php如何设置随机数
- 2个比较经典的PHP加密解密函数分享
- php怎么去除小数点后多余的0
- php中判断是一维数组还是二维数组的解决方案
- php 获取数组中出现次数最多的值(重复最多的值)与出现的次数
- PHP过滤掉换行符、特殊空格、制表符等
- PHP中json_endoce转义反斜杠的问题
- PHP过滤Emoji表情和特殊符号的方法
- PHP完美的提取链接正则
- php很牛的图片采集
- 日期处理
- php 获取今日、昨日、上周、本月的起始时间戳和结束时间戳的方法非常简单
- PHP指定时间戳/日期加一天,一年,一周,一月
- 使用php 获取时间今天明天昨天时间戳的详解
- php获得当月的节假日函数(包含周末,年度节假日)
- PHP获取本月起始和截止时间戳
- php 获取每月开始结束时间,php 获取指定月份的开始结束时间戳
- PHP获取今天,昨天,本月,上个月,本年 起始时间戳
- php、mysql查询当天,本周,本月的用法
- php获取两个时间戳之间相隔多少天多少小时多少分多少秒
- 毫秒级时间戳和日期格式转换
- php-倒计时
- 请求提交上传
- php+put+post,Curl和PHP-如何通过PUT,POST,GET通过curl传递json
- PHP put提交和获取数据
- PHP curl put方式上传文件
- 数据导入导出
- PHP快速导入大量数据到数据库的方法
- PHP快速导出百万级数据到CSV或者EXCEL文件
- PHP解析大型Excel表格的库:box/spout
- PHP导入(百万级)Excel表格数据
- PHP如何切割excel大文件
- 使用 PHP_XLSXWriter 代替 PHPExcel 10W+ 数据秒级导出
- 安装php扩展XLSXWriter
- 解决php导入excel表格时获取日期变成浮点数的方法
- xml处理
- PHP XML和数组互相转换
- php解析xml字符串
- php 生成vcf通讯录
- 文件操作相关
- php获取文件后缀的9种方法
- PHP判断远程文件是否存在
- PHP获取文件修改时间,访问时间,inode修改时间
- php获取远程文件大小教程
- php 读取文件并以文件方式下载
- php 把数字转化为大写中文
- 请求响应
- PHP 获取当前访问的URL
- 压缩
- php生成zip压缩包
- PHPMailer
- 整理PHPMailer 发送邮件 邮件内容为html 可以添加多个附件等
- 通达oa
- OA管理员密码忘了怎么办,这里教你分分钟搞定…
- 跨域
- php解决多站点跨域
- php设置samesite cookie,有效防止CSRF
- Chrome 配置samesite=none方式
- Cookie 的 SameSite 属性
- 图片
- php pdf首页截图,PHP_PHP中使用Imagick读取pdf并生成png缩略图实例,pdf生成png首页缩略图
- PHP -- 七牛云 在线视频 获取某一帧作为封面图
- PHP图片压缩方法
- 如何解决PHP curl或file_get_contents下载图片损坏或无法打开的问题
- php远程下载文章中图片并保存源文件名不变
- 详解PHP如何下载采集图片到本地(附代码实例)
- php如何将webp格式图片转为jpeg
- PHP获取远程图片的宽高和体积大小
- php 软件版本号比较
- 使用PHP通过SMTP发送电邮
- 常用正则表达式
- php如何用正则表达式匹配中文
- 用于分割字符串的 PHP preg_match_all 正则表达式
- 性能优化
- php.ini配置调优
- PHP 几种常见超时的设置方法
- PHP函数in_array、array_key_exists和isset效率分析
- php array push 和array_merge 效率谁高,php 通过array_merge()和array+array合并数组的区别和效率比较...
- php 两个数组取交集、并集、差集
- 设置PHP最大连接数及php-fpm 高并发 参数调整
- 小工具
- php 获取代码执行时间和消耗的内存
- PHP如何判断某项扩展是否开启
- centos7.x下php 导出扩展 XLSXWriter 安装
- php生成mysql数据库字典
- PHP 实现 word/excel/ppt 转换为 PDF
- composer的使用
- showdoc sqlite3 找回管理员密码
- php怎么将数组转为xml
- PHP抖音最新视频提取代码
- yii
- Yii2 如何获取Header参数?
- swoole
- Linux下搭建swoole服务的基本步骤
- 相关学习资料
- 带你学习swoole_process详解
- 按照官方文档 在win10下安装 docker for windows easyswoole镜像 挂载目录
- php常用框架
- Hyperf
- 常用算法PHP版
- thinkphp6
- TP6 事件绑定、监听、订阅
- Thinkphp 模板中输出HTML的变量
- Thinkphp6(操作SQL数据库)
- thinkphp6 mysql查询语句对于为null和为空字符串给出特定值处理
- Thinkphp 6 - 连接配置多个数据库并实现自由切换(详细过程及实例demo)
- TP框架中的Db::name 和 dB::table 以及 db('') 的区别
- thinkphp6.0模型篇之模型的软删除
- thinkphp6自定义日志驱动,增加显示全部请求信息
- 其他系统
- 微擎数据库字段字典
- Flutter实现微信支付和iOS IAP支付
- Flutter上线项目实战——苹果内购
- PHP接入苹果支付
- 调试
- php如何获取当前脚本所有加载的文件
- php跟踪所有调用方法,日志方法
- 解析phpstorm + xdebug 远程断点调试
- PHP XDEBUG调试 PHPSTORM配置
- 异常处理
- PHP 出现 502 解决方案
- php 语法解析错误 syntax error unexpected namespace T_NAMESPACE
- Composer 安装与使用
- 数据库相关
- php pdo怎么设置utf8
- php 如何根据最新聊天对用户进行排序
- php lic&fpm
- 让php程序在linux后台执行
- PHPcli模式和fpm模式优缺点在哪里?
- 运行模式
- php运行模式之cli模式
- 自己库
- php批量获取所有公众号粉丝openid
- 地图
- php 判断点在多边形内,php百度地图api判断地址是否在多边形区域内
- PHP,Mysql-根据一个给定经纬度的点,进行附近地点查询
- MySQL 根据经纬度查找排序
- PHP+MySQL获取坐标范围内的数据
- 【百度地图】删除指定覆盖物
- 百度地图多点+画连接线+数字标注
- laravel5.8
- laravel5.8(四)引入自定义常量文件及公共函数文件
- Lumen 查询执行SQL
- 使你的 Laravel 项目模块化
- Laravel 多条件 AND , OR条件组合查询
- Laravel 查询 多个or或者and条件
- laravel redis操作大全
- laravel中外部定义whereIn的用法和where中使用in
- lumen5.8
- 创建laravel5.8 lumen前后台api项目--记录请求和响应日志
- Laravel和Lumen开启SQL日志记录
- Laravel 5.8 常用操作(路径+日志+分页+其他操作)
- 升级php7.4 laravel lumen报错Trying to access array offset on value of type null
- Laravel 任务调度(计划任务,定时任务)
- laravel的command定时任务时间的设置
- Laravel任务调度的简单使用
- laravel单数据库执行事务和多数据库执行事务
- laravel中锁以及事务的简单使用
- 申请其他相关
- 小程序地理位置接口申请
- PHP高并发
- php 高并发下 秒杀处理思路
- 记录 PHP高并发 商品秒杀 问题的 Redis解决方案
- thinkphp3.2
- thinkphp3.2 数据库 AND OR连缀使用
- laravel
- laravel的联表查询with方法的使用
- laravel获取请求路由对应的控制器和方法
- Laravel 模型关联建立与查询
- Laravel多表(3张表以上)with[]关联查询,对关联的模型做条件查询(has,跟join一样结果 )
- Laravel模型属性的隐藏属性、显示属性和临时暴露隐藏属性用法介绍
- aravel获取当前的url以及当前的基础域名方法汇总
- Laravel 模型实现多库查询或者多表映射
- 关于 Laravel 的 with 多表查询问题
- Laravel 模型过滤(Filter)设计
- 懒加载、预加载、with()、load() 傻傻分不清楚?
- laravel模型$castsl属性
- Laravel Query Builder 复杂查询案例:子查询实现分区查询 partition by
- Laravel 模型关联、关联查询、预加载使用实例
- laravel 中with关联查询限定查询字段
- laravel 原生字段查询 whereRaw 和 where(DB::raw(''))
- lavarel - where条件分组查询(orWhere)
- 通过 Laravel 查询构建器实现复杂的查询语句
- 两个结果集合并成一个
- Laravel 对某一列进行筛选然后求和 sum()
- laravel怎么优雅的拼接where,处理whereIn与where数组查询的问题
- laravel查询时判断是否存在数据
- laravel中的whereNull和whereNotNull
- laravel框架中的子查询
- Laravel框架中 orwhere 多条件查询的使用
- Laravel中where的高级使用方法
- laravel复杂的数据库查询(事例)
- laravel多条件查询方法(and,or嵌套查询)
- Laravel 的 where or 查询
- Laravel 进行where 多个or和and的条件查询可用
- laravel Middleware 中间件 $next($request) 报错不执行问题
- 数据库
- mysql
- mysql联合索引(复合索引)详解
- MYSQL 清空表和截断表
- MySQL快速生成大量测试数据(100万、1000万、1亿)
- 提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意)
- MySQL常用命令
- MySQL(三)|《千万级大数据查询优化》第一篇:创建高性能的索引
- MySQL(一)|性能分析方法、SQL性能优化和MySQL内部配置优化
- MySQL(二)|深入理解MySQL的四种隔离级别及加锁实现原理
- MySQL(四)|《千万级大数据查询优化》第一篇:创建高性能的索引(补充)
- MySQL(五)|《千万级大数据查询优化》第二篇:查询性能优化(1)
- MySQL(六)|《千万级大数据查询优化》第二篇:查询性能优化(2)
- MySQL(七)|MySQL分库分表的那点事
- Mysql索引优化 Mysql通过索引提升查询效率(第二棒)
- MySQL查询的性能优化(查询缓存、排序跟索引)
- 【总结】MySQL数据库
- MySQL存储引擎、事务日志并发访问以及隔离级别
- 技巧
- 数据库 SQL查询重复记录 方法
- 替换数据库中某个字段中的部分字符
- mysql开启bin log 并查看bin log日志(linux)
- 分表分区
- 千万级别数据的mysql数据表优化
- MYSQL百万级数据,如何优化
- MySQL备份和恢复
- MySQL间隙锁死锁问题
- 小技巧
- 基础
- MySQL中sql_mode参数
- mysql数据库异常
- this is incompatible with sql_mode=only_full_group_by
- mysql安全
- MySQL数据库被比特币勒索及安全调整
- MongoDB
- sql查询
- MYSQL按时间段分组查询当天,每小时,15分钟数据分组
- 高级
- 基于 MySQL + Tablestore 分层存储架构的大规模订单系统实践-架构篇
- 数据库安全
- 服务器被黑,MySQL 数据库遭比特币勒索!该如何恢复?
- 数千台MySQL数据库遭黑客比特币勒索,该怎么破?
- MySQL 数据库规范
- MySQL数据库开发的36条铁律
- Elasticsearch
- 安装与配置
- ElasticSearch关闭重启命令
- 设置ES默认分词器IK analyzer
- 查询
- elasticsearch 模糊查询不分词,实现 mysql like
- elasticSearch多条件高级检索语句,包含多个must、must_not、should嵌套示例,并考虑nested对象的特殊检索
- elasticSearch按字段普通检索,结果高亮
- Elasticsearch 如何实现查询/聚合不区分大小写?
- 索引更新&刷新
- refresh与批量操作的效率
- Elasticsearch 删除type
- 分词器
- ElasticSearch最全分词器比较及使用方法
- 异常错误
- 解决ES因内存不足而无法查询的错误,Data too large, data for [<http_request>]
- linux
- 基本知识
- CentOS7.5 通过wget下载文件到指定目录
- 【CentOS】vi命令
- centos7查看硬盘使用情况
- CentOS7 查看目录大小
- Centos 7下查看当前目录大小及文件个数
- 普通用户sudo\su 到root免密码
- 普通用户切换到root用户下的免密配置方法
- linux 获取进程启动参数,linux查看进程启动及运行时间
- Linux 查看进程
- linux删除文件后不释放磁盘的问题
- Linux查找大文件命令
- linux 如何关闭正在执行的php脚本
- linux三剑客(grep、sed、awk)基本使用
- centos 卸载软件
- centos查看内存、cpu占用、占用前10,前X
- Centos 查看系统状态
- 异常
- 问题解决:Failed to download metadata for repo ‘appstream‘: Cannot prepare internal mirrorlist:...
- php相关
- centos 安装phpize
- Centos7.2下phpize安装php扩展
- 切换版本
- 运营工具
- 资深Linux运维工程师常用的10款软件/工具介绍
- 一款良心的终端连接工具
- 六款Linux常用远程连接工具介绍,看看哪一款最适合你
- Finalshell
- Linux Finalshell连接centos7和文件无显示问题
- WSL2:我在原生的Win10玩转Linux系统
- MobaXterm
- 运维
- linux服务器上定时自动备份数据库,并保留最新5天的数据
- Centos系统开启及关闭端口
- CentOS7开放和关闭端口命令
- Linux中查看所有正在运行的进程
- 防火墙firewall-cmd命令详解
- centos 7.8阿里云服务器挂载 数据盘
- Linux Finalshell连接centos7和文件无显示问题
- Centos7系统端口被占用问题的解决方法
- vi
- 如何在Vim/Vi中复制,剪切和粘贴
- 命令
- [Linux kill进程] kill 进程pid的使用详解
- 备份还原
- Linux的几种备份、恢复系统方式
- Linux系统全盘备份方法
- 相关软件安装
- linux下 lua安装
- python
- 升级pip之后出现sys.stderr.write(f“ERROR: {exc}“)
- lua
- centos源码部署lua-5.3
- deepin
- deepin20.6设置默认的root密码
- 任务相关
- 宝塔定时任务按秒执行
- CentOS 7 定时任务 crontab 入门
- centos7定时任务crontab
- Linux下定时任务的查看及取消
- Linux(CentOS7)定时执行任务Crond详细说明
- Linux 查看所有定时任务
- linux查看所有用户定时任务
- Linux 定时任务(超详细)
- 防火墙
- Centos7开启防火墙及特定端口
- CentOS防火墙操作:开启端口、开启、关闭、配置
- 生成 SSH 密钥(windows+liunx)
- 阿里云,挂载云盘
- 前端
- layui
- layui多文件上传
- layer.msg()弹框,弹框后继续运行
- radio取值
- layui-数据表格排序
- Layui select选择框添加搜索选项功能
- 保持原来样式
- layui表格单元如何自动换行
- layui-laydate时间日历控件使用方法详解
- layui定时刷新数据表格
- layer 延时设置
- layer.open 回调函数
- 【Layui内置方法】layer.msg延时关闭msg对话框(代码案例)
- layui多图上传图片顺序错乱及重复上传解决
- layer.confirm关闭弹窗
- vue
- Vue跨域解决方法
- vue 4.xx.xx版本降级至2.9.6
- vue-cli 2.x升级到3.x版本, 和3.x降级到2.x版本命令
- 最新版Vue或者指定版本
- Vue2.6.11按需模块安装配置
- jQuery
- jQuery在页面加载时动态修改图片尺寸的方法
- jquery操作select(取值,设置选中)
- 日历
- FullCalendar中文文档:Event日程事件
- js
- JS 之 重定向
- javascript截取video视频第一帧作为封面方案
- HTML <video> preload 属性
- jQuery使用ajax提交post数据
- JS截取视频靓丽的帧作为封面
- H5案例分享:移动端touch事件判断滑屏手势的方向
- JS快速获取图片宽高的方法
- win
- Windows环境下curl的使用
- Cygwin
- Windows下安装Cygwin及apt-cyg
- Cygwin 安装、CMake 安装
- mklink命令 详细使用
- Nginx
- Nginx高级篇-性能优化
- Nginx常用命令(Linux)
- linux+docker+nginx如何配置环境并配置域名访问
- Nginx的启动(start),停止(stop)命令
- linux 查看nginx 安装路径
- 安装配置
- Linux 查看 nginx 安装目录和配置文件路径
- 【NGINX入门】3.Nginx的缓存服务器proxy_cache配置
- thinkphp6.0 伪静态失效404(win下)
- 深入
- nginx rewrite及多upstream
- Nginx负载均衡(upstream)
- 专业术语
- 耦合?依赖?耦合和依赖的关系?耦合就是依赖
- PHP常用六大设计模式
- 高可用
- 分布式与集群
- Nginx 实践案例:反向代理单台web;反向代理多组web并实现负载均衡
- 容器
- Docker
- 30 分钟快速入门 Docker 教程
- linux查看正在运行的容器,说说Docker 容器常用命令
- Windows 安装Docker至D盘
- 配置
- win10 快速搭建 lnmp+swoole 环境 ,部署laravel6 与 swoole框架laravel-s项目1
- win10 快速搭建 lnmp+swoole 环境 ,部署laravel6 与 swoole框架laravel-s项目2
- docker 容器重命名
- Linux docker常用命令
- 使用
- docker 搭建php 开发环境 添加扩展redis、swoole、xdebug
- docker 单机部署redis集群
- Docker 退出容器不停止容器运行 并重新进入正在运行的容器
- 进入退出docker容器
- Docker的容器设置随Docker的启动而启动
- 使用异常处理
- docker容器中bash: vi: command not found
- OCI runtime exec failed: exec failed:解决方法
- docker启动容器慢,很慢,特别慢的坑
- 解决windows docker开发thinkphp6启动慢的问题
- 【Windows Docker】docker挂载解决IO速度慢的问题
- Docker的网络配置,导致Docker使用网路很慢的问题及解决办法
- golang工程部署到docker容器
- Docker 容器设置自启动
- 如何优雅地删除Docker镜像和容器(超详细)
- 5 个好用的 Docker 图形化管理工具
- Docker 可能会用到的命令
- Kubernetes
- 消息队列
- RabbitMQ
- php7.3安装使用rabbitMq
- Windows环境PHP如何使用RabbitMQ
- RabbitMQ学习笔记:4369、5672、15672、25672默认端口号修改
- Window10 系统 RabbitMQ的安装和简单使用
- RabbitMQ默认端口
- RabbitMQ可视化界面登录不了解决方案
- RocketMQ
- Kafka
- ActiveMQ
- mqtt
- phpMQTT详解以及处理使用过程中内存耗死问题
- MQTT--物联网(IoT)消息推送协议
- php实现mqtt发布/发送 消息到主题
- Mqtt.js 的WSS链接
- emqx
- 如何在 PHP 项目中使用 MQTT
- emqx 修改dashboard 密码
- 其他
- Windows 系统中单机最大TCP的连接数详解
- EMQX
- Linux系统EMQX设置开机自启
- Centos7 EMQX部署
- docker安装 EMQX 免费版 docker安装并配置持久化到服务器
- 实时数仓
- 网易云音乐基于 Flink + Kafka 的实时数仓建设实践
- 实时数仓-基于Flink1.11的SQL构建实时数仓探索实践
- 安全
- 网站如何保护用户的密码
- 关于web项目sessionID欺骗的问题
- php的sessionid可以伪造,不要用来做防刷新处理了
- DVWA-Weak Session IDs (弱会话)漏洞利用方式
- 保证接口数据安全的10种方案
- cookie和session的窃取
- 万能密码漏洞
- 黑客如何快速查找网站后台地址方法整理
- 网站后台万能密码/10大常用弱口令
- 万能密码漏洞02
- 大多数网站后台管理的几个常见的安全问题注意防范
- token可以被窃取吗_盗取token
- token被劫持[token被劫持如何保证接口安全性]
- PHP给后台管理系统加安全防护机制的一些方案
- php - 重新生成 session ID
- 隐藏响应中的server和X-Powered-By
- PHP会话控制之如何正确设置session_name
- Session攻击001
- PHP防SQL注入代码,PHP 预防CSRF、XSS、SQL注入攻击
- php25个安全实践
- php架构师 系统管理员必须知道的PHP安全实践
- 版本控制
- Linux服务器关联Git,通过执行更新脚本实现代码同步
- PHP通过exec执行git pull
- git 在linux部署并从windows上提交代码到linux
- git上传到linux服务器,git一键部署代码到远程服务器(linux)
- linux更新git命令,使用Linux定时脚本更新服务器的git代码
- git異常
- 如何解决remote: The project you were looking for could not be found
- git status显示大量文件修改的原因是什么
- PHPstorm批量修改文件换行符CRLF为LF
- git使用
- git常用命令大全
- centos git保存账户密码
- GIT 常用命令
- git怎样还原修改
- Git 如何放弃所有本地修改的方法
- Git忽略文件模式改变
- git: 放弃所有本地修改
- Git三种方法从远程仓库拉取指定的某一个分支
- 杂七杂八
- h5视频
- H5浏览器支持播放格式:H264 AVCA的MP4格式,不能转换为mpeg-4格式,
- iOS无法播放MP4视频文件的解决方案 mp4视频iphone播放不了怎么办
- h5点播播放mp4视频遇到的坑,ios的h5不能播放视频等
- 【Linux 并发请求数】支持多少并发请求数
- Linux下Apache服务器并发优化
- 缓存
- redis
- Linux启动PHP的多进程任务与守护redis队列
- 重启redis命令
- golang