grempe / secrets.js

Secret sharing for javascript
MIT License
288 stars 73 forks source link

Shares are larger than original data #7

Closed gkaemmer closed 5 years ago

gkaemmer commented 6 years ago

Hi, really enjoying using this library!

The readme states that the shares are "each the same size in bits as the original secret". This doesn't appear to be true.

console.log(secrets.random(256).length, secrets.random(512).length) // => 64, 128
console.log(
  secrets.share(secrets.random(256), 10, 5)[0].length,
  secrets.share(secrets.random(512), 10, 5)[0].length
) // => 99, 163

I guess that the actual "value" of the share is the final X bytes where X is the secret size, but you still need all those extra bytes to actually restore the secret, right?

bingtimren commented 5 years ago

I guess the answer lies in the "note on security" section of the document: https://github.com/grempe/secrets.js#note-on-security

However, because the size of each share is the same as the size of the secret (when using binary Galois fields, as secrets.js does), in practice it does leak some information, namely the size of the secret. Therefore, if you will be using secrets.js to share short password strings (which can be brute-forced much more easily than longer ones), it would be wise to zero-pad them so that the shares do not leak information about the size of the secret. With this in mind, secrets.js will zero-pad in multiples of 128 bits by default which slightly increases the share size for small secrets in the name of added security. You can increase or decrease this padding manually by passing the padLength argument to secrets.share().

amper5and commented 5 years ago

The meat of the readme has not been updated since my initial publishing. grempe has since changed the share format as well as other internals.

EDIT: But...that statement conveys the general principle of Shamir's Secret Sharing Scheme, which in theory maintains the data-length. It's just that different implementations of SSSS handle numbers differently and have different share formats which leads to these discrepancies.

However, padding shouldn't explain the difference here since both of gkaemmer 's examples are multiples of 128 bits anyway.

grempe commented 5 years ago

I'm finally getting around to looking at this issue. Sorry for the delay. This topic is a little confusing (and after all this time I had to go back and try to figure out how it was working myself).

Part of the confusion stems I believe from bit and byte conversions as well as counting metadata bytes and not just the actual data being represented.

The padding always works in bits, padding the binary representation of the original data as needed. It will not simply add the amount of padding specified, but will instead pad the share data to a multiple of that padding value.

If we break down one of the examples above we see:

secrets.share(secrets.random(256), 10, 5)[0].length

// The random secret is 256 bits == 32 bytes == 64 hex char
// The share length is 99 hex chars (composed of 3 chars of header and 96 chars of share data)
// 96 hex == 48 bytes == 384 bits (3x multiple of 128 bits pad) : 128 bits larger than original bit length of 256

So as to not leak the exact length of 256 bits, and extra pad was added of 128 bits (the default).

I've added a little workbook example to help demonstrate.

https://github.com/grempe/secrets.js/blob/master/examples/padding.js

I think this is working as intended, but if you see any issues please do let me know or provide a pull request. I hope this helps (after all this time). I think others have been confused by this as well though so the examples are helpful.

Cheers.