Resisting the rubber hose

The User will select N passphrases which refer to various layers of a filesystem. Disclosing one or more of the passphrases will reveal those layers, but not even the existance of any others. Writing files without having all passphrases can destroy hidden files.

Each layer of the filesystem represents an increased confidentiality.

From each passphrase is derived a key by repeatedly chain-hashing the key, for example using Argon2. The exact number of rounds is not known to The User, and will be discussed shortly.

From each key, a permutation index is derived mapping the indirect block number I to the physical block P[I;k]. This function could be instructed as a uniform hash function H[I,k].

Each physical block is encrypted using the key, perhaps using GCM (using the indirect block number as an index). It is assumed that successful decryption can be verified.

Each layer of the filesystem is divided into virtual blocks, counting to the same number as physical blocks. The virtual block at index J may exist at virtual block H[J] or H[J]+1 or H[J]+2 or any other address that will be discovered using linear probing.

Now, when The User is setting up the disk, they will choose their first passphrase, and upon entering it, will be prompted to wait "some amount of time". This time is important, because the system is performing key derivation rounds until The User is tired of waiting. In this way, the exact number of rounds is unknown, making brute-forcing the passphrase impractical.

The User can select additional passphrases. Once all of the keys have been constructed, the root virtual block for each layer can be computed and written to the disk, each to their own key, and each at a different physical address by performing the previously described permutations and probing.

When The User is ready to log out of their filesystem, the keys are simply discarded from memory.

On return, The User issues a login and enters their first passphrase. The system will perform key derivation rounds until it can successfully retrieve virtual block 1. Note how the P[I;] function combined with probing can guarantee virtual block 1 will always be at a different physical address for each layer. There is no ability for the system to detect an invalid passphrase, but if The User finds themselves waiting longer than expected, they can abort and try a different passphrase.

The User can then login with the further passphrases.

With all keys known to the system, there is no risk of collisions: On selecting a block, each key is attempted for decryption, and if one of the keys finds success, then the block is occupied and the next linear index will be chosen. The P[I;] construction makes it unlikely that this address will be occupied as well, but further probes may be required. Eventually a block will be reached that cannot be decrypted using any known key, and that block is considered free.

If not all of the keys are known, then the allocation of a block could destroy data in the lower layers.

When we deallocate a block, we do not write it with random data, but we encrypt a delete-marker using the highest layer key.

The reason for this is so we can resist some data destruction: Each virtual block can be written M times to successively probed addresses. The plaintext should contain the copy-number and the virtual block number, so when the disk is nearly full, some copies can be discovered and reused to make room.

Upon this mapping of a plaintext virtual block to an encrypted physical representation, we can use the techniques of existing filesystems.