We are developing an open-source peer-to-peer app, Mapeo, designed for users with low technical experience (and no email or phone) to collect data in offline environments. We are generating their identity on the device for each project as a public-private keypair using libsodium crypto_sign_keypair
.
To support identity recovery in the case of device loss or switching to a new device, we want to use a single master key that is used as a seed for all other key pairs. Users will write down this key and manually enter it to recover their identity (similar to how Bitcoin uses BEP-39 wordlists to backup a 256-bit key).
Ideally we would use a 256-bit seed, which is what is supported by libsodium's crypto_sign_seed_keypair
and provides plenty of entropy. However, there are some design limitations:
- BEP-39 wordlists are a good solution to facilitate writing down a key and it might be manageable to write down 24 words for a 256-bit key, however only 10 languages are supported, and we are already supporting multiple languages that do not have word lists (e.g. Thai, Khmer, Vietnamese).
- Most of our users do not have access to a printer, and are on mobile devices, so their is no easy way for them to print out their key backup as a QR code for example.
- The best encoding we have come up with is base-32, since it avoids ambiguous characters. However a 256-bit key with a 32-bit CRC would need 57 characters in base-32. In our design mockups this is an overwhelming random string (even displayed in groups of 5 characters) and we are concerned it will lead to transcription errors.
- Since many users use non-latin alphabets, we may need to display the key as numbers (e.g. arabic numerals) since they are more widely understood. However a 256-bit number is 77 digits, which is hard to write down or enter without an error.
To work around these limitations, my question is whether a 128-bit seed / master key would provide enough entropy for "good enough" security. It is more manageable to write down (30 base-32 characters with a CRC-16, or 44 numerals).
I propose deriving a 256-bit seed (for seeding keypair generation and deriving subkeys) by hashing the 128-bit seed with Argon2 (see libsodium pwhash).
I realize that this gives us only 128-bits of entropy, but since it's hashed to 256-bits with Argon2 it would be costly to brute force.
I am no security expert (you may have gathered that by now!) but my question is whether this idea of a 128-bit seed / master key is enough entropy to make brute-force attacks hard / impossible. We are not looking for NSA level encryption, but enough to prevent a determined attacker (possibly state-sponsored) from being able to brute-force.
It is a compromise between usability and security, we want to keep the app easy enough to use securely, and keep the security good enough, and I'm not sure where the right balance is in terms of key length / entropy.