From 1a7d4a30000daf11be6246a2cfcf60ee7f6388f8 Mon Sep 17 00:00:00 2001 From: Larry Ellis Date: Wed, 18 Sep 2019 21:38:55 -0500 Subject: [PATCH] Diffuser bug fixes; fix truncation of leading zeros in returned binary secrets. --- Compatibility.txt | 8 +- .../Security/Cryptography/Diffuser.cs | 79 +++++++++++-------- .../Security/Cryptography/SecretCombiner.cs | 26 ++++-- .../Security/Cryptography/SecretSplitter.cs | 2 +- 4 files changed, 73 insertions(+), 42 deletions(-) diff --git a/Compatibility.txt b/Compatibility.txt index 8737b96..a9ce5b9 100644 --- a/Compatibility.txt +++ b/Compatibility.txt @@ -157,4 +157,10 @@ jeff@ubuntu:~$ gpg --s2k-digest-algo SHA256 --s2k-cipher-algo AES256 -c hello.tx then hello.txt.gpg was generated) You can then use any 2 of the above 3 shares and the generated GPG file in -SecretSplitter to decrypt it. \ No newline at end of file +SecretSplitter to decrypt it. + +************************************************************************** +Shares produced by version 0.20 of this program may not function properly +with earlier versions of this program; shares produced by earlier versions +of this program my not function properly with version 0.20 of this program. +************************************************************************** \ No newline at end of file diff --git a/SecretSplitter/Security/Cryptography/Diffuser.cs b/SecretSplitter/Security/Cryptography/Diffuser.cs index 28e0e3b..fb7fdaa 100644 --- a/SecretSplitter/Security/Cryptography/Diffuser.cs +++ b/SecretSplitter/Security/Cryptography/Diffuser.cs @@ -8,7 +8,7 @@ namespace Moserware.Security.Cryptography { /// public abstract class Diffuser { public virtual BigInteger Scramble(BigInteger input, int rawByteLength) { - return Scramble(input); + return Scramble(input, rawByteLength); } protected virtual BigInteger Scramble(BigInteger input) { @@ -16,7 +16,7 @@ protected virtual BigInteger Scramble(BigInteger input) { } public virtual BigInteger Unscramble(BigInteger input, int rawByteLength) { - return Unscramble(input); + return Unscramble(input, rawByteLength); } protected virtual BigInteger Unscramble(BigInteger input) { @@ -38,42 +38,65 @@ public class NullDiffuser : Diffuser { // my derived code under the MIT license (instead of GPL) and was generously // granted permission by him. For more license details, see License.txt included // with this code. - public class XteaDiffuser : Diffuser { + public class XteaDiffuser : Diffuser { private const int InnerRounds = 32; private const int OuterRounds = 40; private const UInt32 Delta = 0x9E3779B9; private const UInt32 DecodeInitialSum = unchecked(InnerRounds * Delta); - protected override BigInteger Scramble(BigInteger input) { - int actualByteSize; - byte[] integerBytes = GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitMsbFirstWords(input, - out actualByteSize); + public override BigInteger Scramble(BigInteger input, int rawByteLength) { - for (int i = 0; i < (OuterRounds * actualByteSize); i += 2) { - EncodeSlice(integerBytes, i, actualByteSize, EncipherBlock); + byte[] integerBytes = GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitMsbFirstWords(input); + int integerBytesNeededSize = (rawByteLength + 1) / 2 * 2; + int padLen = integerBytesNeededSize - integerBytes.Length; + if (padLen > 0) { + byte[] padded = new byte[integerBytesNeededSize]; + Array.Copy(integerBytes, padded, integerBytes.Length); + integerBytes = padded; + } + if (rawByteLength % 2 == 1) { + integerBytes[rawByteLength - 1] = integerBytes[rawByteLength]; + } + for (int i = 0; i < (OuterRounds * rawByteLength); i += 2) { + EncodeSlice(integerBytes, i, rawByteLength, EncipherBlock); + } + if (rawByteLength % 2 == 1) { + integerBytes[rawByteLength] = integerBytes[rawByteLength - 1]; + integerBytes[rawByteLength - 1] = 0; } - return GetBigIntegerFromLeastSignificantWordsFirstWith16BitMsbFirstWords(integerBytes, actualByteSize); + return GetBigIntegerFromLeastSignificantWordsFirstWith16BitMsbFirstWords(integerBytes); } - protected override BigInteger Unscramble(BigInteger input) { - int actualByteSize; - byte[] integerBytes = GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitMsbFirstWords(input, - out actualByteSize); + public override BigInteger Unscramble(BigInteger input, int rawByteLength) { - for (int i = (OuterRounds * actualByteSize) - 2; i >= 0; i -= 2) { - EncodeSlice(integerBytes, i, actualByteSize, DecipherBlock); + byte[] integerBytes = GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitMsbFirstWords(input); + int integerBytesNeededSize = (rawByteLength + 1) / 2 * 2; + int padLen = integerBytesNeededSize - integerBytes.Length; + if (padLen > 0) + { + byte[] padded = new byte[integerBytesNeededSize]; + Array.Copy(integerBytes, padded, integerBytes.Length); + integerBytes = padded; + } + if (rawByteLength % 2 == 1) { + integerBytes[rawByteLength - 1] = integerBytes[rawByteLength]; + } + for (int i = (OuterRounds * rawByteLength) - 2; i >= 0; i -= 2) { + EncodeSlice(integerBytes, i, rawByteLength, DecipherBlock); + } + if (rawByteLength % 2 == 1) { + integerBytes[rawByteLength] = integerBytes[rawByteLength - 1]; + integerBytes[rawByteLength - 1] = 0; } - return GetBigIntegerFromLeastSignificantWordsFirstWith16BitMsbFirstWords(integerBytes, actualByteSize); + return GetBigIntegerFromLeastSignificantWordsFirstWith16BitMsbFirstWords(integerBytes); } // The whole point of the diffuser is to diffuse bits, that's why we'll pick least significant words // with most significant word bits. This alone does some diffusion. - private static byte[] GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitMsbFirstWords(BigInteger input, - out int actualBytesWithoutPadding) { + private static byte[] GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitMsbFirstWords(BigInteger input) { byte[] bigEndianBytes = input.ToUnsignedBigEndianBytes(); - actualBytesWithoutPadding = bigEndianBytes.Length; bool isOddNumberOfBytes = bigEndianBytes.Length % 2 != 0; if (isOddNumberOfBytes) { // make sure it's even @@ -95,22 +118,10 @@ private static byte[] GetBigIntegerBytesWithLeastSignificantWordFirstUsing16BitM } } - bool hasExtraPaddingByte = bigEndianBytes.Length != actualBytesWithoutPadding; - - if (hasExtraPaddingByte) { - result[bigEndianBytes.Length - 2] = result[bigEndianBytes.Length - 1]; - } - return result; } - private static BigInteger GetBigIntegerFromLeastSignificantWordsFirstWith16BitMsbFirstWords(byte[] wordBytes, - int actualBytes) { - bool hasBytePadding = actualBytes % 2 == 1; - if (hasBytePadding) { - wordBytes[wordBytes.Length - 1] = wordBytes[wordBytes.Length - 2]; - wordBytes[wordBytes.Length - 2] = 0; - } + private static BigInteger GetBigIntegerFromLeastSignificantWordsFirstWith16BitMsbFirstWords(byte[] wordBytes) { byte[] bigEndianBytes = new byte[wordBytes.Length]; @@ -183,7 +194,7 @@ public override BigInteger Scramble(BigInteger input, int rawByteLength) { return _XteaDiffuser.Scramble(input, rawByteLength); } - + public override BigInteger Unscramble(BigInteger input, int rawByteLength) { if(rawByteLength < _ByteCutoff) { return input; diff --git a/SecretSplitter/Security/Cryptography/SecretCombiner.cs b/SecretSplitter/Security/Cryptography/SecretCombiner.cs index e5951eb..5ad99af 100644 --- a/SecretSplitter/Security/Cryptography/SecretCombiner.cs +++ b/SecretSplitter/Security/Cryptography/SecretCombiner.cs @@ -11,19 +11,19 @@ namespace Moserware.Security.Cryptography { // REVIEW: Keep this as static for simple API usage? public static class SecretCombiner { private static readonly Diffuser DefaultDiffuser = new SsssDiffuser(); - + public static CombinedSecret Combine(string allShares) { return Combine(Regex.Matches(allShares, SecretShare.RegexPattern).OfType().Select(m => SecretShare.Parse(m.Value)), DefaultDiffuser); } - + public static CombinedSecret Combine(IEnumerable allShares) { return Combine(allShares, DefaultDiffuser); } - + public static CombinedSecret Combine(IEnumerable allShares, Diffuser diffuser) { return Combine(allShares.Select(share => Regex.Match(share, SecretShare.RegexPattern).Value).Select(SecretShare.Parse), diffuser); } - + private static CombinedSecret Combine(IEnumerable shares, Diffuser diffuser) { var allShares = shares.ToArray(); @@ -31,6 +31,11 @@ private static CombinedSecret Combine(IEnumerable shares, Diffuser throw new SecretSplitterException("You must provide at least one secret share (piece)."); } + int expectedShareLength = allShares[0].ParsedValue.Substring(allShares[0].ParsedValue.LastIndexOf('-') + 1).Length; + if(!allShares.All(s => s.ParsedValue.Substring(s.ParsedValue.LastIndexOf('-') + 1).Length == expectedShareLength)) { + throw new SecretSplitterException("Secret shares (pieces) must be be of the same size."); + } + var expectedShareType = allShares[0].ShareType; if(!allShares.All(s => s.ShareType == expectedShareType)) { throw new SecretSplitterException("Secret shares (pieces) must be be of the same type."); @@ -43,9 +48,18 @@ private static CombinedSecret Combine(IEnumerable shares, Diffuser var secretCoefficient = LagrangeInterpolator.EvaluateAtZero(allShares.Select(s => s.Point)); var scrambledValue = secretCoefficient.PolynomialValue; - var unscrambledValue = diffuser.Unscramble(scrambledValue, scrambledValue.ToByteArray().Length); + var unscrambledValue = diffuser.Unscramble(scrambledValue, expectedShareLength / 2); var recoveredSecret = unscrambledValue.ToUnsignedBigEndianBytes(); - + + int paddingNeeded = expectedShareLength / 2 - recoveredSecret.Length; + if (paddingNeeded > 0) { + var padBytes = new byte[paddingNeeded]; + var newArray = new byte[paddingNeeded + recoveredSecret.Length]; + Array.Copy(padBytes, 0, newArray, 0, paddingNeeded); + Array.Copy(recoveredSecret, 0, newArray, paddingNeeded, recoveredSecret.Length); + recoveredSecret = newArray; + } + return new CombinedSecret(allShares[0].ShareType, recoveredSecret); } } diff --git a/SecretSplitter/Security/Cryptography/SecretSplitter.cs b/SecretSplitter/Security/Cryptography/SecretSplitter.cs index e9f1912..ec2e86e 100644 --- a/SecretSplitter/Security/Cryptography/SecretSplitter.cs +++ b/SecretSplitter/Security/Cryptography/SecretSplitter.cs @@ -149,7 +149,7 @@ public SecretSplitterException(string message) namespace Moserware.Security.Cryptography.Versioning { public static class VersionInfo { - public const string CurrentVersionString = "0.12"; + public const string CurrentVersionString = "0.20"; public static Version CurrentVersion = new Version(CurrentVersionString); } }