From fc1a15f860598d416749971b5cc3f18392e94ed8 Mon Sep 17 00:00:00 2001 From: BashhScriptKid Date: Mon, 9 Dec 2024 23:11:28 +0800 Subject: [PATCH] Add EncryptionMethod.One in SafeEncryptionProvider.cs --- FastRandom.cs | 287 ++++++++++++++++++ Program.cs | 21 +- SafeEncryptionProvider.cs | 144 +++++---- .../net8.0/fStreamDecryptor.AssemblyInfo.cs | 2 +- 4 files changed, 383 insertions(+), 71 deletions(-) create mode 100644 FastRandom.cs diff --git a/FastRandom.cs b/FastRandom.cs new file mode 100644 index 00000000..bb0c29a1 --- /dev/null +++ b/FastRandom.cs @@ -0,0 +1,287 @@ +using System; + +namespace StreamFormatDecryptor +{ + public class FastRandom + { + // The +1 ensures NextDouble doesn't generate 1.0 + private const double REAL_UNIT_INT = 1.0 / (int.MaxValue + 1.0); + private const double REAL_UNIT_UINT = 1.0 / (uint.MaxValue + 1.0); + private const uint W = 273326509; + private const uint Y = 842502087, Z = 3579807591; + private uint w; + + private uint x, y, z; + + #region Constructors + + /// + /// Initialises a new instance using time dependent seed. + /// + public FastRandom() + { + // Initialise using the system tick count. + Reinitialise(Environment.TickCount); + } + + /// + /// Initialises a new instance using an int value as seed. + /// This constructor signature is provided to maintain compatibility with + /// System.Random + /// + public FastRandom(int seed) + { + Reinitialise(seed); + } + + #endregion + + #region Public Methods [Reinitialisation] + + /// + /// Reinitialises using an int value as a seed. + /// + /// + public void Reinitialise(int seed) + { + // The only stipulation stated for the xorshift RNG is that at least one of + // the seeds x,y,z,w is non-zero. We fulfill that requirement by only allowing + // resetting of the x seed + x = (uint)seed; + y = Y; + z = Z; + w = W; + bitBufferIdx = 32; + } + + #endregion + + #region Public Methods [Next* methods] + + private uint bitBuffer; + private int bitBufferIdx = 32; + + /// + /// Generates a uint. Values returned are over the full range of a uint, + /// uint.MinValue to uint.MaxValue, including the min and max values. + /// + /// + public uint NextUInt() + { + uint t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))); + } + + /// + /// Generates a random int. Values returned are over the range 0 to int.MaxValue-1. + /// MaxValue is not generated to remain functionally equivalent to System.Random.Next(). + /// If you require an int from the full range, including negative values then call + /// NextUint() and cast the value to an int. + /// + /// + public int Next() + { + uint t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + return (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))); + } + + /// + /// Generates a random int over the range 0 to upperBound-1, and not including upperBound. + /// + /// + /// + public int Next(int upperBound) + { + if (upperBound < 0) + throw new ArgumentOutOfRangeException("upperBound", upperBound, "upperBound must be >=0"); + + uint t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + + // The explicit int cast before the first multiplication gives better performance. + // See comments in NextDouble. + return (int)((REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * upperBound); + } + + /// + /// Generates a random int over the range lowerBound to upperBound-1, and not including upperBound. + /// upperBound must be >= lowerBound. lowerBound may be negative. + /// + /// + /// + /// + public int Next(int lowerBound, int upperBound) + { + if (lowerBound > upperBound) + throw new ArgumentOutOfRangeException("upperBound", upperBound, "upperBound must be >=lowerBound"); + + uint t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + + // The explicit int cast before the first multiplication gives better performance. + // See comments in NextDouble. + int range = upperBound - lowerBound; + if (range < 0) + { + // If range is <0 then an overflow has occured and must resort to using long integer arithmetic instead (slower). + // We also must use all 32 bits of precision, instead of the normal 31, which again is slower. + return lowerBound + + (int)((REAL_UNIT_UINT * (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))) * (upperBound - (long)lowerBound)); + } + + // 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int anf gain + // a little more performance. + return lowerBound + + (int)((REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * range); + } + + /// + /// Generates a random double. Values returned are from 0.0 up to but not including 1.0. + /// + /// + public double NextDouble() + { + uint t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + return (REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))); + } + + /// + /// Fills the provided byte array with random bytes. + /// Increased performance is achieved by dividing and packaging bits directly from the + /// random number generator and storing them in 4 byte 'chunks'. + /// + /// + public void NextBytes(byte[] buffer) + { + // Fill up the bulk of the buffer in chunks of 4 bytes at a time. + uint x = this.x, y = this.y, z = this.z, w = this.w; + int i = 0; + uint t; + for (; i < buffer.Length - 3;) + { + // Generate 4 bytes. + t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + buffer[i++] = (byte)(w & 0x000000FF); + buffer[i++] = (byte)((w & 0x0000FF00) >> 8); + buffer[i++] = (byte)((w & 0x00FF0000) >> 16); + buffer[i++] = (byte)((w & 0xFF000000) >> 24); + } + + // Fill up any remaining bytes in the buffer. + if (i < buffer.Length) + { + // Generate 4 bytes. + t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + buffer[i++] = (byte)(w & 0x000000FF); + if (i < buffer.Length) + { + buffer[i++] = (byte)((w & 0x0000FF00) >> 8); + if (i < buffer.Length) + { + buffer[i++] = (byte)((w & 0x00FF0000) >> 16); + if (i < buffer.Length) + { + buffer[i] = (byte)((w & 0xFF000000) >> 24); + } + } + } + } + + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + +// /// +// /// A version of NextBytes that uses a pointer to set 4 bytes of the byte buffer in one operation +// /// thus providing a nice speedup. Note that this requires the unsafe compilation flag to be specified +// /// and so is commented out by default. +// /// +// /// +// public unsafe void NextBytesUnsafe(byte[] buffer) +// { +// if(buffer.Length % 4 != 0) +// throw new ArgumentException("Buffer length must be divisible by 4", "buffer"); +// +// uint x=this.x, y=this.y, z=this.z, w=this.w; +// uint t; +// +// fixed(byte* pByte0 = buffer) +// { +// uint* pDWord = (uint*)pByte0; +// for(int i = 0, len = buffer.Length>>2; i < len; i++) +// { +// t=(x^(x<<11)); +// x=y; y=z; z=w; +// *pDWord++ = w = (w^(w>>19))^(t^(t>>8)); +// } +// } +// +// this.x=x; this.y=y; this.z=z; this.w=w; +// } + + // Buffer 32 bits in bitBuffer, return 1 at a time, keep track of how many have been returned + // with bitBufferIdx. + + /// + /// Generates random bool. + /// Increased performance is achieved by buffering 32 random bits for + /// future calls. Thus the random number generator is only invoked once + /// in every 32 calls. + /// + /// + public bool NextBool() + { + if (bitBufferIdx == 32) + { + // Generate 32 more bits. + uint t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + bitBuffer = w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + // Reset the idx that tells us which bit to read next. + bitBufferIdx = 1; + return (bitBuffer & 0x1) == 1; + } + + bitBufferIdx++; + return ((bitBuffer >>= 1) & 0x1) == 1; + } + + #endregion + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 9e49b3c8..2788f883 100644 --- a/Program.cs +++ b/Program.cs @@ -29,6 +29,8 @@ public class Program private static Dictionary fFiles; public static FileStream fileStream; + + private static readonly byte[] knownPlain = new byte[64]; public static void ContinueOnPress() { @@ -183,6 +185,7 @@ public static void Main(string[] args) Console.WriteLine($"Decryption key: {keyOut}"); string key = keyOut.ToLower().Replace("-", string.Empty); + new FastRandom(1990).NextBytes(knownPlain); //DecryptFile(fileStream, fEnum.fDecryptMode.OSUM); // Put a reader on decrypted fileStream @@ -200,22 +203,32 @@ public static void Main(string[] args) // exists only to keep BinaryReader position moving { int countRedundant = br.ReadInt32(); + Console.WriteLine($"Read {countRedundant} raw entries:"); for (int i = 0; i < countRedundant; i++) { - br.ReadInt16(); - br.ReadString(); + Console.WriteLine($"{br.ReadInt16()}: {br.ReadString()}"); } int mapCount = br.ReadInt32(); - for (int i = 0; i < mapCount; i++) {br.ReadString(); br.ReadInt32();} + for (int i = 0; i < mapCount; i++) + { + Console.WriteLine($"{br.ReadString()}: {br.ReadInt32()}"); + } + - doPostProcessing(br); + doPostProcessing(br); } } static void doPostProcessing(BinaryReader br) { + //Known plain comparison + FastEncryptorStream comparer = new FastEncryptorStream(br.BaseStream, fEnum.EncryptionMethod.One, SafeEncryptionProvider.ConvertByteArrayToUIntArray(keyRaw)); + byte[] plain = new byte[64]; + comparer.Read(plain, 0, 64); + if (!plain.SequenceEqual(knownPlain)) throw new Exception("Nope, we didn't got the key"); + // Set offset on FileInfo fOffsetFileinfo = (int)br.BaseStream.Position; diff --git a/SafeEncryptionProvider.cs b/SafeEncryptionProvider.cs index 4a25ba60..091a5ea4 100644 --- a/SafeEncryptionProvider.cs +++ b/SafeEncryptionProvider.cs @@ -41,7 +41,7 @@ public void Init(uint[] pkey, fEnum.EncryptionMethod EM) k = pkey; m = EM; - byte[] keyB = ConvertUIntArrayToByteArray(pkey); + kB = ConvertUIntArrayToByteArray(pkey); } private void CheckKey() @@ -96,80 +96,87 @@ private static byte RotateRight(byte val, byte n) #endregion - #region Encryption ONE (Disabled) -/** - private void EncryptDecryptOneSafe(byte[] bufferArr, int bufferIdx, int bufferLen, bool isEncrypted) + #region Encryption ONE + + private void EncryptDecryptOneSafe(byte[] bufferArr, byte[] resultArr, int bufferLen, bool isEncrypted, + int bufferPtr = 0, int resultPtr = 0) // Pointers always start at 0 { - uint fullWordCount = unchecked((uint)bufferLen / 8); - uint leftover = (uint)(bufferLen % 8); //remaining of fullWordCount - - uint[] intWordArrB = ConvertByteArrayToUIntArray(bufferArr); + uint fullWordCount = unchecked((uint)bufferLen / 8), + leftover = unchecked((uint)bufferLen) % 8; - intWordArrB -= 2; - intWordArrO -= 2; + uint[] intWordArrB = ConvertByteArrayToUIntArray(bufferArr), + intWordArrO = ConvertByteArrayToUIntArray(resultArr); + int intWordPtrB = 0; + int intWordPtrO = 0; + + // Back up pointers by 2 + intWordPtrB -= 2; + intWordPtrO -= 2; if (isEncrypted) + { for (int wordCount = 0; wordCount < fullWordCount; wordCount++) - EncryptWordOne(intWordArrB += 2, intWordArrO += 2); + EncryptWordOneSafe(intWordArrB, intWordArrO, intWordPtrB += 2, intWordPtrO += 2); + } else + { for (int wordCount = 0; wordCount < fullWordCount; wordCount++) - DecryptWordOne(intWordArrB += 2, intWordArrO += 2); + DecryptWordOneSafe(intWordArrB, intWordArrO, intWordPtrB += 2, intWordPtrO += 2); + } - if (leftover == 0) return; // no leftover for me? get lost :c + if (leftover == 0) return; // Where's my leftover :c - byte[] bufferEnd = bufferArr + bufferLen; - byte[] byteWordArrB2 = bufferEnd - leftover; - byte[] byteWordArrO2 = ConvertUIntArrayToByteArray(resultArr + bufferLen - leftover); ; + byte[] bufferEnd = BitConverter.GetBytes(bufferArr[bufferPtr] + bufferLen); + int bufferEndPtr = 0; + byte[] byteWordArrB2 = BitConverter.GetBytes(bufferEnd[bufferEndPtr] - leftover); + int byteWordPtrB2 = 0; + byte[] byteWordArrO2 = BitConverter.GetBytes(resultArr[resultPtr] + bufferLen - leftover); + int byteWordPtrO2 = 0; - // copy leftoverBuffer[] -> result[] - Array.Copy(byteWordArrB2, byteWordArrO2, leftover); - - // deal with leftover + //copy leftover buffer array to result array + Array.Copy(byteWordArrB2, byteWordPtrB2, byteWordArrO2, byteWordPtrO2, bufferEnd[bufferEndPtr]); + + // Deal with the leftover if (isEncrypted) - SimpleEncryptBytesSafe(byteWordArrO2 - leftover, 0, unchecked((int)leftover)); + SimpleEncryptBytesSafe(byteWordArrO2, (int)(byteWordPtrO2 - leftover), unchecked((int)leftover)); else - SimpleDecryptBytesSafe(byteWordArrO2 - leftover, 0, unchecked((int)leftover)); + SimpleDecryptBytesSafe(byteWordArrO2, (int)(byteWordPtrO2 - leftover), unchecked((int)leftover)); } -**/ + #region Sub-Functions (Encrypt/Decrypt) -/** - private void EncryptWordOne(uint[] v uint[] o) + private void EncryptWordOneSafe(uint[] v, uint[] o, int vPtr, int oPtr) { uint i; - uint v0 = v[0]; - uint v1 = v[1]; + uint v0 = v[vPtr]; + uint v1 = v[vPtr + 1]; uint sum = 0; for (i = 0; i < r; i++) { - //todo: cache sum + k for better speed v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]); sum += d; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]); } - - o[0] = v0; - o[1] = v1; + o[oPtr] = v0; + o[oPtr + 1] = v1; } - - private void DecryptWordOne(uint[] v , uint[] o ) + + private void DecryptWordOneSafe(uint[] v, uint[] o, int vPtr, int oPtr) { uint i; - uint v0 = v[0]; - uint v1 = v[1]; + uint v0 = v[vPtr]; + uint v1 = v[vPtr + 1]; uint sum = unchecked(d * r); for (i = 0; i < r; i++) { - //todo: cache sum + k for better speed v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]); sum -= d; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]); } - o[0] = v0; - o[1] = v1; + o[oPtr] = v0; + o[oPtr + 1] = v1; } -**/ #endregion @@ -227,7 +234,6 @@ private void EncryptDecryptTwoSafe(byte[] bufferArr, bool isEncrypted, int count byte[] leftoverBufferProcessed = ConvertUIntArrayToByteArray(leftoverBufferWords); Buffer.BlockCopy(leftoverBufferProcessed, 0, bufferArr, (int)(offset + fullWordCount * NMaxBytes), (int)_n * 4); - if (isEncrypted) SimpleEncryptBytesSafe(bufferArr, (int)(count - leftover) + offset, count); else @@ -299,38 +305,44 @@ private void EncryptDecryptHomebrew(byte[] bufferArr, int offset,int bufferLen, #endregion #region Encrypt/Decrypt Main - - private void EncryptDecryptSafe(byte[] bufferArr, int bufferLen, bool isEncrypted) - { - EncryptDecryptTwoSafe(bufferArr, isEncrypted, bufferLen, 0); - } - private void EncryptDecryptSafe(byte[] bufferArr, byte[] outputArr, int bufferLength, bool encrypt) + private void EncryptDecryptSafe(byte[] bufferArr, int bufferLen, bool isEncrypted, + int bufferPtr = 0) { switch (m) { case fEnum.EncryptionMethod.One: - throw new NotImplementedException("Holy shit it's the chosen one encryption"); - + EncryptDecryptOneSafe(bufferArr, bufferArr, bufferLen, isEncrypted, bufferPtr, bufferPtr); + break; case fEnum.EncryptionMethod.Two: - throw new NotSupportedException(); //nuh uh - - case fEnum.EncryptionMethod.Three: return; - + EncryptDecryptTwoSafe(bufferArr, isEncrypted, bufferLen, bufferPtr); + break; + case fEnum.EncryptionMethod.Three: + EncryptDecryptHomebrew(bufferArr, bufferPtr, bufferLen, isEncrypted); + break; case fEnum.EncryptionMethod.Four: CheckKey(); break; } } - public void EncryptDecrypt(byte[] buffer, byte[] output, int bufStart, int outputStart, int count, - bool encrypt) + private void EncryptDecryptSafe(byte[] bufferArr, byte[] outputArr, int bufferLen, bool isEncrypted, + int bufferPtr = 0, int resultPtr = 0) { - //only Two is ported to managed code, so the encryption method is ignored - if (output != null) - throw new NotSupportedException("Custom output is not supported when SAFE_ENCRYPTION is enabled."); - EncryptDecryptTwoSafe(buffer, encrypt, count, bufStart); + switch (m) + { + case fEnum.EncryptionMethod.One: + EncryptDecryptOneSafe(bufferArr, outputArr, bufferLen, isEncrypted, bufferPtr, resultPtr); + break; + case fEnum.EncryptionMethod.Three: + case fEnum.EncryptionMethod.Two: + throw new NotSupportedException(); + case fEnum.EncryptionMethod.Four: + CheckKey(); + break; + } } + #endregion @@ -344,22 +356,22 @@ public void EncryptDecrypt(byte[] buffer, byte[] output, int bufStart, int outp **/ public void Decrypt(byte[] buffer) { - EncryptDecrypt(buffer, null, 0, 0, buffer.Length, false); + EncryptDecryptSafe(buffer, buffer.Length, false, 0); } public void Decrypt(byte[] buffer, int start, int count) { - EncryptDecrypt(buffer, null, start, 0, count, false); + EncryptDecryptSafe(buffer, count, false, start); } public void Decrypt(byte[] buffer, byte[] output) { - EncryptDecrypt(buffer, output, 0, 0, buffer.Length, false); + EncryptDecryptSafe(buffer, output, buffer.Length, false, 0, 0); } public void Decrypt(byte[] buffer, byte[] output, int bufStart, int outStart, int count) { - EncryptDecrypt(buffer, output, bufStart, outStart, count, false); + EncryptDecryptSafe(buffer, output, count, false, bufStart, outStart); } #endregion @@ -372,22 +384,22 @@ public void Decrypt(byte[] buffer, byte[] output, int bufStart, int outStart, in **/ public void Encrypt(byte[] buffer) { - EncryptDecrypt(buffer, null, 0, 0, buffer.Length, true); + EncryptDecryptSafe(buffer, buffer.Length, true, 0); } public void Encrypt(byte[] buffer, int start, int count) { - EncryptDecrypt(buffer, null, start, 0, count, true); + EncryptDecryptSafe(buffer, count, true, start); } public void Encrypt(byte[] buffer, byte[] output) { - EncryptDecrypt(buffer, output, 0, 0, buffer.Length, true); + EncryptDecryptSafe(buffer, output, buffer.Length, true, 0, 0); } public void Encrypt(byte[] buffer, byte[] output, int bufStart, int outStart, int count) { - EncryptDecrypt(buffer, output, bufStart, outStart, count, true); + EncryptDecryptSafe(buffer, output, count, true, bufStart, outStart); } diff --git a/obj/Debug/net8.0/fStreamDecryptor.AssemblyInfo.cs b/obj/Debug/net8.0/fStreamDecryptor.AssemblyInfo.cs index 5c164964..89ec43a6 100644 --- a/obj/Debug/net8.0/fStreamDecryptor.AssemblyInfo.cs +++ b/obj/Debug/net8.0/fStreamDecryptor.AssemblyInfo.cs @@ -13,7 +13,7 @@ [assembly: System.Reflection.AssemblyCompanyAttribute("fStreamDecryptor")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6349b257b3c1251ab831d37be89a3bd44f17ab40")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+c89a2b1f21459d4620ed405672c6c50d83231410")] [assembly: System.Reflection.AssemblyProductAttribute("fStreamDecryptor")] [assembly: System.Reflection.AssemblyTitleAttribute("fStreamDecryptor")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]