From e57a53531f08b5273083e7c98c6e07fa32607286 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sat, 12 Nov 2016 09:04:34 -0600 Subject: [PATCH 1/4] Fixed XP support for memecrypto All while retaining FIPS support for later OS's. --- PKHeX/PKHeX.csproj | 1 + PKHeX/Saves/Substructures/MemeCrypto.cs | 89 +++++++++++++------------ PKHeX/Util/CryptoUtil.cs | 66 ++++++++++++++++++ 3 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 PKHeX/Util/CryptoUtil.cs diff --git a/PKHeX/PKHeX.csproj b/PKHeX/PKHeX.csproj index f444e4f38..0a689ec81 100644 --- a/PKHeX/PKHeX.csproj +++ b/PKHeX/PKHeX.csproj @@ -420,6 +420,7 @@ + diff --git a/PKHeX/Saves/Substructures/MemeCrypto.cs b/PKHeX/Saves/Substructures/MemeCrypto.cs index 83744f26b..f2e9c0df1 100644 --- a/PKHeX/Saves/Substructures/MemeCrypto.cs +++ b/PKHeX/Saves/Substructures/MemeCrypto.cs @@ -14,7 +14,7 @@ private static byte[] AESECBEncrypt(byte[] key, byte[] data) { using (MemoryStream ms = new MemoryStream()) { - using (var aes = new AesCryptoServiceProvider()) + using (var aes = Util.GetAesProvider()) { aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; @@ -34,7 +34,7 @@ private static byte[] AESECBDecrypt(byte[] key, byte[] data) { using (MemoryStream ms = new MemoryStream()) { - using (var aes = new AesCryptoServiceProvider()) + using (var aes = Util.GetAesProvider()) { aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; @@ -204,62 +204,67 @@ private static byte[] ReverseCrypt(byte[] input) if (input.Length != 0x80) throw new ArgumentException("Invalid Memecrypto decryption byte[]!"); - var sha1 = new SHA1CryptoServiceProvider(); + using (var sha1 = Util.GetSHA1Provider()) + { + byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); + byte[] enc = new byte[0x60]; + Array.Copy(input, 0x20, enc, 0, 0x60); - byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); - byte[] enc = new byte[0x60]; - Array.Copy(input, 0x20, enc, 0, 0x60); + byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; + Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); + Array.Copy(input, 0, keybuf, PubKeyDer.Length, 0x20); + byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; - Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); - Array.Copy(input, 0, keybuf, PubKeyDer.Length, 0x20); - byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - - byte[] RSA = RSAEncrypt(enc); - var dec = MemeCryptoAESDecrypt(key, RSA); - if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) - return dec; - RSA[0] |= 0x80; - dec = MemeCryptoAESDecrypt(key, RSA); - if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) - return dec; + byte[] RSA = RSAEncrypt(enc); + var dec = MemeCryptoAESDecrypt(key, RSA); + if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) + return dec; + RSA[0] |= 0x80; + dec = MemeCryptoAESDecrypt(key, RSA); + if (sha1.ComputeHash(dec).Take(0x8).SequenceEqual(dec.Skip(0x58))) + return dec; + } return null; } public static byte[] Resign(byte[] sav7) { if (sav7 == null || sav7.Length != 0x6BE00) - return null; - - var sha1 = new SHA1CryptoServiceProvider(); + return null; byte[] outSav = (byte[])sav7.Clone(); - byte[] CurSig = new byte[0x80]; - Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); + using (var sha1 = Util.GetSHA1Provider()) + { + using (var sha256 = Util.GetSHA256Provider()) + { + byte[] CurSig = new byte[0x80]; + Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); - byte[] ChecksumTable = new byte[0x140]; - Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); - byte[] Hash = new SHA256CryptoServiceProvider().ComputeHash(ChecksumTable); + byte[] ChecksumTable = new byte[0x140]; + Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); + byte[] Hash = sha256.ComputeHash(ChecksumTable); - byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); + byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); - byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; - Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); - Array.Copy(Hash, 0, keybuf, PubKeyDer.Length, 0x20); - byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); + byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; + Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); + Array.Copy(Hash, 0, keybuf, PubKeyDer.Length, 0x20); + byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - byte[] secret = ReverseCrypt(CurSig) ?? new byte[0x60]; - byte[] secretWorkBuf = new byte[0x78]; - Hash.CopyTo(secretWorkBuf, 0); - Array.Copy(secret, 0, secretWorkBuf, 0x20, 0x58); - Array.Copy(sha1.ComputeHash(secretWorkBuf), 0, secret, 0x58, 8); + byte[] secret = ReverseCrypt(CurSig) ?? new byte[0x60]; + byte[] secretWorkBuf = new byte[0x78]; + Hash.CopyTo(secretWorkBuf, 0); + Array.Copy(secret, 0, secretWorkBuf, 0x20, 0x58); + Array.Copy(sha1.ComputeHash(secretWorkBuf), 0, secret, 0x58, 8); - Hash.CopyTo(outSav, 0x6BB00); - byte[] MemeCrypted = MemeCryptoAESEncrypt(key, secret); - MemeCrypted[0] &= 0x7F; - var RSA = RSADecrypt(MemeCrypted); - Array.Copy(RSA, 0, outSav, 0x6BB20, 0x60); + Hash.CopyTo(outSav, 0x6BB00); + byte[] MemeCrypted = MemeCryptoAESEncrypt(key, secret); + MemeCrypted[0] &= 0x7F; + var RSA = RSADecrypt(MemeCrypted); + Array.Copy(RSA, 0, outSav, 0x6BB20, 0x60); + } + } return outSav; } diff --git a/PKHeX/Util/CryptoUtil.cs b/PKHeX/Util/CryptoUtil.cs new file mode 100644 index 000000000..c6938d619 --- /dev/null +++ b/PKHeX/Util/CryptoUtil.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace PKHeX +{ + public partial class Util + { + + /// + /// Creates a new instance of , or if not supported by the current platform. + /// + /// Thrown if FIPS mode is enabled on a platform that does not support . + public static SHA1 GetSHA1Provider() + { + SHA1 provider; + try + { + provider = new SHA1CryptoServiceProvider(); + } + catch (PlatformNotSupportedException) + { + provider = new SHA1Managed(); + } + return provider; + } + + /// + /// Creates a new instance of , or if not supported by the current platform. + /// + /// Thrown if FIPS mode is enabled on a platform that does not support . + public static SHA256 GetSHA256Provider() + { + SHA256 provider; + try + { + provider = new SHA256CryptoServiceProvider(); + } + catch (PlatformNotSupportedException) + { + provider = new SHA256Managed(); + } + return provider; + } + + /// + /// Creates a new instance of , or if not supported by the current platform. + /// + /// Thrown if FIPS mode is enabled on a platform that does not support . + public static Aes GetAesProvider() + { + Aes provider; + try + { + provider = new AesCryptoServiceProvider(); + } + catch (PlatformNotSupportedException) + { + provider = new AesManaged(); + } + return provider; + } + } +} From 75171bf1df80ed6eb931cf5a5108184122c63ea9 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sat, 12 Nov 2016 09:30:51 -0600 Subject: [PATCH 2/4] Added warnings for FIPS on XP, and allowed saving to proceed --- PKHeX/MainWindow/Main.cs | 6 ++++++ PKHeX/Saves/SAV7.cs | 23 +++++++++++++++++------ PKHeX/Saves/SaveFile.cs | 2 ++ PKHeX/Saves/SaveUtil.cs | 2 +- PKHeX/Saves/Substructures/MemeCrypto.cs | 22 +++++++++++++++++++++- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/PKHeX/MainWindow/Main.cs b/PKHeX/MainWindow/Main.cs index 91f2056bf..06f286883 100644 --- a/PKHeX/MainWindow/Main.cs +++ b/PKHeX/MainWindow/Main.cs @@ -783,6 +783,12 @@ private void openSAV(SaveFile sav, string path) if (sav == null || sav.Version == GameVersion.Invalid) { Util.Error("Invalid save file loaded. Aborting.", path); return; } + if (sav.RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) + { + Util.Error("Your platform does not support the required cryptography components.", "In order to be able to save your changes, you must either upgrade to a newer version of Windows or disable FIPS compliance mode."); + // Don't abort loading; user can still view save and fix checksum on another platform. + } + // Finish setting up the save file. if (sav.IndeterminateGame && sav.Generation == 3) { diff --git a/PKHeX/Saves/SAV7.cs b/PKHeX/Saves/SAV7.cs index 8e1387ab2..428c729f5 100644 --- a/PKHeX/Saves/SAV7.cs +++ b/PKHeX/Saves/SAV7.cs @@ -819,24 +819,35 @@ private void setWC6(MysteryGift wc6, int index) // Writeback Validity public override string MiscSaveChecks() { - string r = ""; + var r = new StringBuilder(); + + // MemeCrypto check + if (RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) + { + r.AppendLine("Platform does not support required cryptography providers."); + r.AppendLine("Checksum will be broken until the file is saved using an OS without FIPS compliance enabled or a newer OS."); + r.AppendLine(); + } + + // FFFF checks byte[] FFFF = Enumerable.Repeat((byte)0xFF, 0x200).ToArray(); for (int i = 0; i < Data.Length / 0x200; i++) { if (!FFFF.SequenceEqual(Data.Skip(i * 0x200).Take(0x200))) continue; - r = $"0x200 chunk @ 0x{(i*0x200).ToString("X5")} is FF'd." - + Environment.NewLine + "Cyber will screw up (as of August 31st 2014)." + Environment.NewLine + Environment.NewLine; + r.AppendLine($"0x200 chunk @ 0x{(i * 0x200).ToString("X5")} is FF'd."); + r.AppendLine("Cyber will screw up (as of August 31st 2014)."); + r.AppendLine(); // Check to see if it is in the Pokedex if (i * 0x200 > PokeDex && i * 0x200 < PokeDex + 0x900) { - r += "Problem lies in the Pokedex. "; + r.Append("Problem lies in the Pokedex. "); if (i * 0x200 == PokeDex + 0x400) - r += "Remove a language flag for a species < 585, ie Petilil"; + r.Append("Remove a language flag for a species < 585, ie Petilil"); } break; } - return r; + return r.ToString(); } public override string MiscSaveInfo() { diff --git a/PKHeX/Saves/SaveFile.cs b/PKHeX/Saves/SaveFile.cs index 9e8080a5c..b85a88600 100644 --- a/PKHeX/Saves/SaveFile.cs +++ b/PKHeX/Saves/SaveFile.cs @@ -550,5 +550,7 @@ public void setData(byte[] input, int Offset) input.CopyTo(Data, Offset); Edited = true; } + + public virtual bool RequiresMemeCrypto { get { return false; } } } } diff --git a/PKHeX/Saves/SaveUtil.cs b/PKHeX/Saves/SaveUtil.cs index 2992dedb5..08dfbf99c 100644 --- a/PKHeX/Saves/SaveUtil.cs +++ b/PKHeX/Saves/SaveUtil.cs @@ -586,7 +586,7 @@ internal static ushort check16(byte[] data, int blockID, ushort initial = 0) } public static byte[] Resign7(byte[] sav7) { - return MemeCrypto.Resign(sav7); + return MemeCrypto.Resign(sav7, false); } /// Calculates the 32bit checksum over an input byte array. Used in GBA save files. /// Input byte array diff --git a/PKHeX/Saves/Substructures/MemeCrypto.cs b/PKHeX/Saves/Substructures/MemeCrypto.cs index f2e9c0df1..ffa3bdc43 100644 --- a/PKHeX/Saves/Substructures/MemeCrypto.cs +++ b/PKHeX/Saves/Substructures/MemeCrypto.cs @@ -227,7 +227,14 @@ private static byte[] ReverseCrypt(byte[] input) return null; } - public static byte[] Resign(byte[] sav7) + /// + /// Resigns save data. + /// + /// The save data to resign. + /// If true, throw an if MemeCrypto is unsupported. If false, calling this function will have no effect. + /// Thrown if the current platform has FIPS mode enabled on a platform that does not support the required crypto service providers. + /// The resigned save data. + public static byte[] Resign(byte[] sav7, bool throwIfUnsupported = true) { if (sav7 == null || sav7.Length != 0x6BE00) return null; @@ -268,6 +275,19 @@ public static byte[] Resign(byte[] sav7) return outSav; } + + public static bool CanUseMemeCrypto() + { + try + { + Util.GetSHA256Provider(); + } + catch (InvalidOperationException) + { + return false; + } + return true; + } } public static class StringExtentions From 65cd62706db2bfd0d0252c5607e3314a87e9f4ba Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sat, 12 Nov 2016 09:34:22 -0600 Subject: [PATCH 3/4] SAV7 requires memecrypto --- PKHeX/Saves/SAV7.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PKHeX/Saves/SAV7.cs b/PKHeX/Saves/SAV7.cs index 428c729f5..c5a5a0636 100644 --- a/PKHeX/Saves/SAV7.cs +++ b/PKHeX/Saves/SAV7.cs @@ -854,5 +854,13 @@ public override string MiscSaveInfo() return Blocks.Aggregate("", (current, b) => current + $"{b.ID.ToString("00")}: {b.Offset.ToString("X5")}-{(b.Offset + b.Length).ToString("X5")}, {b.Length.ToString("X5")}{Environment.NewLine}"); } + + public override bool RequiresMemeCrypto + { + get + { + return true; + } + } } } From 77d7932d19c9682341a4738f9e2271e0145e7be2 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sat, 12 Nov 2016 09:38:56 -0600 Subject: [PATCH 4/4] Fixed incomplete implementation --- PKHeX/MainWindow/Main.cs | 11 +++-- PKHeX/Saves/Substructures/MemeCrypto.cs | 65 +++++++++++++++---------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/PKHeX/MainWindow/Main.cs b/PKHeX/MainWindow/Main.cs index 06f286883..74ab7de1e 100644 --- a/PKHeX/MainWindow/Main.cs +++ b/PKHeX/MainWindow/Main.cs @@ -783,11 +783,14 @@ private void openSAV(SaveFile sav, string path) if (sav == null || sav.Version == GameVersion.Invalid) { Util.Error("Invalid save file loaded. Aborting.", path); return; } - if (sav.RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) + if (!string.IsNullOrEmpty(path)) // If path is null, this is the default save { - Util.Error("Your platform does not support the required cryptography components.", "In order to be able to save your changes, you must either upgrade to a newer version of Windows or disable FIPS compliance mode."); - // Don't abort loading; user can still view save and fix checksum on another platform. - } + if (sav.RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) + { + Util.Error("Your platform does not support the required cryptography components.", "In order to be able to save your changes, you must either upgrade to a newer version of Windows or disable FIPS compliance mode."); + // Don't abort loading; user can still view save and fix checksum on another platform. + } + } // Finish setting up the save file. if (sav.IndeterminateGame && sav.Generation == 3) diff --git a/PKHeX/Saves/Substructures/MemeCrypto.cs b/PKHeX/Saves/Substructures/MemeCrypto.cs index ffa3bdc43..bf2814596 100644 --- a/PKHeX/Saves/Substructures/MemeCrypto.cs +++ b/PKHeX/Saves/Substructures/MemeCrypto.cs @@ -239,41 +239,54 @@ public static byte[] Resign(byte[] sav7, bool throwIfUnsupported = true) if (sav7 == null || sav7.Length != 0x6BE00) return null; - byte[] outSav = (byte[])sav7.Clone(); - - using (var sha1 = Util.GetSHA1Provider()) + try { - using (var sha256 = Util.GetSHA256Provider()) + byte[] outSav = (byte[])sav7.Clone(); + + using (var sha1 = Util.GetSHA1Provider()) { - byte[] CurSig = new byte[0x80]; - Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); + using (var sha256 = Util.GetSHA256Provider()) + { + byte[] CurSig = new byte[0x80]; + Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); - byte[] ChecksumTable = new byte[0x140]; - Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); - byte[] Hash = sha256.ComputeHash(ChecksumTable); + byte[] ChecksumTable = new byte[0x140]; + Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); + byte[] Hash = sha256.ComputeHash(ChecksumTable); - byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); + byte[] PubKeyDer = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); - byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; - Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); - Array.Copy(Hash, 0, keybuf, PubKeyDer.Length, 0x20); - byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); + byte[] keybuf = new byte[PubKeyDer.Length + 0x20]; + Array.Copy(PubKeyDer, keybuf, PubKeyDer.Length); + Array.Copy(Hash, 0, keybuf, PubKeyDer.Length, 0x20); + byte[] key = sha1.ComputeHash(keybuf).Take(0x10).ToArray(); - byte[] secret = ReverseCrypt(CurSig) ?? new byte[0x60]; - byte[] secretWorkBuf = new byte[0x78]; - Hash.CopyTo(secretWorkBuf, 0); - Array.Copy(secret, 0, secretWorkBuf, 0x20, 0x58); - Array.Copy(sha1.ComputeHash(secretWorkBuf), 0, secret, 0x58, 8); + byte[] secret = ReverseCrypt(CurSig) ?? new byte[0x60]; + byte[] secretWorkBuf = new byte[0x78]; + Hash.CopyTo(secretWorkBuf, 0); + Array.Copy(secret, 0, secretWorkBuf, 0x20, 0x58); + Array.Copy(sha1.ComputeHash(secretWorkBuf), 0, secret, 0x58, 8); - Hash.CopyTo(outSav, 0x6BB00); - byte[] MemeCrypted = MemeCryptoAESEncrypt(key, secret); - MemeCrypted[0] &= 0x7F; - var RSA = RSADecrypt(MemeCrypted); - Array.Copy(RSA, 0, outSav, 0x6BB20, 0x60); + Hash.CopyTo(outSav, 0x6BB00); + byte[] MemeCrypted = MemeCryptoAESEncrypt(key, secret); + MemeCrypted[0] &= 0x7F; + var RSA = RSADecrypt(MemeCrypted); + Array.Copy(RSA, 0, outSav, 0x6BB20, 0x60); + } + } + return outSav; + } + catch (InvalidOperationException) + { + if (throwIfUnsupported) + { + throw; + } + else + { + return (byte[])sav7.Clone(); } } - - return outSav; } public static bool CanUseMemeCrypto()