区块链 区块链技术 比特币公众号手机端

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

liumuhui 4小时前 阅读数 1 #区块链

在密码学中速度何时重要:什么是最快的(和最慢的)?

有时候,你在密码学中需要极快的操作,比如生成签名、密文或哈希值;而另一些时候,你却希望过程慢下来,例如从秘密中派生加密密钥,或存储密码的哈希版本。

因此,我们使用 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明

本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门