openbsd full disk encryption passphrase pbkdf rounds by duration

the openbsd installer is great. there's a couple things i do manually. one of them is full disk encryption, with a selected duration of pbkdf (password-based key derivation function) rounds, aka: iteration time, key stretching. to be performed during installation by hoping into the (S)hell before continuing with the installer.

this was performed with the openbsd 7.8 installer. see the full disk encryption section of the official faq for context.

we want to specify the passphrase pbkdf rounds by duration, something like --iter-time in luks cryptsetup under linux. afaict there's no way to either perform a command that trials a certain number of bcrypt pbkdf trial rounds (without writing your own C program to do it), nor specify rounds by the duration it would take on the system.

from bioctl(8), you can specify -r for rounds:

The number of iterations for the KDF algorithm to use when converting a passphrase into a key, in order to create a new encrypted volume or change the passphrase of an existing encrypted volume. A larger number of iterations takes more time, but offers increased resistance against passphrase guessing attacks. By default, or if rounds is specified as auto, the number of rounds will automatically be based on system performance. The minimum is 16 rounds.

looking at bioctl.c we can see the default (or when using -r auto) is however many rounds can be performed in ~1 second, by seeing how long it takes to perform 100 rounds:

/*
 * Measure this system's performance by measuring the time for 100 rounds.
 * We are aiming for something that takes around 1s.
 */
int
bcrypt_pbkdf_autorounds(void)
{
    struct timespec before, after;
    char buf[SR_CRYPTO_MAXKEYBYTES], salt[128];
    int r = 100;
    int duration;

    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &before);
    if (bcrypt_pbkdf("testpassword", strlen("testpassword"),
        salt, sizeof(salt), buf, sizeof(buf), r) != 0)
        errx(1, "bcrypt pbkdf failed");
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &after);

    duration = after.tv_sec - before.tv_sec;
    duration *= 1000000;
    duration += (after.tv_nsec - before.tv_nsec) / 1000;

    duration /= r;
    r = 1000000 / duration;

    if (r < 16)
        r = 16;

    return r;
}

so what we can do is perform encryption with -r auto and -v to see how many rounds 1 second takes, and then multiply that value with the amount of seconds we want.

ideally we could perform this on a dummy file, but the installer doesn't have sufficient free space for this, the disklabel still has to be performed on it, and the target disk is going to be wiped out by the install anyway. so it makes sense to just perform it on the target disk to measure, wipe it out, and then perform it finally with the calculated number of rounds.

prepare disk:

$ cd /dev
$ sh MAKEDEV sd0 sd1 sd2

532480 blocks is the installer's current default for uefi:

$ fdisk -gy -b 532480 sd0

$ disklabel -E sd0
Label editor (enter '?' for help at any prompt)
sd0> a a
offset: [532544]
size: [1952992591] *
FS type: [4.2BSD] RAID
sd0*> w
sd0> q
No label changes.

now you can create a crypto volume on this disk, but we'll only perform this the first time to measure how many rounds can be performed in ~1 second:

$ bioctl -c C -v -r auto -l sd0a softraid0
New passphrase:
Re-type new passphrase:
Deriving key using bcrypt PBKDF with 300 rounds...
sd2 at scsibus2 targ 1 lun 0: <OPENBSD, SR CRYPTO, 006>
sd2: 953609MB, 512 bytes/sector, 1952992063 sectors
softraid0: CRYPTO volume attached as sd2

now detach this new volume:

$ bioctl -d sd2

wipe out the softraid metadata:

$ dd if=/dev/zero of=/dev/rsd0c bs=1m count=512

(count=256 doesn't suffice, where count=512 succeeds. otherwise the a labelled RAID fstype re-appears after the previous fdisk command. unsure why...)

after this return to the "prepare disk" commands above and re-perform them, but this time specify the actual number of pbkdf rounds desired for the bioctl command. for example: i want it to take 4 seconds on this system, 4 * 300 = 1200:

$ bioctl -c C -v -r 1200 -l sd0a softraid0

finally return to the installer:

$ exit