密码学中速度的重要性:谁最快(谁最慢)?

在密码学中速度何时重要:什么是最快的(和最慢的)?
有时候,你在密码学中需要极快的操作,比如生成签名、密文或哈希值;而另一些时候,你却希望过程慢下来,例如从秘密中派生加密密钥,或存储密码的哈希版本。
因此,我们使用 Zig 编程语言做一次基准测试,并在标准 Windows 机器上运行(测试代码)。
哈希方法(MD5、SHA1、SHA256、Blake2、SHA3)
对于哈希,许多人会认为 SHA-2(SHA-256 和 SHA-512)应该是速度最快的方法之一,然而其性能远不及 SHA-3 Kangaroo 12(KT12)(这里)、Blake2b(这里)和 SHA-1(这里):
1 kt128 1262 MiB/s
2 blake2b 1004 MiB/s
3 sha1 851 MiB/s
4 turboshake-128 725 MiB/s
5 md5 703 MiB/s
6 sha512 640 MiB/s
7 turboshake-256 597 MiB/s
8 blake2s 591 MiB/s
9 blake3 566 MiB/s
10 shake-128 378 MiB/s
11 shake-256 313 MiB/s
12 sha3-256 312 MiB/s
13 ascon-256 234 MiB/s
14 sha3-512 166 MiB/s
15 sha256 48 MiB/s
为什么 KT128 表现如此出色,而它的“父方法”SHA-3 却不尽如人意?原因在于 Kangaroo 12 将哈希计算轮数减少到 12,因此安全级别也随之降低。但这可以接受,因为 SHA-3 的安全余量非常充足,即使削减轮数后依然足够安全。我们还可以看到,Blake2b 比 Blake2s 和 Blake3 快得多。至于轻量级标准 Ascon,其性能相当可观,甚至超过了 SHA-2(256)。
如果想要极致的哈希性能,可以使用并行模式,这能进一步提升 KT12 和 Blake3 的哈希速率:
1 kt128-parallel: 1312 MiB/s
2 blake3-parallel: 600 MiB/s
使用 OpenSSL 的更完整基准测试请见:https://asecuritysite.com/openssl/openssl_full2b
消息认证码(MAC)
对于 MAC,我们使用共享密钥,并结合给定的哈希函数或对称密钥算法来生成 MAC 标签:

图:使用哈希构建 MAC
超快的 AEGIS 密码(这里)在性能上通常碾压其他常见方法:
1 aegis-128x2 mac 19718 MiB/s
2 aegis-128l mac 19479 MiB/s
3 aegis-256x2 mac 14170 MiB/s
4 aegis-128x4 mac 12535 MiB/s
5 ghash 11177 MiB/s
6 aegis-256 mac 10855 MiB/s
7 polyval 10673 MiB/s
8 aegis-256x4 mac 7649 MiB/s
9 siphash128-1-3 5343 MiB/s
10 siphash-1-3 5301 MiB/s
11 siphash128-2-4 3127 MiB/s
12 siphash-2-4 3092 MiB/s
13 aes-cmac 2381 MiB/s
14 poly1305 1663 MiB/s
15 hmac-sha1 905 MiB/s
16 hmac-md5 658 MiB/s
17 hmac-sha512 642 MiB/s
18 hmac-sha256 47 MiB/s
可以看到,基于 AES 加密的 AES-CMAC 方法通常比基于哈希的 HMAC 更快,其中 HMAC-SHA1 速度最快,而 HMAC-SHA256 则落后较多。AES 方法之所以能获得这种速度优势,很可能是现代处理器为 AES 指令集提供了硬件加速。
AEAD —— 对称密钥
对称密钥加密使用同一把密钥进行加解密。我们还可以通过引入关联数据来增强安全性,这就是 AEAD(带附加数据的认证加密)。

在这一类别中,AEGIS(这里)遥遥领先,速度约为 AES OCB 的两倍。而最流行的 AES 版本 —— AES GCM —— 通常也比 ChaCha20 更快:
1 aegis-128l 14918 MiB/s
2 aegis-256x2 12157 MiB/s
3 aes128-ocb 10157 MiB/s
4 aegis-128x2 9157 MiB/s
5 aegis-256 9009 MiB/s
6 aegis-128x4 7278 MiB/s
7 aes256-ocb 7085 MiB/s
8 aes128-gcm 5857 MiB/s
9 aegis-256x4 5054 MiB/s
10 aes256-gcm 4900 MiB/s
11 xchacha8Poly1305 714 MiB/s
12 chacha20Poly1305 440 MiB/s
13 xchacha20Poly1305 440 MiB/s
14 xsalsa20Poly1305 292 MiB/s
15 isapa128a 158 MiB/s
同样,AES GCM 之所以击败 ChaCha20,原因在于现代处理器通常提供了良好的硬件加速支持。若纯粹比较软件实现,在相同硬件条件下,ChaCha20 很可能击败大多数 AES 实现。
使用 OpenSSL 的更完整基准测试请见:https://asecuritysite.com/openssl/openssl3_b
数字签名
对于公钥数字签名,我们创建一对密钥(公钥和私钥),使用私钥对消息的哈希进行签名,然后由验证方使用关联的公钥验证签名:

目前主流使用的签名方法包括 RSA、Ed25519 和 ECDSA。一般来说,由于量子计算机的潜在威胁,RSA 和椭圆曲线方法(Ed25519、ECDSA)正逐渐被边缘化。但现阶段,许多应用仍然依赖 Ed25519 和 ECDSA,因此这些方法在 HSM(硬件安全模块)和 TPM(可信平台模块)等领域依然发挥着重要作用。此外,ECDSA 签名在区块链应用中也被广泛使用。目前最主要的抗量子替代方案是 ML-DSA(即 Dilithium)。
就每秒签名数而言,Ed25519 与 ML-DSA-65 非常接近,且二者都远超 ECDSA:
1 ed25519 18725 signatures/s
2 ml-dsa-65 13105 signatures/s
3 ml-dsa-87 9469 signatures/s
4 ml-dsa-44 4165 signatures/s
5 ecdsa-secp256k1 3597 signatures/s
6 ecdsa-p256 3068 signatures/s
7 ecdsa-p384 897 signatures/s
因此可以说,ML-DSA 在性能上优于 ECDSA。而在每秒验证数方面,ML-DSA 通常也胜过 Ed25519:
1 ml-dsa-44 41471 verifications/s
2 ml-dsa-65 31540 verifications/s
3 ed25519 30080 verifications/s (batch)
4 ml-dsa-87 22650 verifications/s
5 ed25519 13370 verifications/s
由此可见,抗量子方法在验证性能上甚至超越了现有的椭圆曲线签名。这表明,就性能而言,ML-DSA 是一个非常出色的替代选择。
使用 OpenSSL 的更完整基准测试请见:https://asecuritysite.com/openssl/openssl3_b2
密钥交换与密钥封装
对于现有的密钥交换方法,我们使用 Diffie-Hellman 握手技术,在 Bob 和 Alice 之间派生出一个共享的对称密钥:

而在抗量子方法中,通常无法再使用 Diffie-Hellman,于是改为使用公钥加密:用公钥封装密钥,再用私钥解封装:

一种常见的密钥交换方法是 X25519,而在密钥封装方面则常用 Kyber(ML-KEM)。由于两者完成的任务不同,很难直接对比。使用 X25519 时,每秒的交换次数为:
x25519 28913 exchanges/s
对于 Kyber,我们得到:
kyber512d00 55622 encaps/s
kyber768d00 48677 encaps/s
kyber1024d00 34996 encaps/s
kyber512d00 71650 decaps/s
kyber768d00 51284 decaps/s
kyber1024d00 39167 decaps/s
kyber512d00 57248 keygen/s
kyber768d00 34073 keygen/s
kyber1024d00 20853 keygen/s
密钥派生函数(KDF)
对于用于存储密码哈希的 KDF 函数,我们希望其运行速度尽量慢,以增加暴力破解的难度。在这种情况下,bcrypt、scrypt 和 Argon2 都能提供较慢的计算速率:
bcrypt 0.013 s/ops
scrypt 0.026 s/ops
argon2 0.062 s/ops
需要注意的是,这些方法都有可调参数,能够改变生成哈希输出所需的时间,因此将这些算法放在一起横向对比并不容易。这里给出的“秒/操作”是相应参数下的最小耗时,我们可以在此基础上继续调高开销——通常通过轮换数增加哈希计算的迭代轮数来实现。
测试代码
本基准测试的完整代码请见这里:
// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const time = std.time;
const Timer = time.Timer;
const crypto = std.crypto;
const KiB = 1024;
const MiB = 1024 * KiB;
var prng = std.Random.DefaultPrng.init(0);
const random = prng.random();
const Crypto = struct {
ty: type,
name: []const u8,
};
const hashes = [_]Crypto{
// Crypto{ .ty = crypto.hash.Ascon.AsconHash256, .name = "ascon-256" },
Crypto{ .ty = crypto.hash.Md5, .name = "md5" },
Crypto{ .ty = crypto.hash.Sha1, .name = "sha1" },
Crypto{ .ty = crypto.hash.sha2.Sha256, .name = "sha256" },
Crypto{ .ty = crypto.hash.sha2.Sha512, .name = "sha512" },
Crypto{ .ty = crypto.hash.sha3.Sha3_256, .name = "sha3-256" },
Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" },
Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" },
Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
Crypto{ .ty = crypto.hash.sha3.TurboShake128(null), .name = "turboshake-128" },
Crypto{ .ty = crypto.hash.sha3.TurboShake256(null), .name = "turboshake-256" },
Crypto{ .ty = crypto.hash.sha3.KT128, .name = "kt128" },
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
};
const parallel_hashes = [_]Crypto{
Crypto{ .ty = crypto.hash.Blake3, .name = "blake3-parallel" },
Crypto{ .ty = crypto.hash.sha3.KT128, .name = "kt128-parallel" },
};
const block_size: usize = 8 * 8192;
pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
const blocks_count = bytes / block_size;
var block: [block_size]u8 = undefined;
random.bytes(&block);
var h = Hash.init(.{});
var timer = try Timer.start();
const start = timer.lap();
for (0..blocks_count) |_| {
h.update(&block);
}
var final: [Hash.digest_length]u8 = undefined;
h.final(&final);
std.mem.doNotOptimizeAway(final);
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(bytes / elapsed_s));
return throughput;
}
pub fn benchmarkHashParallel(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
const blocks_count = bytes / block_size;
var block: [block_size]u8 = undefined;
random.bytes(&block);
var h = Hash.init(.{});
var timer = try Timer.start();
const start = timer.lap();
for (0..blocks_count) |_| {
h.update(&block);
}
var final: [Hash.digest_length]u8 = undefined;
h.final(&final);
std.mem.doNotOptimizeAway(final);
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(bytes / elapsed_s));
return throughput;
}
const macs = [_]Crypto{
Crypto{ .ty = crypto.onetimeauth.Ghash, .name = "ghash" },
Crypto{ .ty = crypto.onetimeauth.Polyval, .name = "polyval" },
Crypto{ .ty = crypto.onetimeauth.Poly1305, .name = "poly1305" },
Crypto{ .ty = crypto.auth.hmac.HmacMd5, .name = "hmac-md5" },
Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" },
Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha256, .name = "hmac-sha256" },
Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha512, .name = "hmac-sha512" },
Crypto{ .ty = crypto.auth.siphash.SipHash64(2, 4), .name = "siphash-2-4" },
Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" },
Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" },
Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" },
Crypto{ .ty = crypto.auth.aegis.Aegis128X4Mac, .name = "aegis-128x4 mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis256X4Mac, .name = "aegis-256x4 mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis128X2Mac, .name = "aegis-128x2 mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis256X2Mac, .name = "aegis-256x2 mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis128LMac, .name = "aegis-128l mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis256Mac, .name = "aegis-256 mac" },
Crypto{ .ty = crypto.auth.cmac.CmacAes128, .name = "aes-cmac" },
};
pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {
var in: [512 * KiB]u8 = undefined;
random.bytes(in[0..]);
const key_length = if (Mac.key_length == 0) 32 else Mac.key_length;
var key: [key_length]u8 = undefined;
random.bytes(key[0..]);
var mac: [Mac.mac_length]u8 = undefined;
var offset: usize = 0;
var timer = try Timer.start();
const start = timer.lap();
while (offset < bytes) : (offset += in.len) {
Mac.create(mac[0..], in[0..], key[0..]);
mem.doNotOptimizeAway(&mac);
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(bytes / elapsed_s));
return throughput;
}
const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
var secret: [DhKeyExchange.shared_length]u8 = undefined;
random.bytes(secret[0..]);
var public: [DhKeyExchange.shared_length]u8 = undefined;
random.bytes(public[0..]);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < exchange_count) : (i += 1) {
const out = try DhKeyExchange.scalarmult(secret, public);
secret[0..16].* = out[0..16].*;
public[0..16].* = out[16..32].*;
mem.doNotOptimizeAway(&out);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(exchange_count / elapsed_s));
return throughput;
}
const signatures = [_]Crypto{
Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" },
Crypto{ .ty = crypto.sign.ecdsa.EcdsaP256Sha256, .name = "ecdsa-p256" },
Crypto{ .ty = crypto.sign.ecdsa.EcdsaP384Sha384, .name = "ecdsa-p384" },
Crypto{ .ty = crypto.sign.ecdsa.EcdsaSecp256k1Sha256, .name = "ecdsa-secp256k1" },
Crypto{ .ty = crypto.sign.mldsa.MLDSA44, .name = "ml-dsa-44" },
Crypto{ .ty = crypto.sign.mldsa.MLDSA65, .name = "ml-dsa-65" },
Crypto{ .ty = crypto.sign.mldsa.MLDSA87, .name = "ml-dsa-87" },
};
pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
const msg = [_]u8{0} ** 64;
const key_pair = Signature.KeyPair.generate();
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < signatures_count) : (i += 1) {
const sig = try key_pair.sign(&msg, null);
mem.doNotOptimizeAway(&sig);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(signatures_count / elapsed_s));
return throughput;
}
const signature_verifications = [_]Crypto{
Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" },
Crypto{ .ty = crypto.sign.mldsa.MLDSA44, .name = "ml-dsa-44" },
Crypto{ .ty = crypto.sign.mldsa.MLDSA65, .name = "ml-dsa-65" },
Crypto{ .ty = crypto.sign.mldsa.MLDSA87, .name = "ml-dsa-87" },
};
pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
const msg = [_]u8{0} ** 64;
const key_pair = Signature.KeyPair.generate();
const sig = try key_pair.sign(&msg, null);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < signatures_count) : (i += 1) {
try sig.verify(&msg, key_pair.public_key);
mem.doNotOptimizeAway(&sig);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(signatures_count / elapsed_s));
return throughput;
}
const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
const msg = [_]u8{0} ** 64;
const key_pair = Signature.KeyPair.generate();
const sig = try key_pair.sign(&msg, null);
var batch: [64]Signature.BatchElement = undefined;
for (&batch) |*element| {
element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = key_pair.public_key };
}
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < signatures_count) : (i += 1) {
try Signature.verifyBatch(batch.len, batch);
mem.doNotOptimizeAway(&sig);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = batch.len * @as(u64, @intFromFloat(signatures_count / elapsed_s));
return throughput;
}
const kems = [_]Crypto{
Crypto{ .ty = crypto.kem.kyber_d00.Kyber512, .name = "kyber512d00" },
Crypto{ .ty = crypto.kem.kyber_d00.Kyber768, .name = "kyber768d00" },
Crypto{ .ty = crypto.kem.kyber_d00.Kyber1024, .name = "kyber1024d00" },
};
pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
const key_pair = Kem.KeyPair.generate();
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < kems_count) : (i += 1) {
const e = key_pair.public_key.encaps(null);
mem.doNotOptimizeAway(&e);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(kems_count / elapsed_s));
return throughput;
}
pub fn benchmarkKemDecaps(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
const key_pair = Kem.KeyPair.generate();
const e = key_pair.public_key.encaps(null);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < kems_count) : (i += 1) {
const ss2 = try key_pair.secret_key.decaps(&e.ciphertext);
mem.doNotOptimizeAway(&ss2);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(kems_count / elapsed_s));
return throughput;
}
pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < kems_count) : (i += 1) {
const key_pair = Kem.KeyPair.generate();
mem.doNotOptimizeAway(&key_pair);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(kems_count / elapsed_s));
return throughput;
}
const aeads = [_]Crypto{
Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
Crypto{ .ty = crypto.aead.aegis.Aegis128X4, .name = "aegis-128x4" },
Crypto{ .ty = crypto.aead.aegis.Aegis128X2, .name = "aegis-128x2" },
Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },
Crypto{ .ty = crypto.aead.aegis.Aegis256X4, .name = "aegis-256x4" },
Crypto{ .ty = crypto.aead.aegis.Aegis256X2, .name = "aegis-256x2" },
Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },
Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },
Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" },
Crypto{ .ty = crypto.aead.aes_ocb.Aes128Ocb, .name = "aes128-ocb" },
Crypto{ .ty = crypto.aead.aes_ocb.Aes256Ocb, .name = "aes256-ocb" },
Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" },
};
pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 {
var in: [512 * KiB]u8 = undefined;
random.bytes(in[0..]);
var tag: [Aead.tag_length]u8 = undefined;
var key: [Aead.key_length]u8 = undefined;
random.bytes(key[0..]);
var nonce: [Aead.nonce_length]u8 = undefined;
random.bytes(nonce[0..]);
var offset: usize = 0;
var timer = try Timer.start();
const start = timer.lap();
while (offset < bytes) : (offset += in.len) {
Aead.encrypt(in[0..], tag[0..], in[0..], &[_]u8{}, nonce, key);
try Aead.decrypt(in[0..], in[0..], tag, &[_]u8{}, nonce, key);
}
mem.doNotOptimizeAway(&in);
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(2 * bytes / elapsed_s));
return throughput;
}
const aes = [_]Crypto{
Crypto{ .ty = crypto.core.aes.Aes128, .name = "aes128-single" },
Crypto{ .ty = crypto.core.aes.Aes256, .name = "aes256-single" },
};
pub fn benchmarkAes(comptime Aes: anytype, comptime count: comptime_int) !u64 {
var key: [Aes.key_bits / 8]u8 = undefined;
random.bytes(key[0..]);
const ctx = Aes.initEnc(key);
var in = [_]u8{0} ** 16;
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < count) : (i += 1) {
ctx.encrypt(&in, &in);
}
}
mem.doNotOptimizeAway(&in);
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(count / elapsed_s));
return throughput;
}
const aes8 = [_]Crypto{
Crypto{ .ty = crypto.core.aes.Aes128, .name = "aes128-8" },
Crypto{ .ty = crypto.core.aes.Aes256, .name = "aes256-8" },
};
pub fn benchmarkAes8(comptime Aes: anytype, comptime count: comptime_int) !u64 {
var key: [Aes.key_bits / 8]u8 = undefined;
random.bytes(key[0..]);
const ctx = Aes.initEnc(key);
var in = [_]u8{0} ** (8 * 16);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < count) : (i += 1) {
ctx.encryptWide(8, &in, &in);
}
}
mem.doNotOptimizeAway(&in);
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = @as(u64, @intFromFloat(8 * count / elapsed_s));
return throughput;
}
const CryptoPwhash = struct {
ty: type,
params: *const anyopaque,
name: []const u8,
};
const bcrypt_params = crypto.pwhash.bcrypt.Params{ .rounds_log = 8, .silently_truncate_password = true };
const pwhashes = [_]CryptoPwhash{
.{
.ty = crypto.pwhash.bcrypt,
.params = &bcrypt_params,
.name = "bcrypt",
},
.{
.ty = crypto.pwhash.scrypt,
.params = &crypto.pwhash.scrypt.Params.interactive,
.name = "scrypt",
},
.{
.ty = crypto.pwhash.argon2,
.params = &crypto.pwhash.argon2.Params.interactive_2id,
.name = "argon2",
},
};
fn benchmarkPwhash(
allocator: mem.Allocator,
comptime ty: anytype,
comptime params: *const anyopaque,
comptime count: comptime_int,
) !f64 {
const password = "testpass" ** 2;
const opts = ty.HashOptions{
.allocator = allocator,
.params = @as(*const ty.Params, @ptrCast(@alignCast(params))).*,
.encoding = .phc,
};
var buf: [256]u8 = undefined;
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < count) : (i += 1) {
_ = try ty.strHash(password, opts, &buf);
mem.doNotOptimizeAway(&buf);
}
}
const end = timer.read();
const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
const throughput = elapsed_s / count;
return throughput;
}
fn usage() void {
std.debug.print(
\\throughput_test [options]
\\
\\Options:
\\ --filter [test-name]
\\ --seed [int]
\\ --help
\\
, .{});
}
fn mode(comptime x: comptime_int) comptime_int {
return if (builtin.mode == .Debug) x / 64 else x;
}
pub fn main() !void {
const stdout = std.fs.File.stdout().deprecatedWriter();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const arena_allocator = arena.allocator();
const args = try std.process.argsAlloc(arena_allocator);
var filter: ?[]u8 = "";
var i: usize = 1;
while (i < args.len) : (i += 1) {
if (std.mem.eql(u8, args[i], "--mode")) {
try stdout.print("{}\n", .{builtin.mode});
return;
} else if (std.mem.eql(u8, args[i], "--seed")) {
i += 1;
if (i == args.len) {
usage();
std.process.exit(1);
}
const seed = try std.fmt.parseUnsigned(u32, args[i], 10);
prng.seed(seed);
} else if (std.mem.eql(u8, args[i], "--filter")) {
i += 1;
if (i == args.len) {
usage();
std.process.exit(1);
}
filter = args[i];
} else if (std.mem.eql(u8, args[i], "--help")) {
usage();
return;
} else {
usage();
std.process.exit(1);
}
}
inline for (hashes) |H| {
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
const throughput = try benchmarkHash(H.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });
}
}
inline for (parallel_hashes) |H| {
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
const throughput = try benchmarkHashParallel(H.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });
}
}
inline for (macs) |M| {
if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) {
const throughput = try benchmarkMac(M.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ M.name, throughput / (1 * MiB) });
}
}
inline for (exchanges) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKeyExchange(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} exchanges/s\n", .{ E.name, throughput });
}
}
inline for (signatures) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkSignature(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} signatures/s\n", .{ E.name, throughput });
}
}
inline for (signature_verifications) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkSignatureVerification(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} verifications/s\n", .{ E.name, throughput });
}
}
inline for (batch_signature_verifications) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkBatchSignatureVerification(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} verifications/s (batch)\n", .{ E.name, throughput });
}
}
inline for (aeads) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkAead(E.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ E.name, throughput / (1 * MiB) });
}
}
inline for (aes) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkAes(E.ty, mode(100000000));
try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
}
}
inline for (aes8) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkAes8(E.ty, mode(10000000));
try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
}
}
inline for (pwhashes) |H| {
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64));
try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });
}
}
inline for (kems) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKem(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} encaps/s\n", .{ E.name, throughput });
}
}
inline for (kems) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKemDecaps(E.ty, mode(25000));
try stdout.print("{s:>17}: {:10} decaps/s\n", .{ E.name, throughput });
}
}
inline for (kems) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKemKeyGen(E.ty, mode(25000));
try stdout.print("{s:>17}: {:10} keygen/s\n", .{ E.name, throughput });
}
}
}
结论
总而言之,并非所有密码方法在性能上都表现相同。当然,性能并非全部,密码的安全级别同样重要(甚至更为关键)。
- 原文链接: medium.com/asecuritysite...
- 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
鸿途知科网
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。