This is part of my master thesis that I'm about to finish currently.
The algorithm
I am accepting key material from two sources:
- From a TPM, or Hardware-backed Keystore. There we need the whole 32 Bytes (256 bits) for maximum security.
- From a Paper Key, or user input. Assuming we can get ~6 bits of randomness we need at least 42 characters. Going with the more pessimistic ~4.5 bits per character we need at least 56 characters to reach 256 bits strength.
The next step is to calculate candidate values per Slot
.
The Slot
information is stored on disk and readable for an attacker.
Using the Potential Slot Key
and the slot specific Salt
I generate a 512 bits
key stream for the following components:
- The
Encrypted Master Key
from the first 256 bits. - The
Encrypted Nonce
from the next 128 bits, where I discard the first 32 bits because I need the lower 96 bits only. - The
Check Value
from the last 128 bits.
If the Check Value
matches the one stored on disk I know that the user input is valid and I can decrypt the super block with chacha20-poly1305.
Why so complicated?
The idea is that a user can synchronise the encrypted data over multiple devices and access it with convenience of the platforms hardware security.
Even without synchronisation firmware upgrade can erase the contents of a TPM and we need the Paper Key
as a means to open the vault and include a new key with the current TPM.
Open Question
One risk is that an attacker is able to use the Check Value
generated by Argon2 to reverse back to the Encrypted Nonce
or even Encrypted Master Key
.
Related Work
I am inspired by the design of LUKS1 and LUKS2.
Future Work
The 32 bits that are not used in the Slot.Decryption Nonce XOR
could be used for error detection.
This would not stop an attacker but allow the user to detect bit rot.