DEV Community

Justin G
Justin G

Posted on

Hashing across architectures

In a previous post, I mentioned how just because you develop on a brand new or nearly new laptop doesn't mean that's where your code is going to run, especially if it's deployed to the cloud.

Compute has come a long way from the single core days. Pretty sure I have more cpu cores sitting on my desk between my laptop and desktop than all previous computers I've owned since college combined.

But not everyone has access to multiple machines to test on, or even different architectures. I won't go into detail on the rabbit hole I went down recently, but it involved looking into how different languages across different architectures performed hashing a password amongst other things.

TL;DR

Salt Length: 16 bytes
Secret Length: 64 bytes
PBKDF2 with SHA256 algorithm
500,000 iterations

Rust demo apps built in release mode.
Odin demo apps built with -o:speed
Zig demo apps built with -D ReleaseSafe

Macbook Pro M4 Pro, MacOS 15.7.5
Rust 1.97.1 (aws-lc-rs crate): 66ms
Rust 1.97.1 (ring crate): 103ms
Odin (dev-2026-08): 98ms
Zig (0.16): 103ms

AMD Ryzen 9 9950X, CachyOS, Linux Kernel 7.1.8
Rust 1.97.1 (aws-lc-rs crate): 93ms
Rust 1.97.1 (ring crate): 140ms
Odin (dev-2026-08): 123ms
Zig (0.16): 171ms

(Eyeball averages across a number of runs. This isn't meant to be scientific.)

Additional Words

What we can see from above is that performance for hashing a password varies between language choice and architecture, with a clear advantage for the Mac M series chips, which a lot of companies develop on but then run their applications on x86-64 machines.

Even with a language like Rust, choosing one crate, aws-lc-rs, over another, ring, can lead to significant performance improvements, or even allowing to choose more iterations for stronger password hashing.

Now I understand not everyone has access to multiple personal machines, or maybe even the latest hardware. (I worked for years on a M1, and while I don't have the exact numbers available, pretty sure it still beats my AMD Ryzen 9 9950x for the task above.) It just means that you can't assume that the feature you developed will perform the same on different hardware, especially if the hardware is a few years old or older. It's always best to test against the hardware your production code will run on especially if it's an expensive task that could potentially block on your server.

Example code below

Rust (aws-lc-rs)

use aws_lc_rs::pbkdf2;
use aws_lc_rs::rand::{Random, SystemRandom, generate};
use std::num::NonZero;

fn main() {
    let r = SystemRandom::new();
    let salt: Random<[u8; 16]> = generate(&r).unwrap();

    let iterations: NonZero<u32> = NonZero::new(500_000).unwrap();

    let password = "SuperSecretPassword";
    let mut secret = vec![0u8; 64];

    let salt = salt.expose();
    let time = std::time::Instant::now();

    pbkdf2::derive(
        pbkdf2::PBKDF2_HMAC_SHA256,
        iterations,
        &salt,
        password.as_bytes(),
        &mut secret,
    );

    println!("Elapsed: {:?}", time.elapsed());
}
Enter fullscreen mode Exit fullscreen mode

Rust (ring)

use ring::pbkdf2;
use ring::rand::{Random, SystemRandom, generate};
use std::num::NonZero;

fn main() {
    let r = SystemRandom::new();
    let salt: Random<[u8; 16]> = generate(&r).unwrap();

    let iterations: NonZero<u32> = NonZero::new(500_000).unwrap();

    let password = "SuperSecretPassword";
    let mut secret = vec![0u8; 64];

    let salt = salt.expose();
    let time = std::time::Instant::now();

    pbkdf2::derive(
        pbkdf2::PBKDF2_HMAC_SHA256,
        iterations,
        &salt,
        password.as_bytes(),
        &mut secret,
    );

    println!("Elapsed: {:?}", time.elapsed());
}
Enter fullscreen mode Exit fullscreen mode

Odin

package main

import "core:crypto"
import "core:crypto/pbkdf2"
import "core:fmt"
import "core:time"

main :: proc() {
    salt: [16]u8
    dk: [64]u8
    password := "SuperSecretPassword"

    crypto.rand_bytes(salt[:])

    start := time.now()
    pbkdf2.derive(.SHA256, transmute([]byte)(password), salt[:], 500_000, dk[:])
    elapsed := time.since(start)

    ms := time.duration_milliseconds(elapsed)

    fmt.printfln("Time elapsed is: {}ms", ms)
}
Enter fullscreen mode Exit fullscreen mode

Zig

const std = @import("std");
const Io = std.Io;
const print = std.debug.print;
const time = std.time;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    var dk: [64]u8 = undefined;
    var salt: [16]u8 = undefined;

    try Io.randomSecure(io, &salt);

    const start = Io.Clock.awake.now(io);
    try std.crypto.pwhash.pbkdf2(&dk, "SuperSecretPassword", &salt, 500_000, std.crypto.auth.hmac.sha2.HmacSha256);

    const elapsed2: f64 = @floatFromInt(start.untilNow(io, .awake).nanoseconds);
    print("Time elapsed is: {d:.3}ms\n", .{
        elapsed2 / time.ns_per_ms,
    });
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)