最近在写用户管理相关的微服务,其中比较重要的问题是如何保存用户的密码,加盐哈希是一种常见的做法。知乎上有个问题大家可以先读一下: 加盐密码保存的最通用方法是?

对于每个用户的密码,都应该使用独一无二的盐值,每当新用户注册或者修改密码时,都应该使用新的盐值进行加密,并且这个盐值应该足够长,使得有足够的盐值以供加密。随着彩虹表的出现及不断增大,MD5算法不建议再使用了。

存储密码的步骤

  1. 使用基于加密的伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)生成一个足够长的盐值,如Java中的java.security.SecureRandom
  2. 将盐值混入密码(常用的做法是置于密码之前),并使用标准的加密哈希函数进行加密,如SHA256
  3. 把哈希值和盐值一起存入数据库中对应此用户的那条记录

校验密码的步骤

  1. 从数据库取出用户的密码哈希值和对应盐值
  2. 将盐值混入用户输入的密码,并使用相同的哈希函数进行加密
  3. 比较上一步结果和哈希值是否相同,如果相同那么密码正确,反之密码错误

加盐使攻击者无法采用特定的查询表或彩虹表快速破解大量哈希值,但不能阻止字典攻击或暴力攻击。这里假设攻击者已经获取到用户数据库,意味着攻击者知道每个用户的盐值,根据Kerckhoffs’s
principle
,应该假设攻击者知道用户系统使用密码加密算法,如果攻击者使用高端GPU或定制的ASIC,每秒可以进行数十亿次哈希计算,针对每个用户进行字典查询的效率依旧很高效。

为了降低这类攻击,可以用一种叫做密钥扩展的技术,让哈希函数变得很慢,即使GPU或ASIC字典攻击或暴力攻击也会慢得让攻击者无法接受。密钥扩展的实现依赖一种CPU密集型哈希函数,如PBKDF2和本文将要介绍的Bcrypt。这类函数使用一个安全因子或迭代次数作为参数,该值决定了哈希函数会有多慢。

Bcrypt

Bcrypt是由Niels Provos和DavidMazières基于Blowfish密码设计的一种密码散列函数,于1999年在USENIX上发布。

wikipedia上Bcrypt词条中有该算法的伪代码:

Function bcrypt
Input:
cost: Number (4..31) // 该值决定了密钥扩展的迭代次数 Iterations = 2^cost
salt: array of Bytes (16 bytes) // 随机数
password: array of Bytes (1..72 bytes) // 用户密码
Output:
hash: array of Bytes (24 bytes) // 返回的哈希值
// 使用Expensive key setup算法初始化Blowfish状态
state <- EksBlowfishSetup(cost, salt, password) // 这一步是整个算法中最耗时的步骤 ctext <- "OrpheanBeholderScryDoubt" // 24 bytes,初始向量
repeat (64)
ctext <- EncryptECB(state, ctext) // 使用 blowfish 算法的ECB模式进行加密 return Concatenate(cost, salt, ctext)

// Expensive key setup

Function EksBlowfishSetup

Input:

cost: Number (4..31)

salt: array of Bytes (16 bytes)

password: array of Bytes (1..72 bytes)

Output:

state: opaque BlowFish state structure

state <- InitialState()
state <- ExpandKey(state, salt, password)
repeat (2 ^ cost) // 计算密集型
state <- ExpandKey(state, 0, password)
state <- ExpandKey(state, 0, salt) return state

Function ExpandKey(state, salt, password)

Input:

state: Opaque BlowFish state structure // 内部包含 P-array 和 S-box

salt: array of Bytes (16 bytes)

password: array of Bytes (1..72 bytes)

Output:

state: Opaque BlowFish state structure

// ExpandKey 是对输入参数进行固定的移位异或等运算,这里不列出

通过伪代码可以看出,通过制定不同的cost值,可以使得EksBlowfishSetup的运算次数大幅提升,从而达到慢哈希的目的。

Spring Security 中的 Bcrypt

理解了Bcrypt算法的原理,再来看Spring Security中的实现就很简单了。

package org.springframework.security.crypto.bcrypt;

...省略import...

public class BCryptPasswordEncoder implements PasswordEncoder {

...省略log...
<span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">strength</span><span class="o">;</span>         <span class="c1">// 相当于wiki伪代码中的cost,默认为10</span>

<span class="kd">private</span> <span class="kd">final</span> <span class="n">SecureRandom</span> <span class="n">random</span><span class="o">;</span>  <span class="c1">// CSPRNG</span>

<span class="c1">// 构造函数</span>
<span class="kd">public</span> <span class="nf">BCryptPasswordEncoder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">this</span><span class="o">(-</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// 相当于伪代码中的cost, 长度 4 ~ 31</span>
<span class="kd">public</span> <span class="nf">BCryptPasswordEncoder</span><span class="o">(</span><span class="kt">int</span> <span class="n">strength</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">(</span><span class="n">strength</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// 构造函数</span>
<span class="kd">public</span> <span class="nf">BCryptPasswordEncoder</span><span class="o">(</span><span class="kt">int</span> <span class="n">strength</span><span class="o">,</span> <span class="n">SecureRandom</span> <span class="n">random</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">strength</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">strength</span> <span class="o">&lt;</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">MIN_LOG_ROUNDS</span> <span class="o">||</span> <span class="n">strength</span> <span class="o">&gt;</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">MAX_LOG_ROUNDS</span><span class="o">))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Bad strength"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">this</span><span class="o">.</span><span class="na">strength</span> <span class="o">=</span> <span class="n">strength</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">random</span> <span class="o">=</span> <span class="n">random</span><span class="o">;</span>
<span class="o">}</span> <span class="c1">// 加密函数</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">encode</span><span class="o">(</span><span class="n">CharSequence</span> <span class="n">rawPassword</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">salt</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">strength</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">random</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">salt</span> <span class="o">=</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">gensalt</span><span class="o">(</span><span class="n">strength</span><span class="o">,</span> <span class="n">random</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">else</span> <span class="o">{</span>
<span class="n">salt</span> <span class="o">=</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">gensalt</span><span class="o">(</span><span class="n">strength</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">else</span> <span class="o">{</span>
<span class="n">salt</span> <span class="o">=</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">gensalt</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">hashpw</span><span class="o">(</span><span class="n">rawPassword</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span> <span class="n">salt</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// 密码匹配函数</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">matches</span><span class="o">(</span><span class="n">CharSequence</span> <span class="n">rawPassword</span><span class="o">,</span> <span class="n">String</span> <span class="n">encodedPassword</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">encodedPassword</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">encodedPassword</span><span class="o">.</span><span class="na">length</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Empty encoded password"</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span> <span class="k">if</span> <span class="o">(!</span><span class="n">BCRYPT_PATTERN</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="n">encodedPassword</span><span class="o">).</span><span class="na">matches</span><span class="o">())</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Encoded password does not look like BCrypt"</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span> <span class="k">return</span> <span class="n">BCrypt</span><span class="o">.</span><span class="na">checkpw</span><span class="o">(</span><span class="n">rawPassword</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span> <span class="n">encodedPassword</span><span class="o">);</span>
<span class="o">}</span>

}

package org.springframework.security.crypto.bcrypt;

public class BCrypt {
<span class="c1">// 生成盐值的函数 "$2a$" + 2字节log_round + "$" + 22字节随机数Base64编码</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">gensalt</span><span class="o">(</span><span class="kt">int</span> <span class="n">log_rounds</span><span class="o">,</span> <span class="n">SecureRandom</span> <span class="n">random</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">log_rounds</span> <span class="o">&lt;</span> <span class="n">MIN_LOG_ROUNDS</span> <span class="o">||</span> <span class="n">log_rounds</span> <span class="o">&gt;</span> <span class="n">MAX_LOG_ROUNDS</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Bad number of rounds"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">StringBuilder</span> <span class="n">rs</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="o">();</span>
<span class="kt">byte</span> <span class="n">rnd</span><span class="o">[]</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">BCRYPT_SALT_LEN</span><span class="o">];</span> <span class="n">random</span><span class="o">.</span><span class="na">nextBytes</span><span class="o">(</span><span class="n">rnd</span><span class="o">);</span> <span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"$2a$"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">log_rounds</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">)</span> <span class="o">{</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"0"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">log_rounds</span><span class="o">);</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"$"</span><span class="o">);</span>
<span class="n">encode_base64</span><span class="o">(</span><span class="n">rnd</span><span class="o">,</span> <span class="n">rnd</span><span class="o">.</span><span class="na">length</span><span class="o">,</span> <span class="n">rs</span><span class="o">);</span>
<span class="k">return</span> <span class="n">rs</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span> <span class="c1">// 总长度29字节</span>
<span class="o">}</span> <span class="cm">/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
* @return the hashed password
* @throws IllegalArgumentException if invalid salt is passed
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">hashpw</span><span class="o">(</span><span class="n">String</span> <span class="n">password</span><span class="o">,</span> <span class="n">String</span> <span class="n">salt</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IllegalArgumentException</span> <span class="o">{</span>
<span class="c1">// 该函数在验证阶段也会用到,因为前29字节为盐值,所以可以将之前计算过的密码哈希值做为盐值</span>
<span class="n">BCrypt</span> <span class="n">B</span><span class="o">;</span>
<span class="n">String</span> <span class="n">real_salt</span><span class="o">;</span>
<span class="kt">byte</span> <span class="n">passwordb</span><span class="o">[],</span> <span class="n">saltb</span><span class="o">[],</span> <span class="n">hashed</span><span class="o">[];</span>
<span class="kt">char</span> <span class="n">minor</span> <span class="o">=</span> <span class="o">(</span><span class="kt">char</span><span class="o">)</span> <span class="mi">0</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">rounds</span><span class="o">,</span> <span class="n">off</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="n">StringBuilder</span> <span class="n">rs</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">salt</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"salt cannot be null"</span><span class="o">);</span>
<span class="o">}</span> <span class="kt">int</span> <span class="n">saltLength</span> <span class="o">=</span> <span class="n">salt</span><span class="o">.</span><span class="na">length</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">saltLength</span> <span class="o">&lt;</span> <span class="mi">28</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid salt"</span><span class="o">);</span>
<span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">salt</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span> <span class="o">!=</span> <span class="sc">'$'</span> <span class="o">||</span> <span class="n">salt</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span> <span class="o">!=</span> <span class="sc">'2'</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid salt version"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">salt</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">2</span><span class="o">)</span> <span class="o">==</span> <span class="sc">'$'</span><span class="o">)</span> <span class="o">{</span>
<span class="n">off</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">else</span> <span class="o">{</span>
<span class="n">minor</span> <span class="o">=</span> <span class="n">salt</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">minor</span> <span class="o">!=</span> <span class="sc">'a'</span> <span class="o">||</span> <span class="n">salt</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span> <span class="o">!=</span> <span class="sc">'$'</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid salt revision"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">off</span> <span class="o">=</span> <span class="mi">4</span><span class="o">;</span>
<span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">saltLength</span> <span class="o">-</span> <span class="n">off</span> <span class="o">&lt;</span> <span class="mi">25</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid salt"</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// Extract number of rounds</span>
<span class="k">if</span> <span class="o">(</span><span class="n">salt</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="n">off</span> <span class="o">+</span> <span class="mi">2</span><span class="o">)</span> <span class="o">&gt;</span> <span class="sc">'$'</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Missing salt rounds"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">rounds</span> <span class="o">=</span> <span class="n">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">salt</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="n">off</span><span class="o">,</span> <span class="n">off</span> <span class="o">+</span> <span class="mi">2</span><span class="o">));</span> <span class="n">real_salt</span> <span class="o">=</span> <span class="n">salt</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="n">off</span> <span class="o">+</span> <span class="mi">3</span><span class="o">,</span> <span class="n">off</span> <span class="o">+</span> <span class="mi">25</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">passwordb</span> <span class="o">=</span> <span class="o">(</span><span class="n">password</span> <span class="o">+</span> <span class="o">(</span><span class="n">minor</span> <span class="o">&gt;=</span> <span class="sc">'a'</span> <span class="o">?</span> <span class="s">"\000"</span> <span class="o">:</span> <span class="s">""</span><span class="o">)).</span><span class="na">getBytes</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">catch</span> <span class="o">(</span><span class="n">UnsupportedEncodingException</span> <span class="n">uee</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">AssertionError</span><span class="o">(</span><span class="s">"UTF-8 is not supported"</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// 解析成16字节的字节数组</span>
<span class="n">saltb</span> <span class="o">=</span> <span class="n">decode_base64</span><span class="o">(</span><span class="n">real_salt</span><span class="o">,</span> <span class="n">BCRYPT_SALT_LEN</span><span class="o">);</span> <span class="c1">// 这里new了一个新的对象是因为会用到BCrypt中int P[]和 int S[],扩展的密钥存放在这两个结构体</span>
<span class="n">B</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCrypt</span><span class="o">();</span>
<span class="n">hashed</span> <span class="o">=</span> <span class="n">B</span><span class="o">.</span><span class="na">crypt_raw</span><span class="o">(</span><span class="n">passwordb</span><span class="o">,</span> <span class="n">saltb</span><span class="o">,</span> <span class="n">rounds</span><span class="o">);</span> <span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"$2"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">minor</span> <span class="o">&gt;=</span> <span class="sc">'a'</span><span class="o">)</span> <span class="o">{</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">minor</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"$"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">rounds</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">)</span> <span class="o">{</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"0"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">rounds</span><span class="o">);</span>
<span class="n">rs</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="s">"$"</span><span class="o">);</span>
<span class="n">encode_base64</span><span class="o">(</span><span class="n">saltb</span><span class="o">,</span> <span class="n">saltb</span><span class="o">.</span><span class="na">length</span><span class="o">,</span> <span class="n">rs</span><span class="o">);</span>
<span class="n">encode_base64</span><span class="o">(</span><span class="n">hashed</span><span class="o">,</span> <span class="n">bf_crypt_ciphertext</span><span class="o">.</span><span class="na">length</span> <span class="o">*</span> <span class="mi">4</span> <span class="o">-</span> <span class="mi">1</span><span class="o">,</span> <span class="n">rs</span><span class="o">);</span>
<span class="k">return</span> <span class="n">rs</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span> <span class="cm">/**
* Perform the central password hashing step in the bcrypt scheme
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number of rounds of hashing to apply
* @return an array containing the binary hashed password
*/</span>
<span class="kd">private</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">crypt_raw</span><span class="o">(</span><span class="kt">byte</span> <span class="n">password</span><span class="o">[],</span> <span class="kt">byte</span> <span class="n">salt</span><span class="o">[],</span> <span class="kt">int</span> <span class="n">log_rounds</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">cdata</span><span class="o">[]</span> <span class="o">=</span> <span class="o">(</span><span class="kt">int</span><span class="o">[])</span> <span class="n">bf_crypt_ciphertext</span><span class="o">.</span><span class="na">clone</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">clen</span> <span class="o">=</span> <span class="n">cdata</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>
<span class="kt">byte</span> <span class="n">ret</span><span class="o">[];</span> <span class="c1">// rounds = 1 &lt;&lt; log_round</span>
<span class="kt">long</span> <span class="n">rounds</span> <span class="o">=</span> <span class="n">roundsForLogRounds</span><span class="o">(</span><span class="n">log_rounds</span><span class="o">);</span> <span class="n">init_key</span><span class="o">();</span>
<span class="n">ekskey</span><span class="o">(</span><span class="n">salt</span><span class="o">,</span> <span class="n">password</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">long</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">rounds</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="c1">// 最耗时的密钥扩展</span>
<span class="n">key</span><span class="o">(</span><span class="n">password</span><span class="o">);</span>
<span class="n">key</span><span class="o">(</span><span class="n">salt</span><span class="o">);</span>
<span class="o">}</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">64</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="o">(</span><span class="n">clen</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span><span class="o">);</span> <span class="n">j</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">encipher</span><span class="o">(</span><span class="n">cdata</span><span class="o">,</span> <span class="n">j</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="n">ret</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">clen</span> <span class="o">*</span> <span class="mi">4</span><span class="o">];</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">clen</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">ret</span><span class="o">[</span><span class="n">j</span><span class="o">++]</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)</span> <span class="o">((</span><span class="n">cdata</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">&gt;&gt;</span> <span class="mi">24</span><span class="o">)</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="o">);</span>
<span class="n">ret</span><span class="o">[</span><span class="n">j</span><span class="o">++]</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)</span> <span class="o">((</span><span class="n">cdata</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">&gt;&gt;</span> <span class="mi">16</span><span class="o">)</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="o">);</span>
<span class="n">ret</span><span class="o">[</span><span class="n">j</span><span class="o">++]</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)</span> <span class="o">((</span><span class="n">cdata</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="o">)</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="o">);</span>
<span class="n">ret</span><span class="o">[</span><span class="n">j</span><span class="o">++]</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)</span> <span class="o">(</span><span class="n">cdata</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">ret</span><span class="o">;</span>
<span class="o">}</span> <span class="cm">/**
* Check that a plaintext password matches a previously hashed one
* @param plaintext the plaintext password to verify
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">checkpw</span><span class="o">(</span><span class="n">String</span> <span class="n">plaintext</span><span class="o">,</span> <span class="n">String</span> <span class="n">hashed</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">equalsNoEarlyReturn</span><span class="o">(</span><span class="n">hashed</span><span class="o">,</span> <span class="n">hashpw</span><span class="o">(</span><span class="n">plaintext</span><span class="o">,</span> <span class="n">hashed</span><span class="o">));</span>
<span class="o">}</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">equalsNoEarlyReturn</span><span class="o">(</span><span class="n">String</span> <span class="n">a</span><span class="o">,</span> <span class="n">String</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">char</span><span class="o">[]</span> <span class="n">caa</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">();</span>
<span class="kt">char</span><span class="o">[]</span> <span class="n">cab</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">caa</span><span class="o">.</span><span class="na">length</span> <span class="o">!=</span> <span class="n">cab</span><span class="o">.</span><span class="na">length</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span> <span class="kt">byte</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">caa</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">ret</span> <span class="o">|=</span> <span class="n">caa</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">^</span> <span class="n">cab</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">ret</span> <span class="o">==</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>

}

性能

慢哈希既要防止攻击者无法使用暴力破击,又不能影响用户体验,由于机器性能的差异,获取强度参数最好的办法就是执行一个简短的性能基准测试,找到使哈希函数大约耗费0.5秒的值。

public class BCryptBench {
public static void main(String[] args) {
long startTime, endTime, duration;
    <span class="c1">// the default strength 10</span>
<span class="n">BCryptPasswordEncoder</span> <span class="n">bCryptPasswordEncoder10</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCryptPasswordEncoder</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bCryptPasswordEncoder10</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="s">"admin"</span><span class="o">));</span>
<span class="n">endTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">+=</span> <span class="o">(</span><span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">duration</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span> <span class="c1">// 88.4ms</span> <span class="c1">// strength 11</span>
<span class="c1">// the default strength 10</span>
<span class="n">BCryptPasswordEncoder</span> <span class="n">bCryptPasswordEncoder11</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCryptPasswordEncoder</span><span class="o">(</span><span class="mi">11</span><span class="o">);</span>
<span class="n">duration</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bCryptPasswordEncoder11</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="s">"admin"</span><span class="o">));</span>
<span class="n">endTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">+=</span> <span class="o">(</span><span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">duration</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span> <span class="c1">// 175.3ms</span> <span class="c1">// strength 12</span>
<span class="n">BCryptPasswordEncoder</span> <span class="n">bCryptPasswordEncoder12</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCryptPasswordEncoder</span><span class="o">(</span><span class="mi">12</span><span class="o">);</span>
<span class="n">duration</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bCryptPasswordEncoder12</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="s">"admin"</span><span class="o">));</span>
<span class="n">endTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">+=</span> <span class="o">(</span><span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span> <span class="n">duration</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span> <span class="c1">// 344.3ms</span> <span class="c1">// strength 13</span>
<span class="n">BCryptPasswordEncoder</span> <span class="n">bCryptPasswordEncoder13</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCryptPasswordEncoder</span><span class="o">(</span><span class="mi">13</span><span class="o">);</span>
<span class="n">duration</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bCryptPasswordEncoder13</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="s">"admin"</span><span class="o">));</span>
<span class="n">endTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">+=</span> <span class="o">(</span><span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">duration</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span> <span class="c1">// 703.8ms</span> <span class="c1">// strength 14</span>
<span class="n">BCryptPasswordEncoder</span> <span class="n">bCryptPasswordEncoder14</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCryptPasswordEncoder</span><span class="o">(</span><span class="mi">14</span><span class="o">);</span>
<span class="n">duration</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bCryptPasswordEncoder14</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="s">"admin"</span><span class="o">));</span>
<span class="n">endTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">+=</span> <span class="o">(</span><span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">duration</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span> <span class="c1">// 1525.0ms</span> <span class="c1">// strength 15</span>
<span class="n">BCryptPasswordEncoder</span> <span class="n">bCryptPasswordEncoder15</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BCryptPasswordEncoder</span><span class="o">(</span><span class="mi">15</span><span class="o">);</span>
<span class="n">duration</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">bCryptPasswordEncoder15</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="s">"admin"</span><span class="o">));</span>
<span class="n">endTime</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
<span class="n">duration</span> <span class="o">+=</span> <span class="o">(</span><span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span> <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">duration</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span> <span class="c1">// 2921.9ms</span>
<span class="o">}</span>

}

从测试的结果可以看出,如果想选定一个执行时间为0.5秒的慢哈希,需要将Bcrypt函数的强度设置为12或13。而在我们自己的微服务中,使用了默认的强度10。

Reference:

1 Salted Password Hashing - Doing it Right

2 Salted Password Hashing - Doing it Right 译文

原文地址:https://zhjwpku.com/2017/11/30/bcrypt-in-spring-security.html

Spring Security 中的 Bcrypt的更多相关文章

  1. [收藏]Spring Security中的ACL

    ACL即访问控制列表(Access Controller List),它是用来做细粒度权限控制所用的一种权限模型.对ACL最简单的描述就是两个业务员,每个人只能查看操作自己签的合同,而不能看到对方的合 ...

  2. Spring Security中html页面设置hasRole无效的问题

    Spring Security中html页面设置hasRole无效的问题 一.前言 学了几天的spring Security,偶然发现的hasRole和hasAnyAuthority的区别.当然,可能 ...

  3. Spring Security 中的过滤器

    本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12. Spring Security 的本质是一个过滤器链(filter chain) ...

  4. 浅谈使用spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配

    浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(matches) spring security中的BCryptPass ...

  5. 看源码,重新审视Spring Security中的角色(roles)是怎么回事

    在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...

  6. 六:Spring Security 中使用 JWT

    Spring Security 中使用 JWT 1.无状态登录 1.1 什么是有状态? 1.2 什么是无状态 1.3 如何实现无状态 2.JWT 2.1 JWT数据格式 2.2 JWT交互流程 2.3 ...

  7. 五:Spring Security 中的角色继承问题

    Spring Security 中的角色继承问题 以前的写法 现在的写法 源码分析 SpringSecurity 在角色继承上有两种不同的写法,在 Spring Boot2.0.8(对应 Spring ...

  8. Spring Security中实现微信网页授权

    微信公众号提供了微信支付.微信优惠券.微信H5红包.微信红包封面等等促销工具来帮助我们的应用拉新保活.但是这些福利要想正确地发放到用户的手里就必须拿到用户特定的(微信应用)微信标识openid甚至是用 ...

  9. 干货|一个案例学会Spring Security 中使用 JWT

    在前后端分离的项目中,登录策略也有不少,不过 JWT 算是目前比较流行的一种解决方案了,本文就和大家来分享一下如何将 Spring Security 和 JWT 结合在一起使用,进而实现前后端分离时的 ...

随机推荐

  1. win10下安装mongodb(解压版)

    首先到官网下载安装包.(https://www.mongodb.com/download-center#community) 1.创建mongodb目录 2.配置文件mongodb.config 3. ...

  2. SQL优化系列(三)- 用最少的索引获得最大的性能提升

    从全局出发优化索引 对于高负载的数据库,如何创建最少的索引,让数据库的整体性能提高呢?例如,对于100 条SQL语句,如何创建最佳的5条索引? SQL自动优化工具SQL Tuning Expert P ...

  3. KiCad 元件值 F4NNIU 规范(2019-10-10)

    目录 KiCad 元件值 F4NNIU 规范 电阻 电容 电感 二极管 三极管 MOSFET IC 文件历史 KiCad 元件值 F4NNIU 规范 为方便物料统计以及规范物料,在制造 BOM 时可以 ...

  4. POJ3614 [USACO07NOV]防晒霜Sunscreen

    Sunscreen Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9333   Accepted: 3264 Descrip ...

  5. PHP来控制Linux,ssh2来控制服务器端

    注意:我们用PHP来控制Linux,php环境可以在windows也可以在linux,但是我们要控制的机器是一台linux(被控制的linux关闭selinux和firewalld). 如果php在l ...

  6. IntelliJ IDEA 添加项目后编译显示包不存在的解决方案

    File -> Project Structure -> Modules 看看是否有多个项目,删掉无用的.或者调整一下项目,重新 Mark as 一下,指定成 Sources

  7. python 正则表达式简介

  8. Please upgrade the installed version of powershell to the minimum required version and run the command again.

    版权声明:本文为博主原创文章,转载请注明出处.谢谢 https://blog.csdn.net/cow66/article/details/77993908 我的系统是windows 7 安装了vag ...

  9. hdu2041 dp

    #include<stdio.h> int main() { int i,t,n; ]; dp[]=; dp[]=; dp[]=; ;i<=;i++) dp[i]=dp[i-]+dp ...

  10. C++之ARX,Acstring,ACahr转char

    AcDbText* pText = AcDbText::cast(pEnt); AcString sText = DBHelper::AcStringFree(pText->textString ...