Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ZFS_PROP_PASSPHRASE_KDF (passphrasekdf) property and argon2id13 #15682

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

nabijaczleweli
Copy link
Contributor

@nabijaczleweli nabijaczleweli commented Dec 17, 2023

Motivation and Context

Rowe told me to update and post the diff from #14762.

Description

id ZFS_PROP_PASSPHRASE_KDF name "passphrasekdf" enum of either ZFS_PASSPHRASE_KDF_PBKDF2 ("pbkdf2", PBKDF2), which is the default (and no changes there), or ZFS_PASSPHRASE_KDF_ARGON2ID13 ("argon2id13", Argon2id version 13, 18MB, single lane) in which case derive the key with Argon2id instead (but divide pbkdf2iters by 10 (maybe this factor can be reduced so it takes a similar amount of time, but no factor + the default 350000 took multiple seconds which is unacceptable; this does make the default be 35000 Argon2id rounds, so an Argon2 enjoyer should opine if this is "secure", but it takes >500ms in my test VM, so)), the default is 40000 rounds and the min is 10000 rounds.

Do we want to add a "kdfiters" alias for pbkdf2iters?

The initial methodology was "find everywhere where ZFS_PROP_KEYFORMAT is mentioned and add it there as well". Clearly a good approach.

How Has This Been Tested?

(pool/datasets created on unpatched master):

# ./zfs load-key -a
Enter passphrase for 'loop':
derive_key: kdf=PBKDF2 iters=350000
b2f63b61549fc7457631807bd79a3dfa9dc798b9f782425557578e76bbb7582f
Enter passphrase for 'loop/iters':
derive_key: kdf=PBKDF2 iters=200000
5ca66d14b61f8b6df11bb281c409b67645665e9f25c7f284d0781dcdc63739ec
2 / 2 key(s) successfully loaded
# ./zfs change-key -o passphrasekdf=argon2id loop
Enter new passphrase for 'loop':
Re-enter new passphrase for 'loop':
derive_key: kdf=ARGON2ID iters=40000
d8c6249a74fe2db5f20c82a4fd383cb0a7b63435e329f0520b102b28eb6ba97c
# ./zfs change-key -o passphrasekdf=argon2id -o pbkdf2iters=5 loop/iters
Key change error: minimum KDF iterations is 10000
Key change error: encryption failure
# ./zfs change-key -o passphrasekdf=argon2id -o pbkdf2iters=10000 loop/iters
Enter new passphrase for 'loop/iters':
Re-enter new passphrase for 'loop/iters':
derive_key: kdf=ARGON2ID iters=10000
e42b7ca3130920ed0521c2b1cbaf662d13287d58be04d6ec439e36a435987865
# ./zfs unload-key -a
2 / 2 key(s) successfully unloaded
# ./zfs load-key -a
Enter passphrase for 'loop':
derive_key: kdf=ARGON2ID iters=40000
d8c6249a74fe2db5f20c82a4fd383cb0a7b63435e329f0520b102b28eb6ba97c
Enter passphrase for 'loop/iters':
derive_key: kdf=ARGON2ID iters=10000
e42b7ca3130920ed0521c2b1cbaf662d13287d58be04d6ec439e36a435987865
2 / 2 key(s) successfully loaded
# ./zdb -e -K itersiters -dddd loop/iters 2
Unlocked encryption root: loop/iters
Dataset loop/iters [ZPL], ID 389, cr_txg 34, 98.5K, 7 objects, rootbp DVA[0]=<0:26800:1000> DVA[1]=<0:10026600:1000> [L0 DMU objset] fletcher4 uncompressed authenticated Le

    Object  lvl   iblk   dblk  dsize  dnsize  lsize   %full  type
         2    1   128K    512    512     512    512  100.00  ZFS plain file
                                               176   bonus  System attributes
        dnode flags: USED_BYTES USERUSED_ACCOUNTED USEROBJUSED_ACCOUNTED
        dnode maxblkid: 0
        uid     0
        gid     0
        atime   Mon Dec 18 00:02:31 2023
        mtime   Mon Dec 18 00:02:31 2023
        ctime   Mon Dec 18 00:02:31 2023
        crtime  Mon Dec 18 00:02:31 2023
        gen     157
        mode    100644
        size    198
        parent  34
        links   1
        pflags  840800000004



(other system: # zfs send -vw tarta-zoot/enctest@snap > enctest@snap)
(other system: full send of tarta-zoot/enctest@snap estimated size is 71.1K)
(other system: total estimated size is 71.1K)
# ./zfs recv -ev loop < enctest@snap
receiving full stream of tarta-zoot/enctest@snap into loop/enctest@snap
received 27.1K stream in 0.18 seconds (148K/sec)
$ ./zfs get passphrasekdf
NAME               PROPERTY       VALUE     SOURCE
loop               passphrasekdf  argon2id  -
loop/enctest       passphrasekdf  pbkdf2    default
loop/enctest@snap  passphrasekdf  -         -
loop/iters         passphrasekdf  argon2id  -
# ./zfs load-key -a
Enter passphrase for 'loop/enctest':
derive_key: kdf=PBKDF2 iters=350000
2ac2a4f1b3ae791aa326bdbf6fdc8e704ecbd55795bd928efaccfa3fbd99c262
1 / 1 key(s) successfully loaded

# ./zfs send -vw loop/iters@snap > iters@snap
full send of loop/iters@snap estimated size is 50.1K
total estimated size is 50.1K
TIME        SENT   SNAPSHOT loop/iters@snap
# ./zfs recv -v loop/reiters < iters@snap
receiving full stream of loop/iters@snap into loop/reiters@snap
received 18.4K stream in 0.18 seconds (104K/sec)
$ ./zfs get passphrasekdf
NAME               PROPERTY       VALUE     SOURCE
loop               passphrasekdf  argon2id  -
loop/enctest       passphrasekdf  pbkdf2    default
loop/enctest@snap  passphrasekdf  -         -
loop/iters         passphrasekdf  argon2id  -
loop/iters@snap    passphrasekdf  -         -
loop/reiters       passphrasekdf  argon2id  -
loop/reiters@snap  passphrasekdf  -         -
# ./zfs load-key -a
Enter passphrase for 'loop/reiters':
derive_key: kdf=ARGON2ID iters=10000
e42b7ca3130920ed0521c2b1cbaf662d13287d58be04d6ec439e36a435987865
1 / 1 key(s) successfully loaded

(other system: # zfs recv -ev zest < iters@snap)
(other system: receiving full stream of loop/iters@snap into zest/iters@snap)
(other system: received 18.4K stream in 1 seconds (18.4K/sec))
(other system: # zfs load-key -a)
(other system: Enter passphrase for 'zest/iters':)
(other system: Key load error: Incorrect key provided for 'zest/iters'.)
(other system: # ...)
(other system: 0 / 1 key(s) successfully loaded)

# ./zfs create -o encryption=on -o keylocation=prompt -o keyformat=passphrase loop/create
Enter new passphrase:
Re-enter new passphrase:
derive_key: kdf=PBKDF2 iters=350000
04ce5829081d13d5b525ef4c927e4f7a33de7c73501b815d89372b3c347c7c13
# ./zfs create -o encryption=on -o keylocation=prompt -o keyformat=passphrase -o passphrasekdf=argon2id loop/create2
Enter new passphrase:
Re-enter new passphrase:
derive_key: kdf=ARGON2ID iters=40000
730c5e6deacc386f2dd0aa7af12c00f80e730ad1b8b0f2eb98d154508513bd71

What doesn't work rn: zfs change-key will revert to pbkdf2. idk why. probably easy

What I don't know if works (but should): receiving encrypted datasets (every codepath in dsl_crypt that isn't trodden by change-key/load-key, really), zdb, pam_zfs_key

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Library ABI change (libzfs, libzfs_core, libnvpair, libuutil and libzfsbootenv) – only because the DEFAULT_PBKDF2_ITERATIONS and MIN_PBKDF2_ITERATIONS macros were removed
  • Documentation (a change to man pages or other documentation)

Checklist:

  • My code follows the OpenZFS code style requirements.
  • I have updated the documentation accordingly.
  • I have read the contributing document.
  • I have added tests to cover my changes.
  • I have run the ZFS Test Suite with this change applied. – CI take my hand
  • All commit messages are properly formatted and contain Signed-off-by.

Signed-off-by: Ahelenia Ziemiańska <[email protected]>
@nabijaczleweli
Copy link
Contributor Author

openzfs/zfs-buildbot#280

@oromenahar
Copy link
Contributor

checkout #14836 as well

@oromenahar
Copy link
Contributor

oromenahar commented Dec 20, 2023

I read through your PR. I think more parameters for argon2 need to configurable. Argon2 provides the options for this. I think 18MB with one CPU is not enough compute resource in my opinion. But I guess for other people it's enough or even to much, for that reason it should be configurable in my opinion.

But all in all it does not look that bad. You had the same Ideas like me. And I have the same points I don't like on my code and Ideas. Like what about adding a third KDF and more.

BTW: I'm working on a multi key slot variant for ZFS and wanna stop the PR #14836 if I finish it. But don't know when I can finish this work. I have the multislot already finished, but the user space tools don't handle it at the current point of time. Maybe wanna contact @tcaputi for some more information about the crypto key handling. I understand the most things, but not shure about everything.

behlendorf pushed a commit to openzfs/zfs-buildbot that referenced this pull request Dec 22, 2023
@nabijaczleweli
Copy link
Contributor Author

This could be done quite easily by packing more parameters into pbkdf2iters.

static const uint32_t zfs_passphrase_kdf_default_iterations[
ZFS_PASSPHRASE_KDF_KDFS] = {350000, 40000};
static const uint32_t zfs_passphrase_kdf_min_iterations[
ZFS_PASSPHRASE_KDF_KDFS] = {100000, 10000};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, @nabijaczleweli. I agree with @oromenahar that making the memory and parallelism parameters configurable is really important. 18 KiB seems like way too little memory. This minimum number of iterations seems way too high for Argon2; I think iteration counts as low as 1 are perfectly reasonable.

Here's are some examples from my laptop (from 2018, using https://packages.debian.org/bookworm/argon2).

t=40000 m=16KiB takes 0.3s

$ echo pw | time argon2 saltsalt -id -t 40000 -m 4 -p 1 -v 13
Type:		Argon2id
Iterations:	40000
Memory:		16 KiB
Parallelism:	1
Hash:		908df7fe7b24164c854546dadce8a034fd0aa9ad04e6fed6bfb27c074757ca84
Encoded:	$argon2id$v=19$m=16,t=40000,p=1$c2FsdHNhbHQ$kI33/nskFkyFRUba3OigNP0Kqa0E5v7Wv7J8B0dXyoQ
0.349 seconds
Verification ok
argon2 saltsalt -id -t 40000 -m 4 -p 1 -v 13  0.69s user 0.00s system 99% cpu 0.691 total

t=100 m=16MiB takes 1.2s

$ echo pw | time argon2 saltsalt -id -t 100 -m 14 -p 1 -v 13 
Type:		Argon2id
Iterations:	100
Memory:		16384 KiB
Parallelism:	1
Hash:		7e4127adbbe1c9ac4ec0a48f387c7c3755639d18c4f01da8b8f8c15baeee4dbc
Encoded:	$argon2id$v=19$m=16384,t=100,p=1$c2FsdHNhbHQ$fkEnrbvhyaxOwKSPOHx8N1VjnRjE8B2ouPjBW67uTbw
1.185 seconds
Verification ok
argon2 saltsalt -id -t 100 -m 14 -p 1 -v 13  2.32s user 0.02s system 99% cpu 2.349 total

t=1 m=2GiB takes 2.5s

$ echo pw | time argon2 saltsalt -id -t 1 -m 21 -p 1 -v 13
Type:		Argon2id
Iterations:	1
Memory:		2097152 KiB
Parallelism:	1
Hash:		015f25635ba58191504e00830b98d57917f67e5d15a35842f108ffa1397b2b64
Encoded:	$argon2id$v=19$m=2097152,t=1,p=1$c2FsdHNhbHQ$AV8lY1ulgZFQTgCDC5jVeRf2fl0Vo1hC8Qj/oTl7K2Q
2.548 seconds
Verification ok
argon2 saltsalt -id -t 1 -m 21 -p 1 -v 13  3.76s user 1.29s system 99% cpu 5.047 total

This last configuration with t=1 may be best (and I should crank up parallelism if I can).

The Argon2 paper says:

Number T of passes over the memory. The running time depends linearly on this parameter. We expect
that the user chooses this number according to the time constraints on the application. Again, there is
no ”insecure value” for T .

The recommendations here currently suggest single-digit numbers of iterations: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id

m=47104 (46 MiB), t=1, p=1
...
m=7168 (7 MiB), t=5, p=1

Cryptsetup appears to use a minimum iteration count of 4: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/lib/crypto_backend/pbkdf_check.c#L56

It's probably also a good idea to figure out the maximum usable values and make sure that parameters coming into ZFS aren't silently truncated/wrapped.

Copy link
Contributor Author

@nabijaczleweli nabijaczleweli Feb 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me these take 0.894/2.499/4.630 (bookworm on E5645s). Updated to default to 0x000100110001 (t=1, m=17, p=1) with minimum 0x0001000C0001 (t=1, m=12, p=1).

@nabijaczleweli
Copy link
Contributor Author

Updated, now the following is possible:

# ./zpool create -O encryption=on -O passphrasekdf=argon2id -O pbkdf2iters=$((0x000100120001)) loop2 /dev/loop0  -f -O keyformat=passphrase

# ./zfs load-key  -a
Enter passphrase for 'loop2':
derive_key: kdf=1 iters=4296146945
argon2: t=1 m=18 p=1
3493dc0b9a5036bfd2c3fcd63887ba093f3eb5a95ea76fa303bb40b09c0df73e
1 / 1 key(s) successfully loaded

# ./zfs change-key -o pbkdf2iters=$((0x0002001500021)) loop2
2001500021
Enter new passphrase for 'loop2':
Re-enter new passphrase for 'loop2':
derive_key: kdf=1 iters=137460973601
argon2: t=32 m=336 p=33
d56b152a00ebe11c1687495a726aebf39c594c6c84d795b87a502e53d905fdcc

# ./zfs change-key -o kdfparams=$((0x0002001300021)) loop2
Enter new passphrase for 'loop2':
Re-enter new passphrase for 'loop2':
derive_key: kdf=1 iters=137458876449
argon2: t=32 m=304 p=33
23e2ade2b600d6bd226e0a9f9caf03395b894af0cbbace48b80d05e5a9cdbfbf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants