本文介绍了密码哈希算法Argon2,它是一种内存密集型的哈希算法,旨在抵抗GPU破解。文章解释了Argon2的原理、不同变体(Argon2d、Argon2i、Argon2id)及其参数,并提供了一个使用JavaScript实现的Argon2示例,展示了如何在浏览器中使用Argon2进行密码哈希。
你知道那些告诉你密码多久会被破解的表格吗?嗯,它们都有点傻,因为它们没有考虑到破解每个密码的难度。事实上,它们倾向于基于快速哈希方法来制定基准。一个更具代表性的表格是分析每种方法:
在这里,我们看到有快速哈希方法(如 NTLM、MD5 和 SHA1)和慢速哈希方法(如 PBKDF2、bcrypt 和 SHA512crypt)。对于九字符密码,使用 bcrypt(来自表格),将花费超过 180 万年,而使用 NTLM(如早期版本的 Windows 中使用的),只需 几个小时。
那么,最佳的密码哈希方法是什么,它们是如何工作的?我们用于存储哈希密码的最常用方法是 PBKDF2、crypt 和 bcrypt。这些方法使用多轮哈希,这将减慢哈希密码的总体时间。例如,如果我们有 1,000 轮哈希,那么生成哈希值的时间将大约长 1,000 倍。但是 GPU 有多个核心,因此使用具有 4,000 个核心的 GPU,我们可以一次尝试多达 4,000 个密码,这加快了该过程。那么,我们如何破解这些 GPU 呢?嗯,一种方法是给它们一个难题,让它们努力工作。为此,GPU 的每个核心都有相对较少数量的本地(缓存)内存,并且,如果它们需要更多内存,则必须减慢其运行速度,并使处理器因内存传输而停滞。
在网络安全中,有很多情况是你有一个密钥,然后需要从中派生出一些东西——以及一个 salt 值。这涉及到获取一个密码,然后派生出一个固定大小的加密密钥或一个哈希值。但是,我们遇到的问题是,GPU 允许快速哈希,因此通常可以快速破解密码的哈希版本。为了克服这个问题,我们通常对哈希过程应用多轮处理,例如 Bcrypt 和 PBKDF2。
不幸的是,GPU 核心仍然可以以合理的成本运行哈希过程。因此,为了克服使用 GPU 进行破解,我们可以构建一种计算方法,该方法将需要给定的内存量来完成任务。由于 GPU 的核心对于每个核心没有太多内存,因此本地内存会过载,并导致 GPU 减速。这被称为内存硬(memory-hard),并通过 Argon 2 实施。
除此之外,在加密货币等领域,我们还有一些应用程序,其中需要完成给定的计算量(工作量)才能执行任务。同样,我们可以使用 Argon 2 来定义计算的工作量要求。
Argon2 在 RFC 9106 中定义,标题为“用于密码哈希和工作量证明应用程序的内存硬函数(Memory-Hard Function for Password Hashing and Proof-of-Work Applications)”,用于密码哈希、密钥派生和工作量证明:
主要变体是为 x86 架构优化的 Argon2id。它有两个子变体:Argon2d 和 Argon2i。总体而言,Argon2d 提供了一种与数据相关的内存访问方法,并且对加密货币和工作量证明应用程序很有用。它对侧信道时序攻击也没有任何威胁。或者,Argon2i 使用与数据无关的内存访问,可用于密码哈希和基于密码的密钥派生 (KDF)。
Argon 2 中使用的主要参数如图 1 所示,例如迭代次数、memcost 和并行度。标准形式是:
$argon2i$v=19$m=4096,t=2,p=4$salt$rZHPyRIa8XEvQ9rVqpvoibllLagNNGUeCNCmxeZfgBA
其中 v=19 标识我们正在使用 Argon 2。然后参数在“m=4096,t=2,p=4”中定义,其中我们具有 4,096 字节的内存成本,并行度为 4,迭代次数为 2。 在本例中,salt 是“salt”,派生的哈希是“rZHPyRIa8XEvQ9rVqpvoibllLagNNGUeCNCmxeZfgBA”。Argon 2 还可以使用定义的字节数(摘要大小)输出。
图 1:Argon 2
参数为:
一个例子是:
Type: Argon2i
Message: Hello 123
Time cost: 8
Mem cost: 64
Salt: 8f3fe0a782e42b6c
Parallelism: 4
Hash: cc117e31
Hash: $argon2i$v=19$m=64,t=8,p=4$jz/gp4LkK2w$zBF+MQ
Hash generated in 13 ms
以下是纯 JavaScript 中的代码[ here]:
<script src="https://cdnjs.cloudflare.com/ajax/libs/argon2-browser/1.18.0/argon2-bundled.min.js
"></script>
<style>
.dropdown {
font-size: 16px;
border: 2px solid grey;
width: 100%;
border-left: 12px solid green;
border-radius: 5px;
padding: 14px;
}
pre {
font-size: 16px;
border: 2px solid grey;
width: 100%;
border-left: 12px solid green;
border-radius: 5px;
padding: 14px;
}
textarea {
font-size: 20px;
border: 2px solid grey;
width: 100%;
border-radius: 5px;
padding: 14px;
}
</style>
<div class="indented">
<table width="100%">
<tr>
<th width="15%">Method(方法)</th>
<td style="text-align:left">
<p>
<input id="genkey" class="btn btn-large btn-primary" type="button" value="Gen New Argon2(生成新的Argon2)" />
</p>
</td>
</tr>
<tr>
<th width="15%">Message(信息)</th>
<td>
<textarea cols="20" id="message" name="message" rows="1" style="width:100%"></textarea>
</td>
</tr>
<tr>
<th width="15%">Time cost(时间成本)</th>
<td style="text-align:left">
<div class="dropdown">
<select name="timecost" id="timecost">
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
</select>
</div>
</td>
</tr>
<tr>
<th width="15%">Mem Cost(内存成本)</th>
<td style="text-align:left">
<div class="dropdown">
<select name="memcost" id="memcost">
<option value="32">32</option>
<option value="64">64</option>
<option value="128">128</option>
<option value="256">256</option>
<option value="512">512</option>
</select>
</div>
</td>
</tr>
<tr>
<th width="15%">Parallelism(并行度)</th>
<td style="text-align:left">
<div class="dropdown">
<select name="parallelism" id="parallelism">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</div>
</td>
</tr>
<tr>
<th width="15%">Hash Length(哈希长度)</th>
<td style="text-align:left">
<div class="dropdown">
<select name="hashlength" id="hashlength">
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
</select>
</div>
</td>
</tr>
<tr>
<th width="15%">Salt Size (Bytes)(Salt 大小(字节))</th>
<td style="text-align:left">
<div class="dropdown">
<select name="saltlength" id="saltlength">
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
</select>
</div>
</td>
</tr>
<tr>
<th width="15%">Method(方法)</th>
<td style="text-align:left">
<div class="dropdown">
<select name="hashtype" id="hashtype">
<option value="Argon2i">Argon2i</option>
<option value="Argon2d">Argon2d</option>
<option value="Argon2id">Argon2id</option>
</select>
</div>
</td>
</tr>
</table>
<h2>Hash</h2>
<table width="100%">
<tr>
<th valign="top" width="15%">Hash</th>
<td>
<pre id="key"></pre>
</td>
</tr>
</table>
</div>
<script>
function toHexString(byteArray) {
return Array.from(byteArray, function (byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('')
}
(async function () {
async function genKey() {
var message = document.getElementById("message").value;
var hashlength = parseInt(document.getElementById("hashlength").value);
var saltlength = parseInt(document.getElementById("saltlength").value);
var timecost = parseInt(document.getElementById("timecost").value);
var memcost = parseInt(document.getElementById("memcost").value);
var parallel = parseInt(document.getElementById("parallelism").value);
var salt = window.crypto.getRandomValues(new Uint8Array(saltlength));
var dt = new Date();
var time = -(dt.getTime());
var hashtype = document.getElementById("hashtype").value;
var hashingtype;
if (hashtype == "Argon2i") hashingtype = argon2.ArgonType.Argon2i;
else if (hashtype == "Argon2d") hashingtype = argon2.ArgonType.Argon2d;
else hashingtype = argon2.ArgonType.Argon2id;
const hash = await argon2.hash({
pass: message,
salt: salt,
time: timecost, // Time cost(时间成本)
mem: memcost, // Memory cost in KB(KB 中的内存成本)
parallelism: parallel, // Number of parallel threads(并行线程数)
hashLen: hashlength, // Length of the hash(哈希长度)
type: hashingtype,
});
console.log(hash.hashHex);
dt = new Date();
time += (dt.getTime());
document.getElementById("key").innerHTML = "Type:\t\t" + hashtype;
document.getElementById("key").innerHTML += "\nMessage:\t" + message;
document.getElementById("key").innerHTML += "\nTime cost:\t" + timecost;
document.getElementById("key").innerHTML += "\nMem cost:\t" + memcost;
document.getElementById("key").innerHTML += "\nSalt:\t\t" + toHexString(salt);
document.getElementById("key").innerHTML += "\nParallelism:\t" +parallel;
document.getElementById("key").innerHTML += "\nHash:\t\t" + hash.hashHex;
document.getElementById("key").innerHTML += "\nHash:\t\t" + hash.encoded;
document.getElementById("key").innerHTML += '\nHash generated in ' + time + ' ms\n';
}
document.getElementById('message').value = "Hello 123";
await genKey();
document.getElementById("genkey").addEventListener("click", genKey);
document.getElementById("timecost").addEventListener("click", genKey);
document.getElementById("memcost").addEventListener("click", genKey);
document.getElementById("hashlength").addEventListener("click", genKey);
document.getElementById("saltlength").addEventListener("click", genKey);
document.getElementById("hashtype").addEventListener("click", genKey);
document.getElementById("parallelism").addEventListener("click", genKey);
document.getElementById("message").addEventListener("input", genKey);
message.focus();
})();
</script>
浏览器中 Argon2i 的一些示例运行如下:
请注意,时间以毫秒为单位,其中 t 是时间成本。我们可以看到,如果时间成本为 8 且内存成本为 128MB,则将花费 1,467 毫秒。你可以在这里自己尝试一下:
- 原文链接: billatnapier.medium.com/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!