Shares now record their field, so reconstruction returns the secret you split

3 hours ago

SecretSharingDotNet 1.1.0 fixes reconstruction quietly returning a different secret than the one you split. The fix is one more piece of data on each share: the field it was created in. Here that's level 17, written as hex 11. The release also closes a memory wipe gap on the older .NET targets.

Ask about this presentation

Chapters

  1. 0:00Shares record their field
  2. 0:21Reconstruction guessed the field
  3. 0:44Share.SecurityLevel
  4. 1:05ShareFormat.Extended
  5. 1:30Shares you already stored
  6. 1:55Security: the half-buffer wipe
  7. 2:21Check before you upgrade
  8. 2:47Upgrade
Show transcript
  1. SecretSharingDotNet 1.1.0 fixes reconstruction quietly returning a different secret than the one you split. The fix is one more piece of data on each share: the field it was created in. Here that's level 17, written as hex 11. The release also closes a memory wipe gap on the older .NET targets.

    Slide text
    shinji-san/SecretSharingDotNet
    v1.1.0

    Shares now record their field, so reconstruction returns the secret you split

    0B
    index 11
    -
    2A
    value 42
    -
    11
    level 17, new
  2. Until now, a share held only its index and its value. So reconstruction guessed the field: the smallest Mersenne prime above the largest value it saw. When every value in your subset fell below a smaller prime, it used the wrong field. In one test, a one-byte secret came back as 0x18 instead of 0xFF, from shares that were valid and untampered.

    Slide text
    SecretSharingDotNet 1.1.0
    Fixed · issue #403

    Until 1.1.0, reconstruction guessed the field from the share values

    Split

    Runs in a field large enough for the secret

    →

    Share

    INDEX-VALUE

    with no record of that field

    →

    Reconstruct

    Smallest Mersenne prime above the largest share value

    0xFF → 0x18

    One-byte secret, a0 = 511, refit to M13 = 8191. No exception.

  3. So 1.1.0 adds a SecurityLevel property to Share. MakeShares fills it with the exponent that actually governed the split, after any automatic raise, and reconstruction uses it. If your shares go from MakeShares to reconstruction without leaving memory, you change nothing. The round trip is simply correct now.

    Slide text
    SecretSharingDotNet 1.1.0
    Added

    Every share from MakeShares now carries its SecurityLevel

    Share<TNumber>
    Index          0B
    Value          2A
    SecurityLevel  17   // int?, new

    The exponent that actually governed the split, after any auto-raise.

    null means only that the share carries no exponent.

    Shares kept in memory: no code changes
  4. Stored shares are different. By default, ToCharArray still writes the two-part legacy form, so a share written out and read back loses its field. Pass ShareFormat.Extended, and a third segment carries the level in hex. The parser reads both forms, and older versions of the library refuse the extended one rather than misread it. It becomes the default in the next breaking-change cycle.

    Slide text
    SecretSharingDotNet 1.1.0
    Added

    To keep the field on disk, write ShareFormat.Extended

    Legacy · INDEX-VALUE · still the default
    0B
    -
    2A
    Extended · INDEX-VALUE-LEVEL
    0B
    -
    2A
    -
    11
    shares.ToCharArray(uppercase: true, withPrefix: false, ShareFormat.Extended);

    The parser reads both forms. Older versions refuse the extended form rather than misread it.

  5. Shares you saved before this release carry no field. ReissueWithSecurityLevel copies them with the exponent attached, ready for the extended form. Or name the field on each call through the new reconstruction interface, and register it in your container, because the old registration doesn't cover it. Either way, pass the exponent the split really used. A one-byte secret split without an explicit level lands on 17.

    Slide text
    SecretSharingDotNet 1.1.0
    Added

    Shares you already stored: re-issue them, or name the field per call

    Re-issue, then write Extended
    legacyShares
      .ReissueWithSecurityLevel(17);
    Name the field on each call
    serviceCollection.AddTransient<
      IReconstructionWithSecurityLevelUseCase<BigInteger>,
      SecretReconstructor<BigInteger>>();
    
    useCase.Reconstruction(legacyShares, 17);

    Pass the exponent the split really used, after any auto-raise. A plausible wrong one is accepted and can reconstruct a different secret.

  6. Now the security fix. On netstandard2.0 and .NET Framework, the memory wipe used the marshalling size of a char, one byte instead of two, so it cleared only the front half of the buffer. A 10-character secret in a 16-element buffer kept its last two characters in plaintext. That buffer backs ConsolePasswordReader, ToPinnedSecure and every ToCharArray result. The modern targets were never affected.

    Slide text
    SecretSharingDotNet 1.1.0
    Security

    On netstandard2.0 and .NET Framework, the wipe left the back half of the buffer in plaintext

    A 10-character secret in a 16-element buffer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    cleared secret, still readable unused

    Affects ConsolePasswordReader, ToPinnedSecure() and every ToCharArray() result on netstandard2.0, net472, net48, net481.

    Modern targets were never affected. No API change.

  7. Before you upgrade, check how you serialize, compare and subclass. Shares is no longer marked Serializable, which breaks one narrow case: a formatter surrogate registered for Share. Add one for Shares too, or persist through ToCharArray. The level now counts in equality, so a share without one doesn't equal the same coordinates with one. And the SecretReconstructor base finalizer is deprecated, and goes away in 2.0.0.

    Slide text
    SecretSharingDotNet 1.1.0
    Removed · Changed · Deprecated

    Before upgrading, check how you serialize, compare and subclass

    Removed
    breaking, narrow
    [Serializable] is gone from Shares<TNumber>
    Hits a formatter surrogate registered for Share<TNumber>. Add one for Shares<TNumber>, or persist with ToCharArray(bool, bool, ShareFormat).
    Changed
    The level is part of Equals and GetHashCode
    A share recording no level is not equal to one recording a level, in Contains and HashSet.
    Deprecated
    The SecretReconstructor base finalizer, removed in 2.0.0
    A derived type that relies on it to reach Dispose(false) declares its own.
  8. To upgrade, add version 1.1.0 of the package. If shares come from untrusted sources, two limits apply. The recorded level isn't authenticated, so your integrity check has to cover it too. And a short share string can name an exponent whose prime takes minutes to compute, so restrict exponents through your Mersenne prime provider. Store shares in the extended form, and reconstruction returns the secret you split.

    Slide text
    shinji-san/SecretSharingDotNet
    Upgrade

    Upgrade to 1.1.0, then store shares in the extended form

    dotnet add package SecretSharingDotNet -v 1.1.0 -f <FRAMEWORK>

    The recorded level is not authenticated

    An integrity scheme has to cover the level along with the coordinates.

    A short share string can name a costly exponent

    Restrict exponents through the IMersennePrimeProvider your SecurityLevelManager is built on.

    0B
    -
    2A
    -
    11

    The field travels with the share, and reconstruction returns the secret you split.

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️