0

I bought a new Yubikey, and am currently setting it up to use on my desktop PC. Previously the PC was secured with password only, and I'd like to use the Yubikey as an alternative: instead of using the password, I could instead use the Yubikey plus a short PIN code, but the password must remain as a backup mode.

There are multiple ways of doing this on Linux using PAM configuration. However, a problem arises when I want to apply this technique to my full-disk encryption: I'm using ZFS encryption, which seems to only support one keyslot, so both human entry and the Yubikey process need to produce the same password. Also, I don't want to change and lelearn this password.

My FDE password is a Diceware password, and I understand that the security of this scheme isn't significantly compromised if an adversary knows this fact.

First, I reduce my password to a short bit sequence. For example, the first 13 (=log_2(6^5)) bits represent the first word in the password, the next 13 bits indicate the second, and so on. Alternatively, this representation can have a bit more ambiguity: "how many words are used" could also be encoded in it. I write a program that takes in a sequence of bytes and produces the Diceware password that was encoded by it.

Next, I use the Yubikey's challenge-response mode. This mode allows programming a secret value s into the key, and then sending it a challenge, hash(c), and receiving an r=HMAC(hash(c), s), which is 20 bytes or 160 bits long. I take the distilled password from the previous step, p, and compute q=p XOR r, which is then saved in plaintext on disk.

When I want to decrypt my disk, I can either provide my password (or equivalently the p value) directly, or I can use the Yubikey. In the latter case, I input a PINcode or some short password as c, then the Yubikey is used to produce r, and finally this is XOR'd with the plaintext q to generate the p used to decrypt the disk. If either the PINcode or the Yubikey secret is wrong, then the generated p does not produce the correct password and the decryption fails. This whole process runs with a program in the unencrypted pre-boot environment.

What are the security implications of this scheme? My first thought is that maybe the value of q could be leaking something about the value of p, but if the bits of p that aren't used are set to random values, and the r-values produced by a Yubikey are unpredictable (which they seem to be), then the combination of the two seems just as random.

3
  • so to clarify, once you solve p at time of "preboot unlock" you then use the bits from p to index the diceware words so as to compile your existing diceware password, which you then feed to the "unlock zfs" call?
    – brynk
    Commented Apr 12, 2023 at 6:19
  • @brynk Yes, that's correct. Because of this, it seems that p is just like a compressed version of the password. I did try using p to seed a CSPRNG and then use the output of that to compile the password, but that didn't really work as the task amounts to mining a bitcoin with about 80 bits of difficulty.
    – Danya02
    Commented Apr 12, 2023 at 12:15
  • I use similar custom formulas like this, but only when combined with 2fa because you know they say don't try to roll your own cryptography because the cypherpunks already did the hard work for you and you're likely to mess it up and have a false sense of security by obscurity. Interesting though, I have been trying to figure out how to securely use xor in limited execution environments where its not feasible to use AES, such as with smart contracts written in Solidity and I like what you got going on here. Not sure i'd trust it for FDE but very coool.
    – Chev_603
    Commented 2 days ago

1 Answer 1

1

Firstly, I think it's worth saying this scheme would be strengthened if you created a secure random keyfile for fde instead of a diceware phrase. Then you can encrypt this file two (or n) times, deriving a key for each using different input key-material to some password-kdf, in a manner described subsequently, eg: one from your diceware phrase, one from the output of the yubi-key, a backup one, etc...

In the fullness of time it may become possible to have multiple keyslots for ZFS JWaldrep, TCaputi, and friends, 2019-22 but basically encrypting multiple key-copies is the solution discussed in that open issue.

On to your question - my gut feeling is that condensing p into respective indices, ie. cramping all words left-to-right in their respective bytes index, reduces entropy which could lead to problem. For example, a 4-word diceware phrase, with 2-bytes per index, some padding applied from the 5th word on, for 20-bytes in total (ie. the width of HMAC_SHA1):

#word    1      2      3      4      5 ...     10
p = 0x1e60 0x1e59 0x1e58 0x1e57 0xf00f ... 0xf00f

Depending on the cipher in use, even if you pad with random values, and possibly setting a high bit to signal "not used", these extra padding bytes are trailing the actual secret and don't add anything if you're using XOR. If an oracle was discovered that allowed an adversary to avoid the use of the fde key-derivation function, then your security now rests entirely on the entropy in the condensed value p (eg. say 65- or 78-bit, for a four- or five- word diceware phrase respectively).

At a minimum, it's better to "spread out" into at least the 20 bytes (10 dice-words) of r (from HMAC_SHA1), and, even better out to a full 32-bytes.

You could then place the dice-word indices from right to left (encrypting from left to right), or, any fixed order really, eg. by putting dice-words 1 and 2 at either end of p giving something like:

#word  02  15  04  13  06  11  08  09  10  07  12  05  14  03  16  01

I reckon that p should now be encrypted using a 256-bit cipher, eg. ChaCha20Poly1305 or AES-256-GCM, rather than XOR. The reason why is that the earlier bytes (blocks) affect the later bytes (blocks) in stream or block ciphers that act on the 20- or 32-bytes of p.

A suitable key for encryption must also be derived from the yubi challenge response, using some password-kdf (eg. scrypt or argon2 preferred, or bcrypt or pbkdf2 as other options). For example:

kdf_fixed = secure_random(x)
ciph_nonce = secure_random(y)
ciph_key = password_kdf( r , kdf_fixed , options=... )
ciph_and_tag = aead_encrypt( p , ciph_key , ciph_nonce , aad=... )
q = kdf_fixed + ciph_nonce + ciph_and_tag

Your goals are still met by having q ready to be decrypted with yubi-key present, or otherwise by being able to type the diceware phrase in directly using the full-disc encryption key derivation PHeckel'17 (which is by default pbkdf2 with 100k iters). You might also consider increasing the fde keyslot number of kdf iterations, if you haven't done so already.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .