security review of dark-crystal
forked from Dev Diary 02.11.2018 / Coconut Death
I started by doing a quick search for known vulnerabilities, found some vulnerabilities in SSS described on this page and also this paper: how not to share a secret (I didn't bother to try understand all the math, just using it for hints)
- small metadata leak: length of the secret is revealed (solution: pad the secret)
- possible for participant to return the wrong share, which means the correct secret is not recovered.
- If the secret isn't very strong, like "password", it may be possible to confirm a guess just using a single share.
- If two different shares have some relation (such as one being a known multiple of the other) then the other secret can be derived from the first.
Note: the last two are partly me reading between the lines of that paper, which I quickly skimmed. I'm not staking my reputation on those being true, by the following suggestions I have will cover this just in case they are true. Better safe than sorry.
dark-crystal
is a key building block for ssb applications, and I believe, usable security applications in general. We want it to be rock solid, so that it does exactly what it says on the tin and you do not have to understand internal details in order to use it safely. This last point is extremely important and I'd be disapointed if any ssb crypto did not have this property.
Most of the above problems can be fixed using a layer of symmetric crypto. As I suggested in the forked-from-thread sharing a random key, and using that to symmetrically encrypt the actual secret. too-long secrets is currently a problem, but encrypting the secret symmetrically does not have much overhead - 16 additional bytes for the mac + the secret size, and the size of the share will always be the same.
key = randomBytes(32)
shares = createShares(key, N, threashold).map(function (share) {
//give each share a different encryption, also makes it not obvious two shares are for the
//same secret. it's okay to use the same key again if the nonce is different, so we use
//the hash of the share.
return share + secretbox(secret, key, hash(share))
})
Metadata - problem 1, this is not really a very big problem, regular ssb messages private messages don't hide the length. if you wanted could instead have the encrypted secret be between the true size and twice that size. secretbox([secret.length, secret, randomBytes(secret.length*random())
The library used left-pads the secret to be shared with ascii zeros then "1" then the secret,
so that it's possible to distinguish between padding and secret. Since it's padded with zeros,
this protects against metadata (1) but not against 3 (confirming a guess at bad secret) since it doesn't increase the entropy. It would be much better to pad with random data.
But, we should first consider 3, and 4 - problems where mathematical properties of the secret leaks through. problems like this are typical of number theory based cryptography. 3 (weak secret) is easily fixed by sharing a known strong secret (one the system has generated, not one the user has provided). This also prevents 4 (interaction between mathematically related secrets)
One problem that dark-crystal currently has, is that it does not store the current quorum size, which means when reconstructing the secret you need some way to detect whether you've got it. this is mentioned in "possible enhancements" section of the library. Using libsodium's secretbox
solves this problem, because a mac is attached to the secret, so it either returns the plaintext or fails.
Based on my current understanding of secret sharing (this morning's work) that may mitigate problem 3 (note: in a security context "mitigate" means reduce a risk or make a problem less bad, remediate means remove the problem entirely). meaning if you have a k/N secret, and 1 peer returns the wrong secret, it won't open with k/N, but if you get another honest share (now you have k+1 shares) you can just try k times, excluding each share until you successfully decrypt.
Remembering the secret has two parts - the shamir share, and the symmetric part. As long as one peer returns the correct symmetric part, you will be able to decrypt that. As long as you have k/N shamir shares. For any k, trying to combine every k-1 ... 1 possibilities will not be a scalability problem because the order of the shares does not matter.
Okay, I'm gonna look at the paper on authenticated sharing and see if that is better...