共计 6353 个字符,预计需要花费 16 分钟才能阅读完成。
简介
Argon2 是一个密钥推导函数,在 2015 年 7 月被选为明码哈希大赛的冠军,它由卢森堡大学的 Alex Biryukov、Daniel Dinu 和 Dmitry Khovratovich 设计,Argon2 的实现通常是以 Creative Commons CC0 许可(即公共畛域)或 Apache License 2.0 公布,并提供了三个相干版本,别离是 Argon2d,Argon2i 和 Argon2id。
本文将会讨论一下 Argon2 的原理和应用。
密钥推导函数 key derivation function
在密码学中,密钥推导函数 (KDF) 是一种密码学哈希函数,它应用伪随机函数从一个机密值 (如主密钥、明码或口令) 中推导出一个或多个密钥。KDF 可用于将密钥拉伸成更长的密钥,或取得所需格局的密钥,例如将 Diffie-Hellman 密钥替换的后果转换为用于 AES 的对称密钥。
Password Hashing Competition
密码学尽管是钻研明码的,然而其加密算法是越公开越好,只有公开能力去检视该算法的好坏,只有通过大家的彻底钻研,才可能让该算法得以在业界应用和流传。
最闻名的明码算法大赛必定是由 NIST 在 2001 年为了指定规范的 AES 算法举办的大赛,该大赛的目标寻找最新的加密算法来代替老的 DES 算法。在这次大赛中,涌现了许多优良的算法,包含 CAST-256, CRYPTON, DEAL, DFC, E2, FROG, HPC, LOKI97, MAGENTA, MARS, RC6, Rijndael, SAFER+, Serpent, 和 Twofish 等。最终 Rijndael 算法被选为最终的 AES 算法实现。
同样的 PHC 也是一个这样的算法较量,和 NIST 举办的算法较量不同的是,这是一个非官方的,由明码学家们组织的较量。它是在由 Jean-Philippe Aumasson 于 2012 年秋季发动。
2013 年第一季度,公布了征集意见书的告诉,到 2014 年 3 月 31 日截止日期,共收到 24 份意见书。2014 年 12 月,确定了 9 个入围名单。2015 年 7 月,发表 Argon2 为优胜者。
Argon2 算法
Argon2 的设计很简略,旨在实现最高的内存填充率和对多个计算单元的无效利用,同时还能提供对 tradeoff attacks 的进攻(通过利用处理器的缓存和内存)。
Argon2 有三个变种。Argon2i、Argon2d 和 Argon2id。Argon2d 速度更快,并且应用数据依赖的内存拜访形式,这使得它对 GPU 破解攻打有很强的抵抗力,适宜没有 side-channel timing attacks 威逼的利用(例如加密货币)。
Argon2i 则应用数据无关的内存拜访,这对于明码哈希和基于明码的密钥推导算法来说是首选,其特点是速度较慢,因为它在内存上运行了更多的解决逻辑,以避免 tradeoff attacks。
Argon2id 是 Argon2i 和 Argon2d 的混合体,采纳数据依赖型和数据独立型内存拜访相结合的形式,从而能够同时抵挡 side-channel timing attacks 和 GPU 破解攻打的能力。
Argon2 的输出参数
Argon2 有两类输出参数,别离是 primary inputs 和 secondary inputs。
primary inputs 包含要加密的音讯 P 和 nonce S,别离代表 password 和 salt。
P 的长度是 0 到 232- 1 字节,S 的长度是 8 到 232- 1 字节(如果是做明码 hash,举荐 16 字节)。
之所以叫做 primary inputs,是因为这两个参数是必须输出的。
剩下的参数叫做 secondary inputs,他们包含:
并行水平 p,示意同时能够有多少独立的计算链同时运行,取值是 1 到 224-1。
Tag 长度 τ,长度从 4 到 232- 1 字节。‘
内存大小 m, 单位是兆,值取 8p 到 232-1。
迭代器的个数 t,晋升运行速度。取值 1 到 232-1。
版本号 v,一个字节,取值 0x13。
安全值 K,长度是 0 到 232- 1 字节。
附加数据 X,长度是 0 到 232- 1 字节。
Argon2 的类型,0 代表 Argon2d,1 代表 Argon2i,2 代表 Argon2id。
这些输出能够用上面的代码来示意:
Inputs:
password (P): Bytes (0..232-1) Password (or message) to be hashed
salt (S): Bytes (8..232-1) Salt (16 bytes recommended for password hashing)
parallelism (p): Number (1..224-1) Degree of parallelism (i.e. number of threads)
tagLength (T): Number (4..232-1) Desired number of returned bytes
memorySizeKB (m): Number (8p..232-1) Amount of memory (in kibibytes) to use
iterations (t): Number (1..232-1) Number of iterations to perform
version (v): Number (0x13) The current version is 0x13 (19 decimal)
key (K): Bytes (0..232-1) Optional key (Errata: PDF says 0..32 bytes, RFC says 0..232 bytes)
associatedData (X): Bytes (0..232-1) Optional arbitrary extra data
hashType (y): Number (0=Argon2d, 1=Argon2i, 2=Argon2id)
Output:
tag: Bytes (tagLength) The resulting generated bytes, tagLength bytes long
解决流程
咱们先来看一下非并行的 Argon2 的算法流程:
非并行的 Argon2 是最简略的。
上图中 G 示意的是一个压缩函数,接管两个 1024byte 的输出,输入一个 1024byte。
i 示意的是执行的步数,下面的 φ(i) 就是输出,取自内存空间。
作为一个 memory-hard 的算法,一个很重要的工作就是构建初始内存。接下来,咱们看一下如何构建初始内存空间。
首先,咱们须要构建 H0,这是一个 64-byte 的 block 值,通过 H0,能够去构建更多的 block。计算 H0 的公式如下:
H0 = H(p,τ,m,t,v,y,⟨P⟩,P,⟨S⟩,S,⟨K⟩,K,⟨X⟩,X)
它是后面咱们提到的输出参数的 H 函数。H0 的大小是 64byte。
看下 H0 的代码生成:
Generate initial 64-byte block H0.
All the input parameters are concatenated and input as a source of additional entropy.
Errata: RFC says H0 is 64-bits; PDF says H0 is 64-bytes.
Errata: RFC says the Hash is H^, the PDF says it's ℋ (but doesn't document what ℋ is). It's actually Blake2b.
Variable length items are prepended with their length as 32-bit little-endian integers.
buffer ← parallelism ∥ tagLength ∥ memorySizeKB ∥ iterations ∥ version ∥ hashType
∥ Length(password) ∥ Password
∥ Length(salt) ∥ salt
∥ Length(key) ∥ key
∥ Length(associatedData) ∥ associatedData
H0 ← Blake2b(buffer, 64) //default hash size of Blake2b is 64-bytes
对于输出参数并行水平 p 来说,须要将内存分成一个内存矩阵 Bi, 它是一个 p 行的矩阵。
计算矩阵 B 的值:
其中 H′ 是一个基于 H 的变长 hash 算法。
咱们给一下这个算法的实现:
Function Hash(message, digestSize)
Inputs:
message: Bytes (0..232-1) Message to be hashed
digestSize: Integer (1..232) Desired number of bytes to be returned
Output:
digest: Bytes (digestSize) The resulting generated bytes, digestSize bytes long
Hash is a variable-length hash function, built using Blake2b, capable of generating
digests up to 232 bytes.
If the requested digestSize is 64-bytes or lower, then we use Blake2b directly
if (digestSize <= 64) then
return Blake2b(digestSize ∥ message, digestSize) //concatenate 32-bit little endian digestSize with the message bytes
For desired hashes over 64-bytes (e.g. 1024 bytes for Argon2 blocks),
we use Blake2b to generate twice the number of needed 64-byte blocks,
and then only use 32-bytes from each block
Calculate the number of whole blocks (knowing we’re only going to use 32-bytes from each)
r ← Ceil(digestSize/32)-1;
Generate r whole blocks.
Initial block is generated from message
V1 ← Blake2b(digestSize ∥ message, 64);
Subsequent blocks are generated from previous blocks
for i ← 2 to r do
Vi ← Blake2b(Vi-1, 64)
Generate the final (possibly partial) block
partialBytesNeeded ← digestSize – 32*r;
Vr+1 ← Blake2b(Vr, partialBytesNeeded)
Concatenate the first 32-bytes of each block Vi
(except the possibly partial last block, which we take the whole thing)
Let Ai represent the lower 32-bytes of block Vi
return A1 ∥ A2 ∥ … ∥ Ar ∥ Vr+1
如果咱们的迭代次数多于一次,也就是说 t > 1, 咱们这样计算下一次迭代的 B:
B^{t}i=G\left(B^{t-1}i, B\left[i^{\prime}\right]\left[j^{\prime}\right]\right) \oplus B^{t-1}iB
t
i=G(B
t−1
i,B[i
′
][j
′
])⊕B
t−1
i
B^{t}i=G\left(B^{t}i, B\left[i^{\prime}\right]\left[j^{\prime}\right]\right) \oplus B^{t-1}iB
t
i=G(B
t
i,B[i
′
][j
′
])⊕B
t−1
i
最终遍历 T 次之后,咱们失去最终的 B:
B_{\text {final}}=B^{T}0 \oplus B^{T}1 \oplus \cdots \oplus B^{T}p-1B
final
=B
T
0⊕B
T
1⊕⋯⊕B
T
p−1
最初失去输入:
\mathrm{Tag} \leftarrow H^{\prime}\left(B_{\text {final}}\right)Tag←H
′
(B
final
)
这段逻辑也能够用代码来示意:
Calculate number of 1 KB blocks by rounding down memorySizeKB to the nearest multiple of 4*parallelism kibibytes
blockCount ← Floor(memorySizeKB, 4*parallelism)
Allocate two-dimensional array of 1 KiB blocks (parallelism rows x columnCount columns)
columnCount ← blockCount / parallelism; //In the RFC, columnCount is referred to as q
Compute the first and second block (i.e. column zero and one) of each lane (i.e. row)
for i ← 0 to parallelism-1 do for each row
Bi[0] ← Hash(H0 ∥ 0 ∥ i, 1024) //Generate a 1024-byte digest
Bi[1] ← Hash(H0 ∥ 1 ∥ i, 1024) //Generate a 1024-byte digest
Compute remaining columns of each lane
for i ← 0 to parallelism-1 do //for each row
for j ← 2 to columnCount-1 do //for each subsequent column
//i'and j' indexes depend if it's Argon2i, Argon2d, or Argon2id (See section 3.4)
i′, j′ ← GetBlockIndexes(i, j) //the GetBlockIndexes function is not defined
Bi[j] = G(Bi[j-1], Bi′[j′]) //the G hash function is not defined
Further passes when iterations > 1
for nIteration ← 2 to iterations do
for i ← 0 to parallelism-1 do for each row
for j ← 0 to columnCount-1 do //for each subsequent column
//i'and j' indexes depend if it's Argon2i, Argon2d, or Argon2id (See section 3.4)
i′, j′ ← GetBlockIndexes(i, j)
if j == 0 then
Bi[0] = Bi[0] xor G(Bi[columnCount-1], Bi′[j′])
else
Bi[j] = Bi[j] xor G(Bi[j-1], Bi′[j′])
Compute final block C as the XOR of the last column of each row
C ← B0[columnCount-1]
for i ← 1 to parallelism-1 do
C ← C xor Bi[columnCount-1]
Compute output tag
return Hash(C, tagLength)
本文已收录于 http://www.flydean.com/40-arg…
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!