diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..457bc98 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 xiangyuecn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Program.cs b/Program.cs index 7341f67..13bd32e 100644 --- a/Program.cs +++ b/Program.cs @@ -1,62 +1,1250 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Threading; -namespace RSA { +namespace com.github.xiangyuecn.rsacsharp { + /// + /// RSA、RSA_PEM测试控制台主程序,.NET Core、.NET Framework均可测试 + /// GitHub: https://github.com/xiangyuecn/RSA-csharp + /// class Program { - static void RSATest() { - var rsa = new RSA(512); - Console.WriteLine("【512私钥(XML)】:"); - Console.WriteLine(rsa.ToXML()); - Console.WriteLine(); - Console.WriteLine("【512私钥(PEM)】:"); - Console.WriteLine(rsa.ToPEM_PKCS1()); - Console.WriteLine(); - Console.WriteLine("【512公钥(PEM)】:"); - Console.WriteLine(rsa.ToPEM_PKCS1(true)); - Console.WriteLine(); + static public void Main(string[] args) { + //【请在这里编写你自己的测试代码】 + + ShowMenu(args); + } + + static void RSATest(bool fast) { + //新生成一个RSA密钥,也可以通过已有的pem、xml文本密钥来创建RSA + var rsa = new RSA_Util(512); + // var rsa = new RSA_Util("pem或xml文本密钥"); + // var rsa = new RSA_Util(RSA_PEM.FromPEM("pem文本密钥")); + // var rsa = new RSA_Util(RSA_PEM.FromXML("xml文本密钥")); + + //得到pem对象 + RSA_PEM pem = rsa.ToPEM(false); + //提取密钥pem字符串 + string pem_pkcs1 = pem.ToPEM_PKCS1(); + string pem_pkcs8 = pem.ToPEM_PKCS8(); + //提取密钥xml字符串 + string xml = rsa.ToXML(); + + AssertMsg(T("【" + rsa.KeySize + "私钥(XML)】:", "[ " + rsa.KeySize + " Private Key (XML) ]:"), rsa.KeySize == 512); + S(xml); + S(); + ST("【" + rsa.KeySize + "私钥(PKCS#1)】:", "[ " + rsa.KeySize + " Private Key (PKCS#1) ]:"); + S(pem_pkcs1); + S(); + ST("【" + rsa.KeySize + "公钥(PKCS#8)】:", "[ " + rsa.KeySize + " Public Key (PKCS#8) ]:"); + S(pem.ToPEM_PKCS8(true)); + S(); - var str = "abc内容123"; - var en = rsa.Encode(str); - Console.WriteLine("【加密】:"); - Console.WriteLine(en); + var str = T("abc内容123", "abc123"); + var en = rsa.Encrypt("PKCS1", str); + ST("【加密】:", "[ Encrypt ]:"); + S(en); - Console.WriteLine("【解密】:"); - Console.WriteLine(rsa.DecodeOrNull(en)); + ST("【解密】:", "[ Decrypt ]:"); + var de = rsa.Decrypt("PKCS1", en); + AssertMsg(de, de == str); - Console.WriteLine("【签名SHA1】:"); - Console.WriteLine(rsa.Sign("SHA1", str)); + if (!fast) { + var str2 = str; for (var i = 0; i < 15; i++) str2 += str2; + ST("【长文本加密解密】:", "[ Long text encryption and decryption ]:"); + AssertMsg(str2.Length + T("个字 OK", " characters OK"), rsa.Decrypt("PKCS1", rsa.Encrypt("PKCS1", str2)) == str2); + } + + ST("【签名SHA1】:", "[ Signature SHA1 ]:"); + var sign = rsa.Sign("SHA1", str); + Console.WriteLine(sign); + AssertMsg(T("校验 OK", "Verify OK"), rsa.Verify("SHA1", sign, str)); Console.WriteLine(); - var rsa2 = new RSA(rsa.ToPEM_PKCS8(), true); - Console.WriteLine("【用PEM新创建的RSA是否和上面的一致】:"); - Console.WriteLine("XML:" + (rsa2.ToXML() == rsa.ToXML())); - Console.WriteLine("PKCS1:" + (rsa2.ToPEM_PKCS1() == rsa.ToPEM_PKCS1())); - Console.WriteLine("PKCS8:" + (rsa2.ToPEM_PKCS8() == rsa.ToPEM_PKCS8())); + //用pem文本创建RSA + var rsa2 = new RSA_Util(RSA_PEM.FromPEM(pem_pkcs8)); + ST("【用PEM新创建的RSA是否和上面的一致】:", "[ Is the newly created RSA with PEM consistent with the above ]:"); + Assert("XML:", rsa2.ToXML() == rsa.ToXML()); + Assert("PKCS1:", rsa2.ToPEM().ToPEM_PKCS1() == pem.ToPEM_PKCS1()); + Assert("PKCS8:", rsa2.ToPEM().ToPEM_PKCS8() == pem.ToPEM_PKCS8()); + + //用xml文本创建RSA + var rsa3 = new RSA_Util(RSA_PEM.FromXML(xml)); + ST("【用XML新创建的RSA是否和上面的一致】:", "[ Is the newly created RSA with XML consistent with the above ]:"); + Assert("XML:", rsa3.ToXML() == rsa.ToXML()); + Assert("PKCS1:", rsa3.ToPEM().ToPEM_PKCS1() == pem.ToPEM_PKCS1()); + Assert("PKCS8:", rsa3.ToPEM().ToPEM_PKCS8() == pem.ToPEM_PKCS8()); + + //--------RSA_PEM私钥验证--------- + //使用PEM全量参数构造pem对象 + RSA_PEM pemX = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, pem.Key_D, pem.Val_P, pem.Val_Q, pem.Val_DP, pem.Val_DQ, pem.Val_InverseQ); + ST("【RSA_PEM是否和原始RSA一致】:", "[ Is RSA_PEM consistent with the original RSA ]:"); + S(pemX.KeySize + T("位", " bits")); + Assert("XML:", pemX.ToXML(false) == pem.ToXML(false)); + Assert("PKCS1:", pemX.ToPEM_PKCS1() == pem.ToPEM_PKCS1()); + Assert("PKCS8:", pemX.ToPEM_PKCS8() == pem.ToPEM_PKCS8()); + ST("仅公钥:", "Public Key Only:"); + Assert("XML:", pemX.ToXML(true) == pem.ToXML(true)); + Assert("PKCS1:", pemX.ToPEM_PKCS1(true) == pem.ToPEM_PKCS1(true)); + Assert("PKCS8:", pemX.ToPEM_PKCS8(true) == pem.ToPEM_PKCS8(true)); + + //--------RSA_PEM公钥验证--------- + RSA_PEM pemY = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, null); + ST("【RSA_PEM仅公钥是否和原始RSA一致】:", "[ RSA_PEM only public key is consistent with the original RSA ]:"); + S(pemY.KeySize + T("位", " bits")); + Assert("XML:", pemY.ToXML(false) == pem.ToXML(true)); + Assert("PKCS1:", pemY.ToPEM_PKCS1() == pem.ToPEM_PKCS1(true)); + Assert("PKCS8:", pemY.ToPEM_PKCS8() == pem.ToPEM_PKCS8(true)); + + if (!fast) { + //使用n、e、d构造pem对象 + RSA_PEM pem4 = new RSA_PEM(pem.Key_Modulus, pem.Key_Exponent, pem.Key_D); + RSA_Util rsa4 = new RSA_Util(pem4); + ST("【用n、e、d构造解密】", "[ Construct decryption with n, e, d ]"); + de = rsa4.Decrypt("PKCS1", en); + AssertMsg(de, de == str); + AssertMsg(T("校验 OK", "Verify OK"), rsa4.Verify("SHA1", sign, str)); + + + //对调交换公钥私钥 + ST("【Unsafe|对调公钥私钥,私钥加密公钥解密】", "[ Unsafe | Swap the public key and private key, private key encryption and public key decryption ]"); + var rsaPri = rsa.SwapKey_Exponent_D__Unsafe(); + var rsaPub = new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe(); + if (!RSA_Util.IsUseBouncyCastle) { + rsaPub = rsaPri; + ST(".NET自带的RSA不支持仅含公钥的密钥进行解密和签名,使用NoPadding填充方式或IsUseBouncyCastle时无此问题", "The RSA that comes with .NET does not support decryption and signing with keys containing only public keys. This problem does not occur when using NoPadding or IsUseBouncyCastle."); + } + try { + var enPri = rsaPri.Encrypt("PKCS1", str); + var signPub = rsaPub.Sign("SHA1", str); + de = rsaPub.Decrypt("PKCS1", enPri); + AssertMsg(de, de == str); + AssertMsg(T("校验 OK", "Verify OK"), rsaPri.Verify("SHA1", signPub, str)); + } catch (Exception e) { + if (!RSA_Util.IS_CoreOr46 && !RSA_Util.IsUseBouncyCastle) { + S(T("不支持在RSACryptoServiceProvider中使用:", "Not supported in RSACryptoServiceProvider: ") + e.Message); + } else { + throw e; + } + } - var rsa3 = new RSA(rsa.ToXML()); - Console.WriteLine("【用XML新创建的RSA是否和上面的一致】:"); - Console.WriteLine("XML:" + (rsa3.ToXML() == rsa.ToXML())); - Console.WriteLine("PKCS1:" + (rsa3.ToPEM_PKCS1() == rsa.ToPEM_PKCS1())); - Console.WriteLine("PKCS8:" + (rsa3.ToPEM_PKCS8() == rsa.ToPEM_PKCS8())); + rsa4 = rsaPri.SwapKey_Exponent_D__Unsafe(); + de = rsa4.Decrypt("PKCS1", en); + AssertMsg(de, de == str); + AssertMsg(T("校验 OK", "Verify OK"), rsa4.Verify("SHA1", sign, str)); + } + + + if (!fast) { + S(); + ST("【测试一遍所有的加密、解密填充方式】 按回车键继续测试...", "[ Test all the encryption and decryption padding mode ] Press Enter to continue testing..."); + ReadIn(); + RSA_Util rsa5 = new RSA_Util(2048); + testPaddings(false, rsa5, new RSA_Util(rsa5.ToPEM(true)), true); + } + } + static Type Type_RuntimeInformation(Type[] outOSPlatform) { +#if (RSA_BUILD__NET_CORE || NETCOREAPP || NETSTANDARD || NET) //csproj:PropertyGroup.DefineConstants + https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/preprocessor-directives + if (outOSPlatform != null) outOSPlatform[0] = typeof(OSPlatform); + return typeof(RuntimeInformation); +#else + //.NET Framework 4.7.1 才有 都在mscorlib.dll里面 + var type = typeof(ComVisibleAttribute).Assembly.GetType("System.Runtime.InteropServices.RuntimeInformation"); + if (type != null && outOSPlatform != null) outOSPlatform[0] = type.Assembly.GetType("System.Runtime.InteropServices.OSPlatform"); + return type; +#endif + } + static bool NET_IsWindows() { + Type[] typeOSPlatform = new Type[1]; + Type type = Type_RuntimeInformation(typeOSPlatform); + if (type != null) { + dynamic a1 = typeOSPlatform[0].GetProperty("Windows").GetValue(null); + return (bool)RSA_Util.FindFunc(type, "IsOSPlatform", new string[] { "os" }).Invoke(null, new object[] { a1 }); + } + var ver = Environment.OSVersion.VersionString.ToLower(); + return ver.Contains("microsoft") && ver.Contains("windows"); + } + static string NET_Ver() { + string val, os; + Type type = Type_RuntimeInformation(null); + if (type != null) { + val = (string)type.GetProperty("FrameworkDescription").GetValue(null); + os = (string)type.GetProperty("OSDescription").GetValue(null); + } else { + val = "EnvVer-" + Environment.Version; + os = (NET_IsWindows() ? "Windows" : "Linux?"); + } + val += " | " + os; + return val; } - static void Main(string[] args) { - Console.WriteLine("---------------------------------------------------------"); - Console.WriteLine("◆◆◆◆◆◆◆◆◆◆◆◆ RSA测试 ◆◆◆◆◆◆◆◆◆◆◆◆"); - Console.WriteLine("---------------------------------------------------------"); + static void Assert(string msg, bool check) { + AssertMsg(msg + check, check); + } + static void AssertMsg(string msg, bool check) { + if (!check) throw new Exception(msg); + Console.WriteLine(msg); + } - RSATest(); - Console.WriteLine("-------------------------------------------------------------"); - Console.WriteLine("◆◆◆◆◆◆◆◆◆◆◆◆ 回车退出... ◆◆◆◆◆◆◆◆◆◆◆◆"); + /// 控制台输出一个换行 + static private void S() { Console.WriteLine(); - Console.ReadLine(); } + /// 控制台输出内容 + static private void S(string s) { + Console.WriteLine(s); + } + /// 控制台输出内容 + 简版多语言支持,根据当前语言返回中文或英文,简化调用 + static private void ST(string zh, string en) { + Console.WriteLine(T(zh, en)); + } + /// 简版多语言支持,根据当前语言返回中文或英文,简化调用 + static private string T(string zh, string en) { + return RSA_PEM.T(zh, en); + } + static string ReadIn() { + return Console.ReadLine(); + } + static string ReadPath(string tips, string tips2) { + while (true) { + ST("请输入" + tips + "路径" + tips2 + ": ", "Please enter " + tips + " path" + tips2 + ":"); + Console.Write("> "); + string path = ReadIn().Trim(); + if (path.Length == 0 || path.StartsWith("+")) { + return path; + } + if (!File.Exists(path) && !Directory.Exists(path)) { + ST("文件[" + path + "]不存在", "File [" + path + "] does not exist"); + continue; + } + return path; + } + } + static byte[] ReadFile(string path) { + return File.ReadAllBytes(path); + } + static void WriteFile(string path, byte[] val) { + File.WriteAllBytes(path, val); + } + static readonly string HR = "---------------------------------------------------------"; + + + static private Assembly Bc__Assembly = null; + static private string[] Bc__Dlls = new string[] { + "BouncyCastle.Crypto.dll", "BouncyCastle.Cryptography.dll" + }; + static bool CanLoad_BouncyCastle() { + if (Bc__Assembly != null) return true; + Assembly bc = null; + foreach (var dll in Bc__Dlls) { + try { + bc = Assembly.LoadFrom(dll); + Bc__Assembly = bc; + break; + } catch { } + } + return bc != null; + } + static void printEnv() { + S(".NET Version: " + NET_Ver() + " RSA_PEM.Lang=" + RSA_PEM.Lang); + if (RSA_Util.IsUseBouncyCastle) return; + + var errs = ""; + if (!RSA_Util.IS_CoreOr46) { + errs += errs.Length > 0 ? T("、", ", ") : ""; + errs += T("除OAEP+SHA1以外的所有OAEP加密填充模式", "All OAEP encryption padding modes except for OAEP+SHA1"); + + errs += errs.Length > 0 ? T("、", ", ") : ""; + errs += T("PSS签名填充模式(其他填充模式不影响)", "PSS signature padding mode (other padding modes do not affect)"); + } + + bool _; + if (!SupportHash("SHA-512/256", false, out _)) { + errs += errs.Length > 0 ? T("、", ", ") : ""; + errs += T("SHA-512/224(/256)摘要算法", "SHA-512/224 (/256) digest algorithm"); + } + if (!SupportHash("SHA3-256", false, out _)) { + errs += errs.Length > 0 ? T("、", ", ") : ""; + errs += T("SHA3系列摘要算法", "SHA3 series digest algorithm"); + } + ST("*** .NET不支持NoPadding加密填充模式、不支持SHA-512/224(/256)摘要算法、需要.NET8以上才支持SHA3系列摘要算法,可通过引入BouncyCastle加密增强库来扩充.NET加密功能。", "*** .NET does not support the NoPadding encryption padding mode, does not support the SHA-512/224 (/256) digest algorithm, and requires .NET8 or higher to support the SHA3 series digest algorithm. You can expand the .NET encryption function by introducing the BouncyCastle encryption enhancement library."); + if (errs.Length > 0) { + ST("*** 当前.NET版本太低,不支持:" + errs + ";如需获得这些功能支持,解决办法1:升级使用高版本.NET来运行本测试程序(可能支持);解决办法2:引入BouncyCastle即可得到全部支持。", "*** The current .NET version is too low and does not support: " + errs + "; if you need to obtain support for these functions, solution 1: upgrade to a higher version of .NET to run this test program (may be supported); solution 2: Full support is available with the introduction of BouncyCastle."); + } + ST("*** 如需获得全部加密签名模式支持,可按此方法引入BouncyCastle加密增强库:到 https://www.nuget.org/packages/Portable.BouncyCastle 下载得到NuGet包(或使用 BouncyCastle.Cryptography 包),用压缩软件提取其中lib目录内对应.NET版本下的BouncyCastle.Crypto.dll,放到本测试程序目录内,然后通过测试菜单B进行注册即可得到全部支持。", "*** If you need full encryption and signature mode support, you can introduce BouncyCastle encryption enhancement library in this way: Go to https://www.nuget.org/packages/Portable.BouncyCastle to download the NuGet package (or use the BouncyCastle.Cryptography package), and use compression software to extract it The lib directory corresponds to BouncyCastle.Crypto.dll under the .NET version, place it in the directory of this test program, and then register it through test menu B to get full support."); + } + static bool SupportHash(string hash, bool checkBc, out bool isBc) { + object obj = RSA_Util.HashFromName(hash); + var val = obj != null; + isBc = false; + if (val || !checkBc) { + return val; + } + if (BcAssembly != null) { + try { + obj = BcAssembly.GetType("Org.BouncyCastle.Security.DigestUtilities").GetMethod("GetDigest", new Type[] { typeof(string) }).Invoke(null, new object[] { hash }); + } catch { + obj = null; + } + if (obj != null) { + isBc = true; + return true; + } + } + return val; + } + static Assembly BcAssembly = null; + static void testProvider(bool checkOpenSSL) { + if (CanLoad_BouncyCastle()) { + if (BcAssembly == null) { + ST("检测到BouncyCastle加密增强库,是否要进行注册?(Y/N) Y", "The BouncyCastle encryption enhancement library is detected, do you want to register? (Y/N) Y"); + } else { + ST("已注册BouncyCastle加密增强库,是否要保持注册?(Y/N) Y", "BouncyCastle encryption enhancement library has been registered, do you want to keep it registered? (Y/N) Y"); + } + Console.Write("> "); + string val = ReadIn().Trim().ToUpper(); + try { + if (BcAssembly == null && "N" != val) { + BcAssembly = Bc__Assembly; + RSA_Util.UseBouncyCastle(BcAssembly); + ST("已注册BouncyCastle加密增强库", "BouncyCastle encryption enhancement library registered"); + } + if (BcAssembly != null && "N" == val) { + RSA_Util.UseBouncyCastle(null); + BcAssembly = null; + ST("已取消注册BouncyCastle加密增强库", "Unregistered BouncyCastle encryption enhancement library"); + } + } catch (Exception e) { + S(T("BouncyCastle操作失败:", "BouncyCastle operation failed: ") + e.Message); + } + } + printEnv(); + S(); + + RSA_Util rsa = new RSA_Util(2048); + string[] Hashs = new string[] { + "SHA-1","SHA-256","SHA-224","SHA-384","SHA-512" + ,"SHA3-256","SHA3-224","SHA3-384","SHA3-512" + ,"SHA-512/224","SHA-512/256","MD5" + }; + + S("MessageDigest" + T("支持情况:", " support status:")); + { + var Ss = new List(Hashs); + Ss.Add("MD2"); + Ss.Add("SHAKE128"); Ss.Add("SHAKE256");//https://blog.csdn.net/weixin_42579622/article/details/111644921 + foreach (var s in Ss) { + var key = s; bool isBc; + + if (SupportHash(key, true, out isBc)) { + S(" " + key + " | Provider: " + (isBc ? "BouncyCastle" : ".NET")); + } else { + S(" [x] " + key); + } + } + } + + S("Encrypt Padding Mode" + T("支持情况:", " support status:")); + for (int i = 0; i < 1; i++) { + var v1 = i == 9999 ? "NONE" : "ECB"; + var Ss = new List(new string[] {"NoPadding" + ,"PKCS1Padding" + ,"OAEPPadding"}); + foreach (var s in Hashs) { + Ss.Add("OAEPwith" + s + "andMGF1Padding"); + } + foreach (var s in Ss) { + string key = "RSA/" + v1 + "/" + s, key2 = key; + RSA_Util.UseBouncyCastle(null); + for (var n = 0; n < 2; n++) { + try { + rsa.Encrypt(key, "123"); + S(" " + key + " | Provider: " + (n == 1 ? "BouncyCastle" : ".NET")); + } catch { + if (n == 0 && BcAssembly != null) { + RSA_Util.UseBouncyCastle(BcAssembly); + continue; + } + S(" [x] " + key); + } + break; + } + RSA_Util.UseBouncyCastle(BcAssembly); + } + } + + S("Signature Padding Mode" + T("支持情况:", " support status:")); + for (int i = 0; i < 3; i++) { + string v2 = i == 1 ? "/PSS" : ""; + string[] Ss = i == 2 ? new string[] { "RSASSA-PSS" } : Hashs; + foreach (var s in Ss) { + string key = i == 2 ? s : (s.Replace("SHA-", "SHA") + "withRSA" + v2), key2 = key; + RSA_Util.UseBouncyCastle(null); + for (var n = 0; n < 2; n++) { + try { + rsa.Sign(key, "123"); + S(" " + key + " | Provider: " + (n == 1 ? "BouncyCastle" : ".NET")); + } catch { + if (n == 0 && BcAssembly != null) { + RSA_Util.UseBouncyCastle(BcAssembly); + continue; + } + S(" [x] " + key); + } + break; + } + RSA_Util.UseBouncyCastle(BcAssembly); + } + } + + S(HR); + ST("测试一遍所有的加密、解密填充方式:", "Test all the encryption and decryption padding mode:"); + testPaddings(checkOpenSSL, rsa, new RSA_Util(rsa.ToPEM(true)), true); + + S(HR); + ST("Unsafe|是否要对调公钥私钥(私钥加密公钥解密)重新测试一遍?(Y/N) N", "Unsafe | Do you want to swap the public and private keys (private key encryption and public key decryption) and test again? (Y/N) N"); + Console.Write("> "); + string yn = ReadIn().Trim().ToUpper(); + if (yn == "Y") { + var rsaPri = rsa.SwapKey_Exponent_D__Unsafe(); + var rsaPub = new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe(); + testPaddings(checkOpenSSL, rsaPub, rsaPri, true); + } + } + /// 测试一遍所有的加密、解密填充方式 + static int testPaddings(bool checkOpenSSL, RSA_Util rsaPri, RSA_Util rsaPub, bool log) { + int errCount = 0; + var errMsgs = new List(); + var txt = "1234567890"; + if (!checkOpenSSL) { + txt += txt + txt + txt + txt; txt += txt;//100 + txt += txt + txt + txt + txt; txt += txt + "a";//1001 + } + byte[] txtData = Encoding.UTF8.GetBytes(txt); + + if (checkOpenSSL) { + try { + runOpenSSL(rsaPri.HasPrivate ? rsaPri : rsaPub, txtData); + } catch (Exception e) { + S(T("运行OpenSSL失败:", "Failed to run OpenSSL: ") + e.Message); + return errCount; + } + } + + var encKeys = RSA_Util.RSAPadding_Enc_DefaultKeys(); + foreach (var type in encKeys) { + var errMsg = ""; + try { + { + byte[] enc = rsaPub.Encrypt(type, txtData); + byte[] dec = rsaPri.Decrypt(type, enc); + bool isOk = true; + if (dec.Length != txtData.Length) { + isOk = false; + } else { + for (int i = 0; i < dec.Length; i++) { + if (dec[i] != txtData[i]) { + isOk = false; break; + } + } + } + if (!isOk) { + errMsg = T("解密结果不一致", "Decryption results are inconsistent"); + throw new Exception(errMsg); + } + } + if (checkOpenSSL) { + byte[] enc; + try { + enc = testOpenSSL(true, type); + } catch (Exception e) { + errMsg = "+OpenSSL: " + T("OpenSSL加密出错", "OpenSSL encryption error"); + throw e; + } + byte[] dec = rsaPri.Decrypt(type, enc); + bool isOk = true; + if (dec.Length != txtData.Length) { + isOk = false; + } else { + for (int i = 0; i < dec.Length; i++) { + if (dec[i] != txtData[i]) { + isOk = false; break; + } + } + } + if (!isOk) { + errMsg = "+OpenSSL: " + T("解密结果不一致", "Decryption results are inconsistent"); + throw new Exception(errMsg); + } + } + if (log) { + S(" " + (checkOpenSSL ? " [+OpenSSL]" : "") + " " + T("加密解密:", "Encryption decryption: ") + type + " | " + RSA_Util.RSAPadding_Enc(type)); + } + } catch (Exception e) { + if (!log && RSA_Util.IsDotNetSupportError(e.Message)) { + //NOOP + } else { + errCount++; + if (errMsg.Length == 0) errMsg = T("加密解密出现异常", "An exception occurred in encryption decryption"); + errMsg = " [x] " + errMsg + ": " + type + " | " + RSA_Util.RSAPadding_Enc(type); + S(errMsg); + errMsgs.Add(errMsg + T("。", ". ") + e.Message); + } + } + } + + var signKeys = RSA_Util.RSAPadding_Sign_DefaultKeys(); + foreach (var type in signKeys) { + var errMsg = ""; + try { + { + byte[] sign = rsaPri.Sign(type, txtData); + var isOk = rsaPub.Verify(type, sign, txtData); + if (!isOk) { + errMsg = T("未通过校验", "Failed verification"); + throw new Exception(errMsg); + } + } + if (checkOpenSSL) { + byte[] sign; + try { + sign = testOpenSSL(false, type); + } catch (Exception e) { + errMsg = "+OpenSSL: " + T("OpenSSL签名出错", "OpenSSL signature error"); + throw e; + } + var isOk = rsaPub.Verify(type, sign, txtData); + if (!isOk) { + errMsg = "+OpenSSL: " + T("未通过校验", "Failed verification"); + throw new Exception(errMsg); + } + } + if (log) { + S(" " + (checkOpenSSL ? " [+OpenSSL]" : "") + " " + T("签名验证:", "Signature verification: ") + type + " | " + RSA_Util.RSAPadding_Sign(type)); + } + } catch (Exception e) { + if (!log && RSA_Util.IsDotNetSupportError(e.Message)) { + //NOOP + } else { + errCount++; + if (errMsg.Length == 0) errMsg = T("签名验证出现异常", "An exception occurred in signature verification"); + errMsg = " [x] " + errMsg + ": " + type + " | " + RSA_Util.RSAPadding_Sign(type); + S(errMsg); + errMsgs.Add(errMsg + T("。", ". ") + e.Message); + } + } + } + if (log) { + if (errMsgs.Count == 0) { + ST("填充方式全部测试通过。", "All padding mode tests passed."); + } else { + ST("按回车键显示详细错误消息...", "Press Enter to display detailed error message..."); + ReadIn(); + } + } + if (errMsgs.Count > 0) { + S(string.Join("\n", errMsgs)); + } + closeOpenSSL(); + return errCount; + } + /// 多线程并发调用同一个RSA + static void threadRun() { + int ThreadCount = Math.Max(5, Environment.ProcessorCount - 1); + bool Abort = false; + int Count = 0; + int ErrCount = 0; + RSA_Util rsa = new RSA_Util(2048); + RSA_Util rsaPub = new RSA_Util(rsa.ToPEM(true)); + S(T("正在测试中,线程数:", "Under test, number of threads: ") + ThreadCount + T(",按回车键结束测试...", ", press enter to end the test...")); + + for (int i = 0; i < ThreadCount; i++) { + new Thread(() => { + while (!Abort) { + int err = testPaddings(false, rsa, rsaPub, false); + if (err > 0) { + Interlocked.Add(ref ErrCount, err); + } + Interlocked.Increment(ref Count); + } + }).Start(); + } + + long t1 = DateTime.Now.Ticks; + new Thread(() => { + while (!Abort) { + Console.Write("\r" + T("已测试" + Count + "次,", "Tested " + Count + " times, ") + + ErrCount + T("个错误,", " errors, ") + + T("耗时", "") + (DateTime.Now.Ticks - t1) / 10000 / 1000 + T("秒", " seconds total")); + try { + Thread.Sleep(1000); + } catch { } + } + }).Start(); + + ReadIn(); + Abort = true; + ST("多线程并发调用同一个RSA测试已结束。", "Multiple threads concurrently calling the same RSA test is over."); + S(); + } + + + + static void keyTools() { + ST("===== RSA密钥工具:生成密钥、转换密钥格式 =====" + , "===== RSA key tool: generate key, convert key format ====="); + ST("请使用下面可用命令进行操作,命令[]内的为可选参数,参数可用\"\"包裹。", "Please use the following commands to operate. The parameters in the command `[]` are optional parameters, and the parameters can be wrapped with \"\"."); + S(HR); + S("`new 1024 [-pkcs8] [saveFile [puboutFile]]`: " + T("生成新的RSA密钥,指定位数和格式:xml、pkcs1、或pkcs8(默认),提供saveFile可保存私钥到文件,提供puboutFile可额外保存一个公钥文件", "Generate a new RSA key, specify the number of digits and format: xml, pkcs1, or pkcs8 (default), provide saveFile to save the private key to a file, and provide puboutFile to save an additional public key file")); + S(HR); + S("`convert -pkcs1 [-pubout] [-swap] oldFile [newFile]`: " + T("转换密钥格式,提供已有密钥文件oldFile(支持xml、pem格式公钥或私钥),指定要转换成的格式:xml、pkcs1、或pkcs8,提供了-pubout时只导出公钥,提供了-swap时交换公钥指数私钥指数(非常规的:私钥加密公钥解密),提供newFile可保存到文件", "To convert the key format, provide the existing key file oldFile (support xml, pem format public key or private key), specify the format to be converted into: xml, pkcs1, or pkcs8, only export the public key when -pubout is provided, swap public key exponent and private key exponent when -swap is provided (unconventional: private key encryption and public key decryption), and provide newFile Can save to file")); + S(HR); + S("`exit`: " + T("输入 exit 退出工具", "Enter exit to quit the tool")); + while (true) { + loop: + Console.Write("> "); + var inStr = ReadIn().Trim(); + if (inStr.Length == 0) { + ST("输入为空,请重新输入!如需退出请输入exit", "The input is empty, please re-enter! If you need to exit, please enter exit"); + continue; + } + if (inStr.ToLower() == "exit") { + ST("bye! 已退出。", "bye! has exited."); + S(); + return; + } + var args = new List(); + Regex exp = new Regex("(-?)(?:([^\"\\s]+)|\"(.*?)\")\\s*"); + var sb = exp.Replace(inStr, (m) => { + var m1 = m.Groups[1].Value; + var m2 = m.Groups[2] == null ? "" : m.Groups[2].Value; + if (m2.Length > 0) { + args.Add(m1 + m2); + } else { + args.Add(m1 + m.Groups[3].Value); + } + return ""; + }); + if (sb.Length > 0) { + ST("参数无效:" + sb, "Invalid parameter: " + sb); + continue; + } + + var cmdName = args[0].ToLower(); args.RemoveAt(0); + bool nextSave = false; + RSA_Util rsa = null; string type = "", save = "", save2 = ""; bool pubOut = false; + + if (cmdName == "new") {// 生成新的pem密钥 + type = "pkcs8"; string len = ""; + while (args.Count > 0) { + string param = args[0], p = param.ToLower(); args.RemoveAt(0); + + var m = new Regex("^(\\d+)$").Match(p); + if (m.Success) { len = m.Groups[1].Value; continue; } + + m = new Regex("^-(xml|pkcs1|pkcs8)$").Match(p); + if (m.Success) { type = m.Groups[1].Value; continue; } + + if (save.Length == 0 && !p.StartsWith("-")) { save = param; continue; } + if (save2.Length == 0 && !p.StartsWith("-")) { save2 = param; continue; } + + ST("未知参数:" + param, "Unknown parameter: " + param); + goto loop; + } + if (len.Length == 0) { ST("请提供密钥位数!", "Please provide key digits!"); goto loop; } + try { + rsa = new RSA_Util(Convert.ToInt32(len)); + } catch (Exception e) { + S(T("生成密钥出错:", "Error generating key: ") + e.Message); + goto loop; + } + nextSave = true; + } + + if (cmdName == "convert") {// 转换密钥格式 + string old = ""; bool swap = false; + while (args.Count > 0) { + string param = args[0], p = param.ToLower(); args.RemoveAt(0); + + var m = new Regex("^-(xml|pkcs1|pkcs8)$").Match(p); + if (m.Success) { type = m.Groups[1].Value; continue; } + + if (p == "-pubout") { pubOut = true; continue; } + if (p == "-swap") { swap = true; continue; } + + if (old.Length == 0 && !p.StartsWith("-")) { old = param; continue; } + + if (save.Length == 0 && !p.StartsWith("-")) { save = param; continue; } + + ST("未知参数:" + param, "Unknown parameter: " + param); + goto loop; + } + if (type.Length == 0) { ST("请提供要转换成的格式!", "Please provide the format to convert to!"); goto loop; } + if (old.Length == 0) { ST("请提供已有密钥文件!", "Please provide an existing key file!"); goto loop; } + try { + var oldTxt = Encoding.UTF8.GetString(ReadFile(old)); + rsa = new RSA_Util(oldTxt); + if (swap) rsa = rsa.SwapKey_Exponent_D__Unsafe(); + } catch (Exception e) { + S(T("读取密钥文件出错", "Error reading key file ") + " (" + old + "): " + e.Message); + goto loop; + } + nextSave = true; + } + + while (nextSave) { + string val; + if (type == "xml") { + val = rsa.ToXML(pubOut); + } else { + bool pkcs8 = type == "pkcs8"; + val = rsa.ToPEM(false).ToPEM(pubOut, pkcs8, pkcs8); + } + if (save.Length == 0) { + S(val); + } else { + save = Path.GetFullPath(save); + try { + WriteFile(save, Encoding.UTF8.GetBytes(val)); + } catch (Exception e) { + S(T("保存文件出错", "Error saving file ") + " (" + save + "): " + e.Message); + } + S(T("密钥文件已保存到:", "The key file has been saved to: ") + save); + } + if (save2.Length > 0) { + save = save2; save2 = ""; + pubOut = true; + continue; + } + S(); + goto loop; + } + ST("未知命令:" + cmdName, "Unknown command: " + cmdName); + } + } + + + static RSA_PEM loadKey = null; static string loadKeyFile = ""; + /// 设置:加载密钥PEM文件 + static void setLoadKey() { + string path = ReadPath(T("密钥文件", "Key File") + , T(",或文件夹(内含private.pem、test.txt)。或输入'+1024 pkcs8'生成一个新密钥(填写位数、pkcs1、pkcs8)", ", or a folder (containing private.pem, test.txt). Or enter '+1024 pkcs8' to generate a new key (fill in digits, pkcs1, pkcs8) ")); + if (path.StartsWith("+")) {//创建一个新密钥 + Match m = new Regex("^\\+(\\d+)\\s+pkcs([18])$", RegexOptions.IgnoreCase).Match(path); + if (!m.Success) { + ST("格式不正确,请重新输入!", "The format is incorrect, please re-enter!"); + setLoadKey(); + } else { + int keySize = Convert.ToInt32(m.Groups[1].Value); + RSA_Util rsa = new RSA_Util(keySize); + bool isPkcs8 = m.Groups[2].Value == "8"; + RSA_PEM pem = rsa.ToPEM(false); + S(keySize + T("位私钥已生成,请复制此文本保存到private.pem文件:", " bit private key has been generated. Please copy this text and save it to the private.pem file:")); + S(pem.ToPEM(false, isPkcs8, isPkcs8)); + S(keySize + T("位公钥已生成,请复制此文本保存到public.pem文件:", " bit public key has been generated. Please copy this text and save it to the public.pem file:")); + S(pem.ToPEM(true, isPkcs8, isPkcs8)); + waitAnyKey = true; + } + return; + } + if (path.Length == 0 && loadKeyFile.Length == 0) { + ST("未输入文件,已取消操作", "No file input, operation cancelled"); + return; + } + if (path.Length == 0) { + path = loadKeyFile; + ST("重新加载密钥文件", "Reload key file"); + } + + if (Directory.Exists(path)) { + string txtPath = path + Path.DirectorySeparatorChar + "test.txt"; + path = path + Path.DirectorySeparatorChar + "private.pem"; + if (!File.Exists(path)) { + ST("此文件夹中没有private.pem文件!", "There is no private.pem file in this folder!"); + setLoadKey(); + return; + } + if (File.Exists(txtPath)) {//顺带加载文件夹里面的目标源文件 + loadSrcBytes = ReadFile(txtPath); + loadSrcFile = txtPath; + } + } + string txt = Encoding.UTF8.GetString(ReadFile(path)); + loadKey = RSA_PEM.FromPEM(txt); + loadKeyFile = path; + } + + static byte[] loadSrcBytes = null; static string loadSrcFile = ""; + /// 设置:加载目标源文件 + static void setLoadSrcBytes() { + string path = ReadPath(T("目标源文件", "Target Source File"), ""); + if (path.Length == 0 && loadSrcFile.Length == 0) { + ST("未输入文件,已取消操作", "No file input, operation cancelled"); + return; + } + if (path.Length == 0) { + path = loadSrcFile; + ST("重新加载目标源文件", "Reload target source file"); + } + loadSrcBytes = ReadFile(path); + loadSrcFile = path; + } + + static string encType = ""; + /// 设置加密填充模式 + static bool setEncType() { + S(T("请输入加密填充模式", "Please enter the encryption Padding mode") + + (encType.Length > 0 ? T(",回车使用当前值", ", press Enter to use the current value ") + encType : "") + + T(";填充模式取值可选:", "; Padding mode values: ") + string.Join(", ", RSA_Util.RSAPadding_Enc_DefaultKeys()) + + T(", 或其他支持的值", ", or other supported values")); + Console.Write("> "); + string val = ReadIn().Trim(); + if (val.Length > 0) { + encType = val; + } + if (encType.Length == 0) { + ST("未设置,已取消操作", "Not set, operation canceled"); + } + return encType.Length > 0; + } + /// 加密 + static void execEnc() { + string save = loadSrcFile + ".enc.bin"; + S(T("密钥文件:", "Key file: ") + loadKeyFile); + S(T("目标文件:", "Target file: ") + loadSrcFile); + S(T("填充模式:", "Padding mode: ") + encType + " | " + RSA_Util.RSAPadding_Enc(encType)); + ST("正在加密目标源文件...", "Encrypting target source file..."); + RSA_Util rsa = new RSA_Util(loadKey); + long t1 = DateTime.Now.Ticks; + byte[] data = rsa.Encrypt(encType, loadSrcBytes); + S(T("加密耗时:", "Encryption time: ") + (DateTime.Now.Ticks - t1) / 10000 + "ms"); + WriteFile(save, data); + S(T("已加密,结果已保存:", "Encrypted, the result is saved: ") + save); + } + /// 解密对比 + static void execDec() { + string encPath = loadSrcFile + ".enc.bin"; + S(T("密钥文件:", "Key file: ") + loadKeyFile); + S(T("密文文件:", "Ciphertext file: ") + encPath); + S(T("对比文件:", "Compare files: ") + loadSrcFile); + S(T("填充模式:", "Padding mode: ") + encType + " | " + RSA_Util.RSAPadding_Enc(encType)); + byte[] + data = ReadFile(encPath); + ST("正在解密文件...", "Decrypting file..."); + RSA_Util rsa = new RSA_Util(loadKey); + long t1 = DateTime.Now.Ticks; + byte[] val = rsa.Decrypt(encType, data); + S(T("解密耗时:", "Decryption time: ") + (DateTime.Now.Ticks - t1) / 10000 + "ms"); + WriteFile(loadSrcFile + ".dec.txt", val); + bool isOk = true; + if (val.Length != loadSrcBytes.Length) { + isOk = false; + } else { + for (int i = 0; i < val.Length; i++) { + if (val[i] != loadSrcBytes[i]) { + isOk = false; break; + } + } + } + if (isOk) { + ST("解密成功,和对比文件的内容一致。", "The decryption is successful, which is consistent with the content of the comparison file."); + return; + } + throw new Exception(T("解密结果和对比文件的内容不一致!", "The decryption result is inconsistent with the content of the comparison file!")); + } + + + static string signType = ""; + /// 设置签名hash+填充模式 + static bool setSignType() { + S(T("请输入签名Hash+填充模式", "Please enter the signature Hash+Padding mode") + + (signType.Length > 0 ? T(",回车使用当前值", ", press Enter to use the current value ") + signType : "") + + T(";签名模式取值可选:", "; Signature mode values: ") + string.Join(", ", RSA_Util.RSAPadding_Sign_DefaultKeys()) + + T(", 或其他支持的值", ", or other supported values")); + Console.Write("> "); + string val = ReadIn().Trim(); + if (val.Length > 0) { + signType = val; + } + if (signType.Length == 0) { + ST("未设置,已取消操作", "Not set, operation canceled"); + } + return signType.Length > 0; + } + /// 签名 + static void execSign() { + string save = loadSrcFile + ".sign.bin"; + S(T("密钥文件:", "Key file: ") + loadKeyFile); + S(T("目标文件:", "Target file: ") + loadSrcFile); + S(T("签名模式:", "Signature mode: ") + signType + " | " + RSA_Util.RSAPadding_Sign(signType)); + ST("正在给目标源文件签名...", "Signing target source file..."); + RSA_Util rsa = new RSA_Util(loadKey); + byte[] data = rsa.Sign(signType, loadSrcBytes); + WriteFile(save, data); + S(T("已签名,结果已保存:", "Signed, results saved: ") + save); + } + /// 验证签名 + static void execVerify() { + string binPath = loadSrcFile + ".sign.bin"; + S(T("密钥文件:", "Key file: ") + loadKeyFile); + S(T("目标文件:", "Target file: ") + loadSrcFile); + S(T("签名文件:", "Signature file: ") + binPath); + S(T("签名模式:", "Signature mode: ") + signType + " | " + RSA_Util.RSAPadding_Sign(signType)); + byte[] data = ReadFile(binPath); + ST("正在验证签名...", "Verifying signature..."); + RSA_Util rsa = new RSA_Util(loadKey); + bool val = rsa.Verify(signType, data, loadSrcBytes); + if (val) { + ST("签名验证成功。", "Signature verification successful."); + return; + } + throw new Exception(T("签名验证失败!", "Signature verification failed!")); + } + + + + + + /// 调用openssl相关测试代码 + static void runOpenSSL(RSA_Util rsa, byte[] data) { + var shell = "/bin/bash"; + if (NET_IsWindows()) { + shell = "cmd"; + } + + S(T("正在打开OpenSSL...", "Opening OpenSSL...") + " Shell: " + shell); + closeOpenSSL(); + openSSLProc = new Process(); + ProcessStartInfo info = openSSLProc.StartInfo; + info.FileName = shell; + info.UseShellExecute = false; + info.RedirectStandardError = true; + info.RedirectStandardInput = true; + info.RedirectStandardOutput = true; + info.CreateNoWindow = true; + openSSLProc.Start(); + + openSSLBuffer = new StringBuilder(); + openSSLErrBuffer = new StringBuilder(); + var threadSync = openSSLThreadSync; + openSSLThread1 = new Thread(() => { + try { + while (openSSLThreadSync == threadSync) { + var line = openSSLProc.StandardOutput.ReadLine(); + if (line != null) { + openSSLBuffer.Append(line).Append('\n'); + } + } + } catch { } + }); + openSSLThread2 = new Thread(() => { + try { + while (openSSLThreadSync == threadSync) { + var line = openSSLProc.StandardError.ReadLine(); + if (line != null) { + openSSLErrBuffer.Append(line).Append('\n'); + } + } + } catch { } + }); + openSSLThread1.Start(); + openSSLThread2.Start(); + + WriteFile("test_openssl_key.pem", Encoding.UTF8.GetBytes(rsa.ToPEM(false).ToPEM_PKCS8(false))); + WriteFile("test_openssl_data.txt", data); + + byte[] no = new byte[rsa.KeySize / 8]; + Array.Copy(data, 0, no, no.Length - data.Length, data.Length); + WriteFile("test_openssl_data.txt.nopadding.txt", no); + + openSSLProc.StandardInput.Write("openssl version\necho " + openSSLBoundary + "\n"); + openSSLProc.StandardInput.Flush(); + while (true) { + if (openSSLBuffer.ToString().IndexOf(openSSLBoundary) != -1) { + if (openSSLErrBuffer.Length > 0) { + closeOpenSSL(); + throw new Exception(T("打开OpenSSL出错:", "Error opening OpenSSL: ") + openSSLErrBuffer.ToString().Trim()); + } + S("OpenSSL Version: " + openSSLBuffer.ToString().Trim()); + break; + } + Thread.Sleep(10); + } + } + static private Process openSSLProc; + static private StringBuilder openSSLBuffer, openSSLErrBuffer; + static private Thread openSSLThread1, openSSLThread2; + static private int openSSLThreadSync; + static private readonly string openSSLBoundary = "--openSSL boundary--"; + static void closeOpenSSL() { + openSSLThreadSync++; + if (openSSLProc == null) return; + try { + openSSLProc.Kill(); + openSSLProc.Dispose(); + } catch { } + openSSLProc = null; + } + static byte[] testOpenSSL(bool encOrSign, string mode) { + bool debug = false; string cmd = ""; + string keyFile = "test_openssl_key.pem", txtFile = "test_openssl_data.txt"; + string save = txtFile + (encOrSign ? ".enc.bin" : ".sign.bin"); + if (encOrSign) {//加密 + if (mode == "NO") { + cmd = "openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:none -in " + txtFile + ".nopadding.txt -inkey " + keyFile + " -out " + save; + } else if (mode == "PKCS1") { + cmd = "openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in " + txtFile + " -inkey " + keyFile + " -out " + save; + } else if (mode.StartsWith("OAEP+")) { + string hash = mode.Replace("OAEP+", "").Replace("-512/", "512-"); + cmd = "openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:" + hash + " -in " + txtFile + " -inkey " + keyFile + " -out " + save; + } + } else {//签名 + if (mode.StartsWith("PKCS1+")) { + string hash = mode.Replace("PKCS1+", "").Replace("-512/", "512-"); + cmd = "openssl dgst -" + hash + " -binary -sign " + keyFile + " -out " + save + " " + txtFile; + } else if (mode.StartsWith("PSS+")) { + string hash = mode.Replace("PSS+", "").Replace("-512/", "512-"); + cmd = "openssl dgst -" + hash + " -binary -out " + txtFile + ".hash " + txtFile; + cmd += "\n"; + cmd += "openssl pkeyutl -sign -pkeyopt digest:" + hash + " -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in " + txtFile + ".hash -inkey " + keyFile + " -out " + save; + } + } + if (cmd.Length == 0) { + string msg = T("无效mode:", "Invalid mode: ") + mode; + S("[OpenSSL Code Error] " + msg); + throw new Exception(msg); + } + if (File.Exists(save)) { + File.Delete(save); + } + + if (debug) S("[OpenSSL Cmd][" + mode + "]" + cmd); + openSSLBuffer.Length = 0; + openSSLErrBuffer.Length = 0; + openSSLProc.StandardInput.Write(cmd + "\n"); + openSSLProc.StandardInput.Write("echo " + openSSLBoundary + "\n"); + openSSLProc.StandardInput.Flush(); + + while (true) { + if (openSSLBuffer.ToString().IndexOf(openSSLBoundary) != -1) { + if (openSSLErrBuffer.Length > 0) { + if (debug) S("[OpenSSL Error]\n" + openSSLErrBuffer + "\n[End]"); + throw new Exception("OpenSSL Error: " + openSSLErrBuffer.ToString().Trim()); + } + if (debug) S("[OpenSSL Output]\n" + openSSLBuffer + "\n[End] save:" + Path.GetFullPath(save)); + break; + } + Thread.Sleep(10); + } + return ReadFile(save); + } + + static void showOpenSSLTips() { + ST("===== OpenSSL中RSA相关的命令行调用命令 =====" + , "===== RSA-related command-line invocation commands in OpenSSL ====="); + S(); + ST("::先准备一个测试文件 test.txt 里面填少量内容,openssl不支持自动分段加密" + , "::First prepare a test file test.txt and fill in a small amount of content, openssl does not support automatic segmentation encryption"); + S(); + ST("::生成新密钥", "::Generate new key"); + S("openssl genrsa -out private.pem 1024"); + S(); + ST("::提取公钥PKCS#8", "::Extract public key PKCS#8"); + S("openssl rsa -in private.pem -pubout -out public.pem"); + S(); + ST("::转换成RSAPublicKey PKCS#1", "::Convert to RSAPublicKey PKCS#1"); + S("openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public.pem.rsakey"); + ST("::测试RSAPublicKey PKCS#1,不出意外会出错。因为这个公钥里面没有OID,通过RSA_PEM转换成PKCS#8自动带上OID就能正常加密" + , "::Test RSAPublicKey PKCS#1, no accident will go wrong. Because there is no OID in this public key, it can be encrypted normally by converting RSA_PEM into PKCS#8 and automatically bringing OID"); + S("echo abcd123 | openssl rsautl -encrypt -inkey public.pem.rsakey -pubin"); + S(); + S(); + S(); + ST("::加密和解密,填充方式:PKCS1" + , "::Encryption and decryption, padding mode: PKCS1"); + S("openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin"); + S("openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt"); + S(); + ST("::加密和解密,填充方式:OAEP+SHA256,掩码生成函数MGF1使用相同的hash算法" + , "::Encryption and decryption, padding mode: OAEP+SHA256, mask generation function MGF1 uses the same hash algorithm"); + S("openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin"); + S("openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt"); + S(); + S(); + ST("::命令行参数中的sha256可以换成md5、sha1等;如需sha3系列,就换成sha3-256即可" + , "::The sha256 in the command line parameters can be replaced by md5, sha1, etc.; if you need the sha3 series, you can replace it with sha3-256"); + S(); + S(); + ST("::签名和验证,填充方式:PKCS1+SHA256", "::Signature and verification, padding mode: PKCS1+SHA256"); + S("openssl dgst -sha256 -binary -sign private.pem -out test.txt.sign.bin test.txt"); + S("openssl dgst -sha256 -binary -verify public.pem -signature test.txt.sign.bin test.txt"); + S(); + ST("::签名和验证,填充方式:PSS+SHA256 ,salt=-1使用hash长度=256/8,掩码生成函数MGF1使用相同的hash算法" + , "::Signature and verification, padding mode: PSS+SHA256, salt=-1 use hash length=256/8, mask generation function MGF1 uses the same hash algorithm"); + S("openssl dgst -sha256 -binary -out test.txt.hash test.txt"); + S("openssl pkeyutl -sign -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -inkey private.pem -out test.txt.sign.bin"); + S("openssl pkeyutl -verify -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -pubin -inkey public.pem -sigfile test.txt.sign.bin"); + S(); + S(); + } + + + + + static bool waitAnyKey = true; + static void ShowMenu(string[] args) { + if (args != null && args.Length > 0) { + foreach (var v in args) { + if (v.StartsWith("-zh=")) { + RSA_PEM.Lang = v.StartsWith("-zh=1") ? "zh" : "en"; + } + } + S(args.Length + T("个启动参数:", " startup parameters: ") + string.Join(" ", args)); + S(); + } + + bool newRun = true; + while (true) { + if (newRun) { + newRun = false; + S("====== https://github.com/xiangyuecn/RSA-csharp ======"); + printEnv(); + S(HR); + } + + var isSet = loadKeyFile.Length > 0 && loadSrcFile.Length > 0; + var setTips = isSet ? "" : " " + T("[不可用]请先设置4、5", "[Unavailable] Please set 4, 5 first") + " "; + var floadTips = T("[已加载,修改后需重新加载]", "[loaded, need to reload after modification]"); + var fileName = loadSrcFile.Length > 0 ? Path.GetFileName(loadSrcFile) : "test.txt"; + + S(T("【功能菜单】", "[ Menu ]") + " .NET Version: " + NET_Ver()); + S("1. " + T("测试:运行基础功能测试(1次)", "Test: Run basic functional tests (1 time)")); + S("2. " + T("测试:运行基础功能测试(1000次)", "Test: Run basic functional tests (1000 times)")); + S("3. " + T("测试:多线程并发调用同一个RSA", "Test: Multiple threads call the same RSA concurrently")); + S(HR); + S("4. " + T("设置:加载密钥PEM文件", "Setup: Load key PEM file") + (loadKeyFile.Length > 0 ? " " + floadTips + Path.GetFileName(loadKeyFile) + " " + loadKey.KeySize + " bits" : "")); + S("5. " + T("设置:加载目标源文件", "Setup: Load Target Source File") + (loadSrcFile.Length > 0 ? " " + floadTips + fileName + " " + loadSrcBytes.Length + " Bytes" : "")); + S("6. " + T("加密 ", "Encrypt") + setTips + " " + fileName + " -> " + fileName + ".enc.bin"); + S("7. " + T("解密对比", "Decrypt") + setTips + " " + fileName + ".enc.bin -> " + fileName + ".dec.txt"); + S("8. " + T("签名 ", "Sign ") + setTips + " " + fileName + " -> " + fileName + ".sign.bin"); + S("9. " + T("验证签名", "Verify ") + setTips + " " + fileName + ".sign.bin"); + S(HR); + S("A. " + T("RSA密钥工具:生成密钥、转换密钥格式", "RSA key tool: generate key, convert key format")); + S("B. " + T("显示当前环境支持的加密和签名填充模式,输入 B2 可同时对比OpenSSL结果", "Display the encryption and signature padding modes supported by the current environment, enter B2 to compare OpenSSL results at the same time") + + " (" + (CanLoad_BouncyCastle() ? (BcAssembly == null ? + T("可注册BouncyCastle加密增强库", "Can register BouncyCastle encryption enhancement library") + : T("已注册BouncyCastle加密增强库", "BouncyCastle encryption enhancement library registered") + ) : T("未检测到BouncyCastle加密增强库", "BouncyCastle encryption enhancement library was not detected")) + ")"); + S("C. " + T("显示OpenSSL中RSA相关的命令行调用命令", "Display RSA-related command line calls in OpenSSL")); + S("*. " + T("输入 exit 退出,输入 lang=zh|en 切换显示语言", "Enter exit to exit, enter lang=zh|en to switch display language") + + (RSA_Util.IS_CORE ? "" : T(",输入 net45 或 net46 切换高低版本Framework兼容模式", ", enter net45 or net46 to switch between high and low version Framework compatibility mode") + + " (" + T("当前为:", "Currently: ") + (RSA_Util.IS_CoreOr46 ? "net46" : "net45") + ")")); + S(); + ST("请输入菜单序号:", "Please enter the menu number:"); + Console.Write("> "); + + waitAnyKey = true; + while (true) { + var inTxt = ReadIn().Trim().ToUpper(); + + try { + if (inTxt == "1") { + RSATest(false); + } else if (inTxt == "2") { + for (int i = 0; i < 1000; i++) { ST("第" + i + "次>>>>>", i + "th time>>>>>"); RSATest(true); } + } else if (inTxt == "3") { + waitAnyKey = false; + threadRun(); + } else if (inTxt == "4") { + waitAnyKey = false; + setLoadKey(); + } else if (inTxt == "5") { + waitAnyKey = false; + setLoadSrcBytes(); + } else if (isSet && inTxt == "6") { + bool next = setEncType(); + if (next) { + execEnc(); + } + } else if (isSet && inTxt == "7") { + bool next = setEncType(); + if (next) { + execDec(); + } + } else if (isSet && inTxt == "8") { + bool next = setSignType(); + if (next) { + execSign(); + } + } else if (isSet && inTxt == "9") { + bool next = setSignType(); + if (next) { + execVerify(); + } + } else if (inTxt == "A") { + waitAnyKey = false; + keyTools(); + } else if (inTxt == "B" || inTxt == "B2") { + testProvider(inTxt == "B2"); + } else if (inTxt == "C") { + showOpenSSLTips(); + } else if (inTxt.StartsWith("LANG=")) { + waitAnyKey = false; newRun = true; + if (inTxt == "LANG=ZH") { + RSA_PEM.Lang = "zh"; + S("已切换语言成简体中文"); + } else if (inTxt == "LANG=EN") { + RSA_PEM.Lang = "en"; + S("Switched language to English-US"); + } else { + waitAnyKey = true; newRun = false; + ST("语言设置命令无效!", "Invalid language setting command!"); + } + } else if (inTxt == "NET45" || inTxt == "NET46") { + if (RSA_Util.IS_CORE) { + ST(".NET Core下无需进行此配置", "This configuration is not required under .NET Core"); + } else if (inTxt == "NET45") { + RSA_Util.IS_CoreOr46_Test_Set(-1); + ST("已配置使用.NET Framework 4.5及以下版本模式进行测试", "Configured to use .NET Framework 4.5 and below version mode for testing"); + } else { + RSA_Util.IS_CoreOr46_Test_Set(1); + ST("已配置使用.NET Framework 4.6及以上版本模式进行测试", "Configured to use .NET Framework 4.6 and above version mode for testing"); + } + } else if (inTxt == "EXIT") { + S("bye!"); + return; + } else { + inTxt = ""; + ST("序号无效,请重新输入菜单序号!", "The menu number is invalid, please re-enter the menu number!"); + Console.Write("> "); + continue; + } + } catch (Exception e) { + S(e.ToString()); + Thread.Sleep(100); + waitAnyKey = true; + } + break; + } + + if (waitAnyKey) { + ST("按任意键继续...", "Press any key to continue..."); + Console.ReadKey(); + } + S(); + } + } + + } } diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index 36132e0..0000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// 有关程序集的常规信息通过以下 -// 特性集控制。更改这些特性值可修改 -// 与程序集关联的信息。 -[assembly: AssemblyTitle("https://github.com/xiangyuecn/RSA-csharp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("RSA-csharp")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 将 ComVisible 设置为 false 使此程序集中的类型 -// 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, -// 则将该类型上的 ComVisible 特性设置为 true。 -[assembly: ComVisible(false)] - -// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID -[assembly: Guid("7661a9da-7f07-403a-8e49-5224ae79a009")] - -// 程序集的版本信息由下面四个值组成: -// -// 主版本 -// 次版本 -// 生成号 -// 修订号 -// -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README-English.md b/README-English.md new file mode 100644 index 0000000..3358702 --- /dev/null +++ b/README-English.md @@ -0,0 +1,380 @@ +# :open_book:RSA-csharp Usage Documentation + +> This document is translated from Chinese to English using Google Translate. + +**Functions of this project: support `PEM` (`PKCS#1`, `PKCS#8`) format RSA key generation, import, and export in `.NET Core` and `.NET Framework` environments; a variety of common RSA encryption signatures padding algorithm support.** + +- Support .NET Framework 4.5+, .NET Standard 2.0+ (.NET Core 2.0+, .NET 5+) +- RSA can be created through `PEM`, `XML` format keys +- RSA can be created by specifying key digits and key parameters +- Can export `PEM`, `XML` format public key, private key; format mutual conversion +- Public key encryption, private key decryption: `NoPadding`, `PKCS1Padding`, `OAEP+MD5`, `OAEP+SHA1 ... SHA3-512` +- Private key signature, public key verification: `PKCS1+SHA1 ... SHA3-512`, `PKCS1+MD5`, `PSS+SHA1 ... SHA3-512` +- Unconventional: private key encryption, public key decryption, public key signature, private key verification +- Multilingual support: provide Chinese and English language support +- There is also a Java version [RSA-java](https://github.com/xiangyuecn/RSA-java), all encrypted signature algorithms are interoperable in `Java`, `.NET`, `OpenSSL` +- The source code is simple, and compile and test `.bat|.sh` scripts are provided. The source code can be modified and run without Visual Studio, and can be used by copying + +[​](?) + +You can just copy the `RSA_PEM.cs` and `RSA_Util.cs` files to your project to use all the functions. You can also clone the entire project code and double-click `Test-Build-Run.bat` to run the test directly (macOS, linux use the terminal to run `.sh`), through the `scripts/Create-dll.bat(sh)` script can generate dll files for project reference. + +The underlying implementation of the `RSA_PEM` class uses bytecode parsing of the PEM file at the binary level, which is simple, lightweight, and zero-dependent; `RSA_Util` is an encapsulation of the RSA operation class, which supports cross-platform use, and can optionally be used with the `BouncyCastle` encryption enhancement library Richer support for cryptographic signature modes is available. + + +Source Files|Platform Support|Instructions|Dependencies +:-:|:-:|:-|:- +**RSA_PEM.cs**|.NET Core, .NET Framework|Used to parse and export PEM, create RSA instance|NONE +**RSA_Util.cs**|.NET Core, .NET Framework|RSA operation class, which encapsulates encryption, decryption, and signature verification|RSA_PEM +Program.cs|.NET Core, .NET Framework|Test console program|RSA_PEM, RSA_Util + +[​](?) + +**Screenshot of Test-Build-Run.bat test compilation and operation:** + +![console test](images/1-en.png) + + +[​](?) + +[​](?) + +## Quick Start: Encryption, Decryption, Signature, Verification + +### Step 1: Reference RSA-csharp +- Method 1: Copy the `RSA_PEM.cs` and `RSA_Util.cs` files directly to your project and use them. +- Method 2: Use the `scripts/Create-dll.bat(sh)` script to generate a dll file, and add the reference of this dll to the project to use it. +- Method 3: Download the corresponding version of the dll file in Releases (that is, the dll generated by the method 2 script), and add the reference of this dll to the project to use it. + +> Note: The .NET Framework project may needs to manually reference the `System.Numerics` assembly to support `BigInteger`. The project created by vs by default does not automatically import this assembly, and the .NET Core project does not need this operation. + +### Step 2: Write the code +``` c# +//Parse pem or xml first, and both public and private keys can be parsed +//var pem=RSA_PEM.FromPEM("-----BEGIN XXX KEY-----....-----END XXX KEY-----"); +//var pem=RSA_PEM.FromXML("...."); + +//Directly create RSA operation classes, which can be created as global objects, and both encryption and decryption signatures support concurrent calls +//var rsa=new RSA_Util(pem); +var rsa=new RSA_Util(2048); //You can also directly generate a new key, rsa.ToPEM() gets the pem object + +//Optionally register the BouncyCastle encryption enhancement library (just register once when the program starts), which is used to implement the encryption signature padding mode not supported by .NET, NuGet: Portable.BouncyCastle or BouncyCastle.Cryptography +//RSA_Util.UseBouncyCastle(typeof(RsaEngine).Assembly); + +//Encrypt with public key, padding mode: PKCS1, you can use OAEP+SHA256 and other padding modes +var enTxt=rsa.Encrypt("PKCS1", "test123"); +//Decrypt with private key +var deTxt=rsa.Decrypt("PKCS1", enTxt); + +//Sign with private key, padding mode: PKCS1+SHA1, PSS+SHA256 and other padding modes can be used +var sign=rsa.Sign("PKCS1+SHA1", "test123"); +//Verify with public key +var isVerify=rsa.Verify("PKCS1+SHA1", sign, "test123"); + +//Export PEM text +var pemTxt=rsa.ToPEM().ToPEM_PKCS8(); + +//Unconventional (unsafe, not recommended): private key encryption, public key decryption, public key signature, private key verification +RSA_Util rsaS_Private=rsa.SwapKey_Exponent_D__Unsafe(); +RSA_Util rsaS_Public=new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe(); +//... rsaS_Private.Encrypt rsaS_Public.Decrypt +//... rsaS_Public.Sign rsaS_Private.Verify + +Console.WriteLine(pemTxt+"\n"+enTxt+"\n"+deTxt+"\n"+sign+"\n"+isVerify); +Console.ReadLine(); +//****For more examples, please read Program.cs**** +//****For more functional methods, please read the detailed documentation below**** +``` + +**If you need function customization, website, app, small program development, etc., please add the QQ group below and contact the group owner (ie the author), thank you~** + + + +[​](?) + +## [QQ group] communication and support + +Welcome to join QQ group: 421882406, pure lowercase password: `xiangyuecn` + + + + + + + + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +# :open_book:Documentation + +## Encryption Paddings + +> In the table below, Frame is the support of .NET Framework, Core is the support of .NET Core, and BC is the support of the BouncyCastle encryption enhancement library (can be registered through the RSA_Util.UseBouncyCastle method); √ means support, × means no support, and other values are A certain version starts to support; among them, the mask generation function MGF1 of OAEP uses the same Hash algorithm as OAEP. + +Padding|Algorithm|Frame|Core|BC +:-|:-|:-:|:-:|:-: +NO|RSA/ECB/NoPadding|√|√|√ +PKCS1 |RSA/ECB/PKCS1Padding|√|√|√ +OAEP+SHA1 |RSA/ECB/OAEPwithSHA-1andMGF1Padding|√|√|√ +OAEP+SHA256|RSA/ECB/OAEPwithSHA-256andMGF1Padding|4.6+|√|√ +OAEP+SHA224|RSA/ECB/OAEPwithSHA-224andMGF1Padding|×|×|√ +OAEP+SHA384|RSA/ECB/OAEPwithSHA-384andMGF1Padding|4.6+|√|√ +OAEP+SHA512|RSA/ECB/OAEPwithSHA-512andMGF1Padding|4.6+|√|√ +OAEP+SHA-512/224|RSA/ECB/OAEPwithSHA-512/224andMGF1Padding|×|×|√ +OAEP+SHA-512/256|RSA/ECB/OAEPwithSHA-512/256andMGF1Padding|×|×|√ +OAEP+SHA3-256|RSA/ECB/OAEPwithSHA3-256andMGF1Padding|×|8+|√ +OAEP+SHA3-224|RSA/ECB/OAEPwithSHA3-224andMGF1Padding|×|×|√ +OAEP+SHA3-384|RSA/ECB/OAEPwithSHA3-384andMGF1Padding|×|8+|√ +OAEP+SHA3-512|RSA/ECB/OAEPwithSHA3-512andMGF1Padding|×|8+|√ +OAEP+MD5 |RSA/ECB/OAEPwithMD5andMGF1Padding|4.6+|√|√ + + + +## Signature Paddings + +> In the table below, Frame is the support of .NET Framework, Core is the support of .NET Core, and BC is the support of the BouncyCastle encryption enhancement library (can be registered through the RSA_Util.UseBouncyCastle method); √ means support, × means no support, and other values are A certain version starts to support; the number of salt bytes of PSS is equal to the number of bytes of the hash algorithm used, the mask generation function MGF1 of PSS uses the same hash algorithm as that of PSS, and the value of the trailing attribute TrailerField is fixed at 0xBC. + +Padding|Algorithm|Frame|Core|BC +:-|:-|:-:|:-:|:-: +SHA1 ... SHA3-512|Same as PKCS1+SHA***||| +PKCS1+SHA1 |SHA1withRSA|√|√|√ +PKCS1+SHA256|SHA256withRSA|√|√|√ +PKCS1+SHA224|SHA224withRSA|×|×|√ +PKCS1+SHA384|SHA384withRSA|√|√|√ +PKCS1+SHA512|SHA512withRSA|√|√|√ +PKCS1+SHA-512/224|SHA512/224withRSA|×|×|√ +PKCS1+SHA-512/256|SHA512/256withRSA|×|×|√ +PKCS1+SHA3-256|SHA3-256withRSA|×|8+|√ +PKCS1+SHA3-224|SHA3-224withRSA|×|×|√ +PKCS1+SHA3-384|SHA3-384withRSA|×|8+|√ +PKCS1+SHA3-512|SHA3-512withRSA|×|8+|√ +PKCS1+MD5 |MD5withRSA|√|√|√ +PSS+SHA1 |SHA1withRSA/PSS|4.6+|√|√ +PSS+SHA256|SHA256withRSA/PSS|4.6+|√|√ +PSS+SHA224|SHA224withRSA/PSS|×|×|√ +PSS+SHA384|SHA384withRSA/PSS|4.6+|√|√ +PSS+SHA512|SHA512withRSA/PSS|4.6+|√|√ +PSS+SHA-512/224|SHA512/224withRSA/PSS|×|×|√ +PSS+SHA-512/256|SHA512/256withRSA/PSS|×|×|√ +PSS+SHA3-256|SHA3-256withRSA/PSS|×|8+|√ +PSS+SHA3-224|SHA3-224withRSA/PSS|×|×|√ +PSS+SHA3-384|SHA3-384withRSA/PSS|×|8+|√ +PSS+SHA3-512|SHA3-512withRSA/PSS|×|8+|√ +PSS+MD5 |MD5withRSA/PSS|4.6+|√|√ + + + +[​](?) + +[​](?) + +## RSA_PEM Class Documentation +The `RSA_PEM.cs` file does not depend on any files, you can directly copy this file to use in your project; through `FromPEM`, `ToPEM` and `FromXML`, `ToXML` two pairs of methods, you can implement PEM `PKCS#1`, `PKCS#8` mutual conversion, PEM, XML mutual conversion. + +Note: `openssl rsa -in privateKey -pubout` exports PKCS#8 format public key (used more), `openssl rsa -pubin -in PKCS#8 publicKey -RSAPublicKey_out` exports PKCS#1 format public key (rarely used). + + +### Static Attributes and Methods + +`RSA_PEM` **FromPEM(string pem)**: Create RSA with PEM format key, support PKCS#1, PKCS#8 format PEM, error will throw an exception. pem format such as: `-----BEGIN XXX KEY-----....-----END XXX KEY-----`. + +`RSA_PEM` **FromXML(string xml)**: Convert the key in XML format to PEM, support public key xml, private key xml, and an exception will be thrown if an error occurs. xml format such as: `....`. + +`string` **T(string zh, string en)**: Simplified multi-language support, returns Chinese or English according to the current language `Lang` value. + +`string` **Lang**: Simplified multi-language support, value: `zh` (Simplified Chinese), `en` (English-US), the default value is based on the system, and the specified language can be assigned. + + +### Construction Methods + +**RSA_PEM(RSA rsa, bool convertToPublic = false)**: Construct a PEM through the public key or private key in RSA. If convertToPublic, the RSA containing the private key will only read the public key, and the RSA containing only the public key will not be affected. + +**RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ)**: Construct a PEM through the full amount of PEM field parameters. Except for the modulus and public key exponent that must be provided, other private key exponent information must be provided or not provided at all (the exported PEM only contains the public key) Note: all If the first byte of the parameter is 0, it must be removed first. + +**RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull)**: Construct a PEM through the public key exponent and the private key exponent, and P and Q will be calculated in reverse, but they are extremely unlikely to be the same as the P and Q of the original generated key. Note: If the first byte of all parameters is 0, it must be removed first. Errors will throw exceptions. The private key exponent may not be provided, and the exported PEM only contains the public key. + + +### Instance Attributes + +`byte[]`: **Key_Modulus**(Modulus n, both public key and private key), **Key_Exponent**(Public key exponent e, both public key and private key), **Key_D**(Private key exponent d, only available when private key); These 3 are enough for encryption and decryption. + +`byte[]`: **Val_P**(prime1), **Val_Q**(prime2), **Val_DP**(exponent1), **Val_DQ**(exponent2), **Val_InverseQ**(coefficient); The private key in PEM has more values; these values can be deduced through n, e, and d (only the effective value is deduced, which is different from the original value with high probability). + +`int` **KeySize**: Key digits. + +`bool` **HasPrivate**: Whether to include the private key. + + +### Instance Methods + +`RSA` **GetRSA_ForCore()**: Convert the public key or private key in PEM into an RSA object. If the private key is not provided, RSA only contains the public key. The returned RSA supports cross-platform use, but only supports use in the .NET Core environment. + +`RSACryptoServiceProvider` **GetRSA_ForWindows()**: Convert the public key or private key in PEM into an RSA object. If the private key is not provided, RSA only contains the public key. Both .NET Core and .NET Framework are available, but the returned RSACryptoServiceProvider does not support cross-platform, so it can only be used in Windows systems. + +`void` **GetRSA__ImportParameters(RSA rsa)**: Import the key parameter into the RSA object, this method is called in `GetRSA_ForCore`, `GetRSA_ForWindows`, and can be used to create other types of RSA. + +`RSA_PEM` **CopyToNew(bool convertToPublic = false)**: Copy the key in the current PEM to a new PEM object. convertToPublic: When equal to true, the PEM containing the private key will only return the public key, and the PEM containing only the public key will not be affected. + +`RSA_PEM` **SwapKey_Exponent_D__Unsafe()**: [Unsafe and not recommended] Swap the public key exponent (Key_Exponent) and the private key exponent (Key_D): use the public key as the private key (new.Key_D=this.Key_Exponent) and the private key as the public key (new.Key_Exponent=this.Key_D), returns a new PEM object; for example, used for: private key encryption, public key decryption, this is an unconventional usage. The current object must contain a private key, otherwise an exception will be thrown if it cannot be swapped. Note: It is very insecure to use the public key as a private key, because the public key exponent of most generated keys is 0x10001 (AQAB), which is too easy to guess and cannot be used as a real private key. + +`byte[]` **ToDER(bool convertToPublic, bool privateUsePKCS8, bool publicUsePKCS8)**: Convert the key pair in RSA to DER format. The DER format is binary data before Base64 text encoding in PEM. Refer to the ToPEM method for parameter meanings. + +`string` **ToPEM(bool convertToPublic, bool privateUsePKCS8, bool publicUsePKCS8)**: Convert the key in RSA to PEM format. convertToPublic: When it is equal to true, the RSA containing the private key will only return the public key, and the RSA containing only the public key will not be affected. **privateUsePKCS8**: The return format of the private key, when it is equal to true, it returns the PKCS#8 format (`-----BEGIN PRIVATE KEY-----`), otherwise returns PKCS#1 format (`-----BEGIN RSA PRIVATE KEY-----`), this parameter is invalid when returning a public key; Both formats are used more commonly. **publicUsePKCS8**: The return format of the public key, when it is equal to true, it returns the PKCS#8 format (`-----BEGIN PUBLIC KEY-----`), otherwise returns PKCS#1 format (`-----BEGIN RSA PUBLIC KEY-----`), this parameter is invalid when returning the private key; Generally, the true PKCS#8 format public key is mostly used, and the PKCS#1 format public key seems to be relatively rare. + +`string` **ToPEM_PKCS1(bool convertToPublic=false)**: Simplified writing of the ToPEM method, regardless of the public key or the private key, it returns the PKCS#1 format; it seems that the export of the PKCS#1 public key is less used, and the PKCS#8 public key is used more, and the private key #1#8 is almost. + +`string` **ToPEM_PKCS8(bool convertToPublic=false)**: Simplified writing of the ToPEM method, regardless of whether the public key or the private key returns the PKCS#8 format. + +`string` **ToXML(bool convertToPublic)**: Convert the key in RSA to XML format. If convertToPublic, the RSA containing the private key will only return the public key, and the RSA containing only the public key will not be affected. + + + + +[​](?) + +[​](?) + +## RSA_Util Class Documentation +The `RSA_Util.cs` file depends on `RSA_PEM.cs`, which encapsulates encryption, decryption, signature, verification, and key import and export operations; .NET Core is supported by the actual RSA implementation class, .NET Framework 4.5 and below is supported by RSACryptoServiceProvider, .NET Framework 4.6 and above is supported by RSACng; or the BouncyCastle encryption enhancement library is introduced to provide support. + + +### Static Attributes and Methods + +`string` **RSAPadding_Enc(string padding)**: Convert the encryption padding into the corresponding Algorithm string, such as: `PKCS1 -> RSA/ECB/PKCS1Padding`. + +`string` **RSAPadding_Sign(string hash)**: Convert the signature padding into the corresponding Algorithm string, such as: `PKCS1+SHA1 -> SHA1withRSA`. + +`bool` **IsDotNetSupportError(string errMsg)**: Determine whether the exception message is an error caused by .NET compatibility. + +`void` **UseBouncyCastle(Assembly bouncyCastleAssembly)**: Forces the use of the BouncyCastle cryptographic enhancement library for RSA operations. Just call it once after the program starts, directly call the class in BouncyCastle, pass in the assembly: `UseBouncyCastle(typeof(RsaEngine).Assembly)`, pass in null to cancel the use. The project introduces the BouncyCastle encryption enhancement library to expand the .NET encryption function, NuGet: Portable.BouncyCastle or BouncyCastle.Cryptography, document https://www.bouncycastle.org/csharp/, call this method to register when the program starts to get All encrypted signature padding are supported. + +`bool` **IsUseBouncyCastle**: Whether to force the use of the BouncyCastle encryption enhancement library for RSA operations. When true, .NET RSA will not be used. + +`bool` **IS_CORE**: Whether the current running environment is .NET Core, false is .NET Framework. + + +### Construction Methods + +**RSA_Util(int keySize)**: Create a new RSA with the specified key size, a new key will be generated, and an exception will be thrown if an error occurs. + +**RSA_Util(string pemOrXML)**: Create an RSA with a key in `PEM` or `XML` format, which can be a public key or a private key, and throws an exception if an error occurs. XML format such as: `...`. pem supports `PKCS#1`, `PKCS#8` format, the format is as follows: `-----BEGIN XXX KEY-----....-----END XXX KEY-----`. + +**RSA_Util(RSA_PEM pem)**: Create RSA through a pem object, where pem is a public key or private key, and an exception is thrown if an error occurs. + +**RSA_Util(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ)**: This method will first generate RSA_PEM and then create RSA. Construct a PEM through the full amount of PEM field data. Except for the modulus and public key exponent, all other private key exponent information must be provided or not provided (the exported PEM only contains the public key). Note: all parameters If the first byte is 0, it must be removed first. + +**RSA_Util(byte[] modulus, byte[] exponent, byte[] dOrNull)**: This method will first generate RSA_PEM and then create RSA. Constructing a PEM through the public key exponent and the private key exponent will calculate P and Q in reverse, but they are extremely unlikely to be the same as the P and Q of the original generated key. Note: If the first byte of all parameters is 0, it must be removed first. Errors will throw exceptions. The private key exponent may not be provided, and the exported PEM only contains the public key. + + +### Instance Attributes + +`RSA` **RSAObject**: Get RSA object, the actual RSA implementation class under .NET Core, RSACryptoServiceProvider under .NET Framework 4.5 and below, RSACng under .NET Framework 4.6 and above; Note: .NET RSA will not be used when IsUseBouncyCastle. + +`bool` **RSAIsUseCore**: Whether the used RSA object is the used .NET Core (RSA), otherwise it will be the used .NET Framework (4.5 and below RSACryptoServiceProvider, 4.6 and above RSACng); Note: When IsUseBouncyCastle will not use .NET RSA. + +`int` **KeySize**: Key digits. + +`bool` **HasPrivate**: Whether to include the private key. + + +### Instance Methods + +`string` **ToXML(bool convertToPublic = false)**: Export the secret key in XML format. If the RSA contains a private key, the private key will be exported by default. When only the public key is set, only the public key will be exported; if the private key is not included, only the public key will be exported. + +`RSA_PEM` **ToPEM(bool convertToPublic = false)**: Export RSA_PEM object (then you can export PEM text by RSA_PEM.ToPEM method), if convertToPublic RSA containing private key will only return public key, RSA containing only public key will not be affected. + +`RSA_Util` **SwapKey_Exponent_D__Unsafe()**: [Unsafe and not recommended] Swap the public key exponent (Key_Exponent) and the private key exponent (Key_D): use the public key as the private key (new.Key_D=this.Key_Exponent) and the private key as the public key (new.Key_Exponent=this.Key_D), returns a new RSA object; for example, used for: private key encryption, public key decryption, this is an unconventional usage. If the current key only contains the public key, the swap will not occur, and the returned new RSA will allow decryption and signing operations with the public key; However, the RSA that comes with .NET does not support decryption and signing with keys containing only the public key, and the exponent must be swapped (If it is .NET Framework 4.5 and below, public and private keys are not supported), there is no such problem when using NoPadding or IsUseBouncyCastle. Note: It is very unsafe to use a public key as a private key, because the public key exponent of most generated keys is 0x10001 (AQAB), which is too easy to guess and cannot be used as a true private key. In some private key encryption implementations, such as Java's own RSA, when using non-NoPadding padding, encryption with private key objects may use EMSA-PKCS1-v1_5 padding (using the private key exponent to construct a public key object does not have this problem ), so when interoperating between different programs, you may need to use the corresponding padding algorithm to first fill the data, and then use NoPadding padding to encrypt (decryption also uses NoPadding padding to decrypt, and then remove the padding data). + +`string` **Encrypt(string padding, string str)**: Encrypt arbitrary length string (utf-8) returns base64, and an exception is thrown if an error occurs. This method is thread safe. padding specifies the encryption padding, such as: PKCS1, OAEP+SHA256 uppercase, refer to the encryption padding table above, and the default is PKCS1 when using a null value. + +`byte[]` **Encrypt(string padding, byte[] data)**: Encrypt arbitrary length data, and throw an exception if an error occurs. This method is thread safe. + +`string` **Decrypt(string padding, string str)**: Decrypt arbitrary length ciphertext (base64) to get string (utf-8), and throw an exception if an error occurs. This method is thread safe. padding specifies the encryption padding, such as: PKCS1, OAEP+SHA256 uppercase, refer to the encryption padding table above, and the default is PKCS1 when using a null value. + +`byte[]` **Decrypt(string padding, byte[] data)**: Decrypt arbitrary length data, and throw an exception if an error occurs. This method is thread safe. + +`string` **Sign(string hash, string str)**: Sign the string str, return the base64 result, and throw an exception if an error occurs. This method is thread safe. hash specifies the signature digest algorithm and signature padding, such as: SHA256, PSS+SHA1 uppercase, refer to the signature padding table above. + +`byte[]` **Sign(string hash, byte[] data)**: Sign the data, and throw an exception if an error occurs. This method is thread safe. + +`bool` **Verify(string hash, string sign, string str)**: Verify whether the signature of the string str is sign (base64), and throw an exception if an error occurs. This method is thread safe. hash specifies the signature digest algorithm and signature padding, such as: SHA256, PSS+SHA1 uppercase, refer to the signature padding table above. + +`bool` **Verify(string hash, byte[] sign, byte[] data)**: Verify whether the signature of data is sign, and throw an exception if an error occurs. This method is thread safe. + + + + + + +[​](?) + +[​](?) + +## OpenSSL RSA common command line reference +``` bat +::First prepare a test file test.txt and fill in a small amount of content, openssl does not support automatic segmentation encryption + +::Generate new key +openssl genrsa -out private.pem 1024 + +::Extract public key PKCS#8 +openssl rsa -in private.pem -pubout -out public.pem + +::Convert to RSAPublicKey PKCS#1 +openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public.pem.rsakey +::Test RSAPublicKey PKCS#1, no accident will go wrong. Because there is no OID in this public key, it can be encrypted normally by converting RSA_PEM into PKCS#8 and automatically bringing OID +echo abcd123 | openssl rsautl -encrypt -inkey public.pem.rsakey -pubin + + + +::Encryption and decryption, padding mode: PKCS1 +openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin +openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt + +::Encryption and decryption, padding mode: OAEP+SHA256, mask generation function MGF1 uses the same hash algorithm +openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin +openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt + + +::The sha256 in the command line parameters can be replaced by md5, sha1, etc.; if you need the sha3 series, you can replace it with sha3-256 + + +::Signature and verification, padding mode: PKCS1+SHA256 +openssl dgst -sha256 -binary -sign private.pem -out test.txt.sign.bin test.txt +openssl dgst -sha256 -binary -verify public.pem -signature test.txt.sign.bin test.txt + +::Signature and verification, padding mode: PSS+SHA256, salt=-1 use hash length=256/8, mask generation function MGF1 uses the same hash algorithm +openssl dgst -sha256 -binary -out test.txt.hash test.txt +openssl pkeyutl -sign -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -inkey private.pem -out test.txt.sign.bin +openssl pkeyutl -verify -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -pubin -inkey public.pem -sigfile test.txt.sign.bin +``` + + + + + + + +[​](?) + +[​](?) + +[​](?) + +# :star:Donate +If this library is helpful to you, please star it. + +You can also use Alipay or WeChat to donate to the author: + +![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-alipay.png) ![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-weixin.png) + diff --git a/README.md b/README.md index 3722c3e..ab36234 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,385 @@ -# RSA-csharp的帮助文档 +**【[源GitHub仓库](https://github.com/xiangyuecn/RSA-csharp)】 | 【[Gitee镜像库](https://gitee.com/xiangyuecn/RSA-csharp)】如果本文档图片没有显示,请手动切换到Gitee镜像库阅读文档。** +# :open_book:RSA-csharp使用文档 ( [English Documentation](README-English.md) ) -## 跑起来 +**本项目核心功能:支持`.NET Core`、`.NET Framework`环境下`PEM`(`PKCS#1`、`PKCS#8`)格式RSA密钥生成、导入、导出,多种常见RSA加密、签名填充算法支持。** -clone下来用vs应该能够直接打开,经目测看起来没什么卵用的文件都svn:ignore掉了(svn滑稽。 +- 支持.NET Framework 4.5+、.NET Standard 2.0+(.NET Core 2.0+、.NET 5+) +- 可通过`PEM`、`XML`格式密钥创建RSA +- 可通过指定密钥位数、密钥参数创建RSA +- 可导出`PEM`、`XML`格式公钥、私钥,格式相互转换 +- 公钥加密、私钥解密:`NoPadding`、`PKCS1Padding`、`OAEP+MD5`、`OAEP+SHA1 ... SHA3-512` +- 私钥签名、公钥验证:`PKCS1+SHA1 ... SHA3-512`、`PKCS1+MD5`、`PSS+SHA1 ... SHA3-512` +- 非常规的:私钥加密、公钥解密,公钥签名、私钥验证 +- 多语言支持:提供中文、英文两种语言支持 +- 另有Java版 [RSA-java](https://github.com/xiangyuecn/RSA-java),所有加密签名算法在`Java`、`.NET`、`OpenSSL`中均可互通 +- 源码简单,提供编译测试`.bat|.sh`脚本,无需Visual Studio即可修改和运行,copy即用 +[​](?) -## 主要支持 +你可以只copy `RSA_PEM.cs`、`RSA_Util.cs` 文件到你的项目中使用,即可使用上所有的功能。也可以clone整个项目代码双击 `Test-Build-Run.bat` 即可直接运行测试(macOS、linux用终端运行`.sh`的),通过`scripts/Create-dll.bat(sh)`脚本可生成dll文件供项目引用。 -- 通过`XML格式`密钥对创建RSA -- 通过`PEM格式`密钥对创建RSA -- RSA加密、解密 -- RSA签名、验证 -- 导出`XML格式`公钥、私钥 -- 导出`PEM格式`公钥、私钥 -- `PEM格式`秘钥对和`XML格式`秘钥对互转 +`RSA_PEM`类底层实现采用PEM文件二进制层面上进行字节码解析,简单轻巧0依赖;`RSA_Util`为封装RSA操作类,支持跨平台使用,可选搭配使用`BouncyCastle`加密增强库可获得更丰富的加密签名模式支持。 -## 已知问题 +源文件|平台支持|功能说明|依赖项 +:-:|:-:|:-|:- +**RSA_PEM.cs**|.NET Core、.NET Framework|用于解析和导出PEM,创建RSA实例|无 +**RSA_Util.cs**|.NET Core、.NET Framework|RSA操作类,封装了加密、解密、验签|RSA_PEM +Program.cs|.NET Core、.NET Framework|测试控制台程序|RSA_PEM、RSA_Util -代码在.NET Framework 4.5测试过,如果要用在.NET Core下,除了RSA_PEM.cs可以copy过去用用外,其他几个文件不建议拿到.NET Core下使用;因为RSA_PEM里面除了RSACryptoServiceProvider、RSAParameters之外的代码是容易用别的语言来实现,而这两个玩意在这个文件里面几乎算是可有可无的东西。问题在于我用的RSACryptoServiceProvider在.NET Core下面就是个残废,参考[/issues/1](https://github.com/xiangyuecn/RSA-csharp/issues/1),手动额外实现一下FromXmlString、ToXmlString(关键是会去用这两个函数完全是被.NET Framework的RSACryptoServiceProvider逼的啊,要是他直接支持PEM秘钥,这个仓库都省了,不可能因为RSACryptoServiceProvider的XML格式而去支持XML格式~)。 +[​](?) +**Test-Build-Run.bat 测试编译运行截图:** +![控制台测试](images/1.png) -## 前言、自述、还有啥 -在写一个小转换工具时加入了RSA加密解密支持(见图RSA工具),秘钥输入框支持填写XML和PEM格式,操作类型里面支持XML->PEM、PEM->XML的转换。 +[​](?) -实现相应功能发现原有RSA操作类不能良好工作,PEM->XML没问题,只要能通过PEM创建RSA,就能用`RSACryptoServiceProvider`自带方法导出XML。但XML->PEM没有找到相应的简单实现方法,大部分博客写的用BouncyCastle库来操作,代码是少,但BouncyCastle就有好几兆大小,我的小工具啊才100K;所以自己实现了一个支持导出`PKCS#1`、`PKCS#8`格式PEM密钥的方法`RSA_PEM.ToPEM`。 +[​](?) + +## 快速使用:加密、解密、签名、校验 + +### 步骤一:引入RSA-csharp +- 方法1:直接复制 `RSA_PEM.cs`、`RSA_Util.cs` 文件到你的项目中使用。 +- 方法2:使用`scripts/Create-dll.bat(sh)`脚本生成dll文件,项目添加这个dll的引用即可使用。 +- 方法3:下载Releases中对应版本的dll文件(就是方法2脚本生成的dll),项目添加这个dll的引用即可使用。 + +> 注意:.NET Framework项目可能需要手动引入`System.Numerics`程序集来支持`BigInteger`,vs默认创建的项目没有自动引入此程序集,.NET Core项目不需要此操作。 + + +### 步骤二:编写代码 +``` c# +//先解析pem或xml,公钥私钥均可解析 +//var pem=RSA_PEM.FromPEM("-----BEGIN XXX KEY-----....-----END XXX KEY-----"); +//var pem=RSA_PEM.FromXML("...."); + +//直接创建RSA操作类,可创建成全局对象,加密解密签名均支持并发调用 +//var rsa=new RSA_Util(pem); +var rsa=new RSA_Util(2048); //也可以直接生成新密钥,rsa.ToPEM()得到pem对象 + +//可选注册BouncyCastle加密增强库(程序启动时注册一次即可),用来实现.NET不支持的加密签名填充方式,NuGet:Portable.BouncyCastle或BouncyCastle.Cryptography +//RSA_Util.UseBouncyCastle(typeof(RsaEngine).Assembly); + +//公钥加密,填充方式:PKCS1,可以使用 OAEP+SHA256 等填充方式 +var enTxt=rsa.Encrypt("PKCS1", "测试123"); +//私钥解密 +var deTxt=rsa.Decrypt("PKCS1", enTxt); + +//私钥签名,填充方式:PKCS1+SHA1,可以使用 PSS+SHA256 等填充方式 +var sign=rsa.Sign("PKCS1+SHA1", "测试123"); +//公钥校验签名 +var isVerify=rsa.Verify("PKCS1+SHA1", sign, "测试123"); + +//导出pem文本 +var pemTxt=rsa.ToPEM().ToPEM_PKCS8(); + +//非常规的(不安全、不建议使用):私钥加密、公钥解密,公钥签名、私钥验证 +RSA_Util rsaS_Private=rsa.SwapKey_Exponent_D__Unsafe(); +RSA_Util rsaS_Public=new RSA_Util(rsa.ToPEM(true)).SwapKey_Exponent_D__Unsafe(); +//... rsaS_Private.Encrypt rsaS_Public.Decrypt +//... rsaS_Public.Sign rsaS_Private.Verify + +Console.WriteLine(pemTxt+"\n"+enTxt+"\n"+deTxt+"\n"+sign+"\n"+isVerify); +Console.ReadLine(); +//****更多的实例,请阅读 Program.cs**** +//****更多功能方法,请阅读下面的详细文档**** +``` + +**如需功能定制,网站、App、小程序开发等需求,请加下面的QQ群,联系群主(即作者),谢谢~** + + + +[​](?) + +## 【QQ群】交流与支持 + +欢迎加QQ群:421882406,纯小写口令:`xiangyuecn` + + + + + + + + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +# :open_book:文档 + +## 加密填充方式 + +> 下表中Frame为.NET Framework支持情况,Core为.NET Core的支持情况,BC为BouncyCastle加密增强库支持情况(可通过RSA_Util.UseBouncyCastle方法注册);√为支持,×为不支持,其他值为某版本开始支持;其中OAEP的掩码生成函数MGF1使用和OAEP相同的Hash算法。 + +加密填充方式|Algorithm|Frame|Core|BC +:-|:-|:-:|:-:|:-: +NO|RSA/ECB/NoPadding|√|√|√ +PKCS1 |RSA/ECB/PKCS1Padding|√|√|√ +OAEP+SHA1 |RSA/ECB/OAEPwithSHA-1andMGF1Padding|√|√|√ +OAEP+SHA256|RSA/ECB/OAEPwithSHA-256andMGF1Padding|4.6+|√|√ +OAEP+SHA224|RSA/ECB/OAEPwithSHA-224andMGF1Padding|×|×|√ +OAEP+SHA384|RSA/ECB/OAEPwithSHA-384andMGF1Padding|4.6+|√|√ +OAEP+SHA512|RSA/ECB/OAEPwithSHA-512andMGF1Padding|4.6+|√|√ +OAEP+SHA-512/224|RSA/ECB/OAEPwithSHA-512/224andMGF1Padding|×|×|√ +OAEP+SHA-512/256|RSA/ECB/OAEPwithSHA-512/256andMGF1Padding|×|×|√ +OAEP+SHA3-256|RSA/ECB/OAEPwithSHA3-256andMGF1Padding|×|8+|√ +OAEP+SHA3-224|RSA/ECB/OAEPwithSHA3-224andMGF1Padding|×|×|√ +OAEP+SHA3-384|RSA/ECB/OAEPwithSHA3-384andMGF1Padding|×|8+|√ +OAEP+SHA3-512|RSA/ECB/OAEPwithSHA3-512andMGF1Padding|×|8+|√ +OAEP+MD5 |RSA/ECB/OAEPwithMD5andMGF1Padding|4.6+|√|√ + + + +## 签名填充方式 + +> 下表中Frame为.NET Framework支持情况,Core为.NET Core的支持情况,BC为BouncyCastle加密增强库支持情况(可通过RSA_Util.UseBouncyCastle方法注册);√为支持,×为不支持,其他值为某版本开始支持;其中PSS的salt字节数等于使用的Hash算法字节数,PSS的掩码生成函数MGF1使用和PSS相同的Hash算法,跟踪属性TrailerField取值固定为0xBC。 + +签名填充方式|Algorithm|Frame|Core|BC +:-|:-|:-:|:-:|:-: +SHA1 ... SHA3-512|等同于PKCS1+SHA***||| +PKCS1+SHA1 |SHA1withRSA|√|√|√ +PKCS1+SHA256|SHA256withRSA|√|√|√ +PKCS1+SHA224|SHA224withRSA|×|×|√ +PKCS1+SHA384|SHA384withRSA|√|√|√ +PKCS1+SHA512|SHA512withRSA|√|√|√ +PKCS1+SHA-512/224|SHA512/224withRSA|×|×|√ +PKCS1+SHA-512/256|SHA512/256withRSA|×|×|√ +PKCS1+SHA3-256|SHA3-256withRSA|×|8+|√ +PKCS1+SHA3-224|SHA3-224withRSA|×|×|√ +PKCS1+SHA3-384|SHA3-384withRSA|×|8+|√ +PKCS1+SHA3-512|SHA3-512withRSA|×|8+|√ +PKCS1+MD5 |MD5withRSA|√|√|√ +PSS+SHA1 |SHA1withRSA/PSS|4.6+|√|√ +PSS+SHA256|SHA256withRSA/PSS|4.6+|√|√ +PSS+SHA224|SHA224withRSA/PSS|×|×|√ +PSS+SHA384|SHA384withRSA/PSS|4.6+|√|√ +PSS+SHA512|SHA512withRSA/PSS|4.6+|√|√ +PSS+SHA-512/224|SHA512/224withRSA/PSS|×|×|√ +PSS+SHA-512/256|SHA512/256withRSA/PSS|×|×|√ +PSS+SHA3-256|SHA3-256withRSA/PSS|×|8+|√ +PSS+SHA3-224|SHA3-224withRSA/PSS|×|×|√ +PSS+SHA3-384|SHA3-384withRSA/PSS|×|8+|√ +PSS+SHA3-512|SHA3-512withRSA/PSS|×|8+|√ +PSS+MD5 |MD5withRSA/PSS|4.6+|√|√ + + + +[​](?) + +[​](?) + +## RSA_PEM 类文档 +`RSA_PEM.cs`文件不依赖任何文件,可以直接copy这个文件到你项目中用;通过`FromPEM`、`ToPEM` 和`FromXML`、`ToXML`这两对方法,可以实现PEM`PKCS#1`、`PKCS#8`相互转换,PEM、XML的相互转换。 + +注:`openssl rsa -in 私钥文件 -pubout`导出的是PKCS#8格式公钥(用的比较多),`openssl rsa -pubin -in PKCS#8公钥文件 -RSAPublicKey_out`导出的是PKCS#1格式公钥(用的比较少)。 + + +### 静态属性和方法 + +`RSA_PEM` **FromPEM(string pem)**:用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM,出错将会抛出异常。pem格式如:`-----BEGIN XXX KEY-----....-----END XXX KEY-----`。 + +`RSA_PEM` **FromXML(string xml)**:将XML格式密钥转成PEM,支持公钥xml、私钥xml,出错将会抛出异常。xml格式如:`....`。 + +`string` **T(string zh, string en)**:简版多语言支持,根据当前语言`Lang`值返回中文或英文。 + +`string` **Lang**:简版多语言支持,取值:`zh`(简体中文)、`en`(English-US),默认根据系统取值,可赋值指定语言。 + + +### 构造方法 + +**RSA_PEM(RSA rsa, bool convertToPublic = false)**:通过RSA中的公钥和私钥构造一个PEM,如果convertToPublic含私钥的RSA将只读取公钥,仅含公钥的RSA不受影响。 + +**RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ)**:通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥)注意:所有参数首字节如果是0,必须先去掉。 + +**RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull)**:通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同。注意:所有参数首字节如果是0,必须先去掉。出错将会抛出异常。私钥指数可以不提供,导出的PEM就只包含公钥。 + + +### 实例属性 + +`byte[]`:**Key_Modulus**(模数n,公钥、私钥都有)、**Key_Exponent**(公钥指数e,公钥、私钥都有)、**Key_D**(私钥指数d,只有私钥的时候才有);有这3个足够用来加密解密。 + +`byte[]`:**Val_P**(prime1)、**Val_Q**(prime2)、**Val_DP**(exponent1)、**Val_DQ**(exponent2)、**Val_InverseQ**(coefficient); (PEM中的私钥才有的更多的数值;可通过n、e、d反推出这些值(只是反推出有效值,和原始的值大概率不同))。 + +`int` **KeySize**:密钥位数。 + +`bool` **HasPrivate**:是否包含私钥。 + + +### 实例方法 + +`RSA` **GetRSA_ForCore()**:将PEM中的公钥私钥转成RSA对象,如果未提供私钥,RSA中就只包含公钥。返回的RSA支持跨平台使用,但只支持在.NET Core环境中使用。 + +`RSACryptoServiceProvider` **GetRSA_ForWindows()**:将PEM中的公钥私钥转成RSA对象,如果未提供私钥,RSA中就只包含公钥。.NET Core、.NET Framework均可用,但返回的RSACryptoServiceProvider不支持跨平台,所以只支持在Windows系统中使用。 + +`void` **GetRSA__ImportParameters(RSA rsa)**:将密钥参数导入到RSA对象中,`GetRSA_ForCore`、`GetRSA_ForWindows`中调用了本方法,可用于创建其他类型的RSA时使用。 + +`RSA_PEM` **CopyToNew(bool convertToPublic = false)**:将当前PEM中的密钥对复制出一个新的PEM对象。convertToPublic:等于true时含私钥的PEM将只返回公钥,仅含公钥的PEM不受影响。 + +`RSA_PEM` **SwapKey_Exponent_D__Unsafe()**:【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新PEM对象;比如用于:私钥加密、公钥解密,这是非常规的用法。当前对象必须含私钥,否则无法交换会直接抛异常。注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥。 + +`byte[]` **ToDER(bool convertToPublic, bool privateUsePKCS8, bool publicUsePKCS8)**:将RSA中的密钥对转换成DER格式,DER格式为PEM中的Base64文本编码前的二进制数据,参数含义参考ToPEM方法。 + +`string` **ToPEM(bool convertToPublic, bool privateUsePKCS8, bool publicUsePKCS8)**:将RSA中的密钥对转换成PEM格式。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 。**privateUsePKCS8**:私钥的返回格式,等于true时返回PKCS#8格式(`-----BEGIN PRIVATE KEY-----`),否则返回PKCS#1格式(`-----BEGIN RSA PRIVATE KEY-----`),返回公钥时此参数无效;两种格式使用都比较常见。**publicUsePKCS8**:公钥的返回格式,等于true时返回PKCS#8格式(`-----BEGIN PUBLIC KEY-----`),否则返回PKCS#1格式(`-----BEGIN RSA PUBLIC KEY-----`),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式公钥似乎比较少见。 + +`string` **ToPEM_PKCS1(bool convertToPublic=false)**:ToPEM方法的简化写法,不管公钥还是私钥都返回PKCS#1格式;似乎导出PKCS#1公钥用的比较少,PKCS#8的公钥用的多些,私钥#1#8都差不多。 + +`string` **ToPEM_PKCS8(bool convertToPublic=false)**:ToPEM方法的简化写法,不管公钥还是私钥都返回PKCS#8格式。 + +`string` **ToXML(bool convertToPublic)**:将RSA中的密钥对转换成XML格式,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 + + + + +[​](?) + +[​](?) + +## RSA_Util 类文档 +`RSA_Util.cs`文件依赖`RSA_PEM.cs`,封装了加密、解密、签名、验证、秘钥导入导出操作;.NET Core下由实际的RSA实现类提供支持,.NET Framework 4.5及以下由RSACryptoServiceProvider提供支持,.NET Framework 4.6及以上由RSACng提供支持;或者引入BouncyCastle加密增强库提供支持。 + + +### 静态属性和方法 + +`string` **RSAPadding_Enc(string padding)**:将加密填充方式转换成对应的Algorithm字符串,比如`PKCS1 -> RSA/ECB/PKCS1Padding`。 + +`string` **RSAPadding_Sign(string hash)**:将签名填充方式转换成对应的Algorithm字符串,比如`PKCS1+SHA1 -> SHA1withRSA`。 + +`bool` **IsDotNetSupportError(string errMsg)**:判断异常消息是否是因为.NET兼容性产生的错误。 + +`void` **UseBouncyCastle(Assembly bouncyCastleAssembly)**:强制使用BouncyCastle加密增强库进行RSA操作。只需在程序启动后调用一次即可,直接调用一下BouncyCastle里面的类,传入程序集:`UseBouncyCastle(typeof(RsaEngine).Assembly)`,传入null取消使用。项目中引入BouncyCastle加密增强库来扩充.NET加密功能,NuGet:Portable.BouncyCastle或BouncyCastle.Cryptography,文档 https://www.bouncycastle.org/csharp/ ,在程序启动时调用本方法进行注册即可得到全部的加密签名填充方式支持。 + +`bool` **IsUseBouncyCastle**:是否强制使用BouncyCastle加密增强库进行RSA操作,为true时将不会使用.NET的RSA。 + +`bool` **IS_CORE**:当前运行环境是否为.NET Core,false为.NET Framework。 + + +### 构造方法 + +**RSA_Util(int keySize)**:用指定密钥大小创建一个新的RSA,会生成新密钥,出错抛异常。 + +**RSA_Util(string pemOrXML)**:通过`PEM格式`或`XML格式`密钥,创建一个RSA,pem或xml内可以只包含一个公钥或私钥,或都包含,出错抛异常。`XML格式`如:`...`。pem支持`PKCS#1`、`PKCS#8`格式,格式如:`-----BEGIN XXX KEY-----....-----END XXX KEY-----`。 + +**RSA_Util(RSA_PEM pem)**:通过一个pem对象创建RSA,pem为公钥或私钥,出错抛异常。 + +**RSA_Util(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ)**:本方法会先生成RSA_PEM再创建RSA。通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥)注意:所有参数首字节如果是0,必须先去掉。 + +**RSA_Util(byte[] modulus, byte[] exponent, byte[] dOrNull)**:本方法会先生成RSA_PEM再创建RSA。通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同。注意:所有参数首字节如果是0,必须先去掉。出错将会抛出异常。私钥指数可以不提供,导出的PEM就只包含公钥。 + + +### 实例属性 + +`RSA` **RSAObject**:获取最底层的RSA对象,.NET Core下为实际的RSA实现类,.NET Framework 4.5及以下RSACryptoServiceProvider,.NET Framework 4.6及以上RSACng;注意:IsUseBouncyCastle时将不会使用.NET的RSA。 + +`bool` **RSAIsUseCore**:最底层的RSA对象是否是使用的.NET Core(RSA),否则将是使用的.NET Framework(4.5及以下RSACryptoServiceProvider、4.6及以上RSACng);注意:IsUseBouncyCastle时将不会使用.NET的RSA。 + +`int` **KeySize**:密钥位数。 + +`bool` **HasPrivate**:是否包含私钥。 + + +### 实例方法 + +`string` **ToXML(bool convertToPublic = false)**:导出`XML格式`秘钥对。如果RSA包含私钥,默认会导出私钥,设置仅仅导出公钥时只会导出公钥;不包含私钥只会导出公钥。 + +`RSA_PEM` **ToPEM(bool convertToPublic = false)**:导出RSA_PEM对象(然后可以通过RSA_PEM.ToPEM方法导出PEM文本),如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响。 + +`RSA_Util` **SwapKey_Exponent_D__Unsafe()**:【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新RSA对象;比如用于:私钥加密、公钥解密,这是非常规的用法。当前密钥如果只包含公钥,将不会发生对调,返回的新RSA将允许用公钥进行解密和签名操作;但.NET自带的RSA不支持仅含公钥的密钥进行解密和签名,必须进行指数对调(如果是.NET Framework 4.5及以下版本,公钥私钥均不支持),使用NoPadding填充方式或IsUseBouncyCastle时无此问题。注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥。部分私钥加密实现中,比如Java自带的RSA,使用非NoPadding填充方式时,用私钥对象进行加密可能会采用EMSA-PKCS1-v1_5填充方式(用私钥指数构造成公钥对象无此问题),因此在不同程序之间互通时,可能需要自行使用对应填充算法先对数据进行填充,然后再用NoPadding填充方式进行加密(解密也按NoPadding填充进行解密,然后去除填充数据)。 + +`string` **Encrypt(string padding, string str)**:加密任意长度字符串(utf-8)返回base64,出错抛异常。本方法线程安全。padding指定填充方式,如:PKCS1、OAEP+SHA256大写,参考上面的加密填充方式表格,使用空值时默认为PKCS1。 + +`byte[]` **Encrypt(string padding, byte[] data)**:加密任意长度数据,出错抛异常。本方法线程安全。 + +`string` **Decrypt(string padding, string str)**:解密任意长度密文(base64)得到字符串(utf-8),出错抛异常。本方法线程安全。padding指定填充方式,如:PKCS1、OAEP+SHA256大写,参考上面的加密填充方式表格,使用空值时默认为PKCS1。 + +`byte[]` **Decrypt(string padding, byte[] data)**:解密任意长度数据,出错抛异常。本方法线程安全。 + +`string` **Sign(string hash, string str)**:对字符串str进行签名,返回base64结果,出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式,如:SHA256、PSS+SHA1大写,参考上面的签名填充方式表格。 + +`byte[]` **Sign(string hash, byte[] data)**:对data进行签名,出错抛异常。本方法线程安全。 + +`bool` **Verify(string hash, string sign, string str)**:验证字符串str的签名是否是sign(base64),出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式,如:SHA256、PSS+SHA1大写,参考上面的签名填充方式表格。 + +`bool` **Verify(string hash, byte[] sign, byte[] data)**:验证data的签名是否是sign,出错抛异常。本方法线程安全。 + + + + + + +[​](?) + +[​](?) + +## OpenSSL RSA常用命令行参考 +``` bat +::先准备一个测试文件 test.txt 里面填少量内容,openssl不支持自动分段加密 + +::生成新密钥 +openssl genrsa -out private.pem 1024 + +::提取公钥PKCS#8 +openssl rsa -in private.pem -pubout -out public.pem + +::转换成RSAPublicKey PKCS#1 +openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public.pem.rsakey +::测试RSAPublicKey PKCS#1,不出意外会出错。因为这个公钥里面没有OID,通过RSA_PEM转换成PKCS#8自动带上OID就能正常加密 +echo abcd123 | openssl rsautl -encrypt -inkey public.pem.rsakey -pubin + + + +::加密和解密,填充方式:PKCS1 +openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin +openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:pkcs1 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt + +::加密和解密,填充方式:OAEP+SHA256,掩码生成函数MGF1使用相同的hash算法 +openssl pkeyutl -encrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt -pubin -inkey public.pem -out test.txt.enc.bin +openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -in test.txt.enc.bin -inkey private.pem -out test.txt.dec.txt + + +::命令行参数中的sha256可以换成md5、sha1等;如需sha3系列,就换成sha3-256即可 + + +::签名和验证,填充方式:PKCS1+SHA256 +openssl dgst -sha256 -binary -sign private.pem -out test.txt.sign.bin test.txt +openssl dgst -sha256 -binary -verify public.pem -signature test.txt.sign.bin test.txt + +::签名和验证,填充方式:PSS+SHA256 ,salt=-1使用hash长度=256/8,掩码生成函数MGF1使用相同的hash算法 +openssl dgst -sha256 -binary -out test.txt.hash test.txt +openssl pkeyutl -sign -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -inkey private.pem -out test.txt.sign.bin +openssl pkeyutl -verify -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:-1 -in test.txt.hash -pubin -inkey public.pem -sigfile test.txt.sign.bin +``` + + + + + + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +[​](?) + +# :open_book:知识库 + +在写一个小转换工具时加入了RSA加密解密支持,实现相应功能发现原有RSA操作类不能良好工作,PEM->XML没问题,只要能通过PEM创建RSA,就能用`RSACryptoServiceProvider`自带方法导出XML。但XML->PEM没有找到相应的简单实现方法,大部分博客写的用BouncyCastle库来操作,代码是少,但BouncyCastle就有好几兆大小,我的小工具啊才100K;所以自己实现了一个支持导出`PKCS#1`、`PKCS#8`格式PEM密钥的方法`RSA_PEM.ToPEM`。 操作过程中发现原有RSA操作类不支持用`PKCS#8`格式PEM密钥来创建RSA对象(用的[RSACryptoServiceProviderExtension](https://www.cnblogs.com/adylee/p/3611461.html)的扩展方法来支持PEM密钥),仅支持`PKCS#1`,所以又自己实现了一个从PEM密钥来创建`RSACryptoServiceProvider`的方法`RSA_PEM.FromPEM`。 @@ -66,11 +418,10 @@ PEM格式中,每段数据基本上都是`type+长度数据占用位数+长度 内容前面要加0(可能现在全部是加0吧,数据结尾这个字节不满8位?什么情况下会出现不够1字节?不够就用二进制0补齐,然后内容前面加补了几位)。 -### PEM公钥编码格式 -`PKCS#1`、`PKCS#8`公钥编码都是统一的格式。 +### PEM PKCS#8公钥编码格式 ``` -/*****1024位公钥*****/ +/*****1024位PKCS#8公钥*****/ -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYw9+M3+REzDtYqwBrs/as/Oy8 GRE5OmnqOV0EfkEiCIjiczbVEFnZ3qRjLbDATfmBxNQ6c6Fga8nX28glEH/aL/RG @@ -78,7 +429,7 @@ GRE5OmnqOV0EfkEiCIjiczbVEFnZ3qRjLbDATfmBxNQ6c6Fga8nX28glEH/aL/RG RWdNQlpIaTDo5IhJJwIDAQAB -----END PUBLIC KEY----- -/*****二进制表述*****/ +/*****二进制表述(文本是16进制下同)*****/ 30819F300D06092A864886F70D010101050003818D003081890281810098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E48849270203010001 @@ -98,7 +449,6 @@ RWdNQlpIaTDo5IhJJwIDAQAB , 30_SEQUENCE:{ 02_INTEGER: "整数" , 02_INTEGER: "整数" - , 02_INTEGER: "整数" } } */ @@ -137,6 +487,33 @@ RWdNQlpIaTDo5IhJJwIDAQAB ``` +### PEM PKCS#1公钥编码格式 + +``` +/*****1024位PKCS#1公钥*****/ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAJjD34zf5ETMO1irAGuz9qz87LwZETk6aeo5XQR+QSIIiOJzNtUQWdne +pGMtsMBN+YHE1DpzoWBrydfbyCUQf9ov9EbYp2kjwoxSooCzM3J2wdJ6H0xIpx3n +OBLg24TS755HsIHyQ5aKmIsNCZjfySkzfqlFZ01CWkhpMOjkiEknAgMBAAE= +-----END RSA PUBLIC KEY----- + +/*****二进制表述*****/ +3081890281810098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E48849270203010001 + + +/*****二进制分解(和PKCS#8公钥格式就是只留了N、E两个数据,及其简单)*****/ +30 81 89 + + /*RSA Modulus*/ + 02 81 81 + 0098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E4884927 + + /*RSA Exponent*/ + 02 03 + 010001 +``` + + ### PEM PKCS#1私钥编码格式 ``` @@ -266,83 +643,32 @@ yZKNX3VxmLEHXQ== ``` - - - -# C# RSA操作类 - - -## 主要文件 - -### RSA.cs -此文件依赖`RSA_PEM.cs`,用于进行加密、解密、签名、验证、秘钥导入导出操作。 - -#### [构造函数] new RSA(`1024`) -通过指定密钥长度来创建RSA,会生成新密钥。 - -#### [构造函数] new RSA(`""`) -通过`XML格式`密钥对创建RSA,xml可以是公钥或私钥,`XML格式`如: -``` -mMPfjN/kRMw7WKsAa7P2rPzsvBkROTpp6jldBH5BIgiI4nM21RBZ2d6kYy2wwE35gcTUOnOhYGvJ19vIJRB/2i/0RtinaSPCjFKigLMzcnbB0nofTEinHec4EuDbhNLvnkewgfJDloqYiw0JmN/JKTN+qUVnTUJaSGkw6OSISSc=AQAB +## Bug:C#生成的非正常密钥 +RSACryptoServiceProvider生成的密钥,有一定概率生成某些字段首字节为0的密钥(测试时发现几百个里面有一个),解析这种数据就难搞了,Java生成的密钥就没有出现这种情况。直接放2个样本吧,这两个样本是有效的密钥: ``` +样本一:D参数首字节是0 +3pfIaO4DYwrsymg7xddlr2gN2rtl2C+H3+RX6Nc3GQMJ+otZn5exMg5cMIWe+aS3mWB9XJi9AxlRdRAFf4j1PQ==AQAB

/nSZaBqEGehINeMa805aAqjNOhlHNYGZmF/C4evMvsc=

3/GsDJA/AnKq/lqbpCr1OB5h2wIKsLlPGafPljFzN9s=CSHGH6ZT91oOvWBZJ0I4mL/WHa+qjpEIIh/Nrq33uyE=nIg/m3SEJoDiVvIcko7YYwaRndT6hfaxfJxYtIISKDs=rok4ds5VOzho0h+4uvTeeOjEL1jxDaarUlnb3gxB6yY=ADQrXs04+5I6/URzKY807KAvww+A3F3OxgmzeucXidJSc+RMfVzkdf30X2iJHTj20EpzSfwCnlRNERCYXIqskQ==
-#### [构造函数] new RSA(`"PEM"`, `any`) -通过`PEM格式`密钥对创建RSA,PEM可以是公钥或私钥,支持`PKCS#1`、`PKCS#8`格式,`PEM格式`如: +样本二:DP参数首字节是0 +zbTQkRxbyJfCYnEzUjG3rWWRCpWYka5rWmnkqPXCYvLdZ5OIJEy+2Rgu9wCAnCdCBWMRLdUWjmJdNQizBcITVQ==AQAB

+N3qwya7gWN568BuciUwkSlgWVlORusk267Nkkiu3jc=

05o0BiIaDFYtHYNUh5/ROgDSkWqPXjy8Nlmh0S6QdNM=AJemo2hIMfqmo6UFnkfwYagTjqLjyM9uewdjfeGmaOk=ZK3D/v8Owbvm71njSDxkQmLNzV6UJFRlgL6Y3Xx4Qv0=zHAAUftduljSvsxV0TAblEX+FUXw3unb3M6oaow7+R0=n7XqVTAiZuzFBG+FfCSTynHYGdKqITm9qfYbjb85zF4EWxZWdMjm/9V2guKbRYGV9Xo96PowNnjeJ+NkbG5B5Q==
``` ------BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYw9+M3+REzDtYqwBrs/as/Oy8 -GRE5OmnqOV0EfkEiCIjiczbVEFnZ3qRjLbDATfmBxNQ6c6Fga8nX28glEH/aL/RG -2KdpI8KMUqKAszNydsHSeh9MSKcd5zgS4NuE0u+eR7CB8kOWipiLDQmY38kpM36p -RWdNQlpIaTDo5IhJJwIDAQAB ------END PUBLIC KEY----- -``` - -#### [方法] .ToXML(`[false]是否仅仅导出公钥`) -导出`XML格式`秘钥对。如果RSA包含私钥,默认会导出私钥,设置仅仅导出公钥时只会导出公钥;不包含私钥只会导出公钥。 - -#### [方法] .ToPEM_PKCS1|.ToPEM_PKCS8(`[false]是否仅仅导出公钥`) -导出`PEM格式`秘钥对,两个方法分别导出`PKCS#1`、`PKCS#8`格式。如果RSA包含私钥,默认会导出私钥,设置仅仅导出公钥时只会导出公钥;不包含私钥只会导出公钥。 - -#### [方法] .Encode(`"字符串"|bytes`) -加密操作,支持任意长度数据。 - -#### [方法] .DecodeOrNull(`"Base64字符串"|bytes`) -解密操作,解密失败返回null,支持任意长度数据。 - -#### [方法] .Sign(`"hash"`, `"字符串"|bytes`) -通过hash算法(MD5、SHA1等)来对数据进行签名。 - -#### [方法] .Verify(`"hash"`, `"sign"`, `"字符串"`) -通过hash算法(MD5、SHA1等)来验证字符串是否和sign签名一致。 - - -### RSA_PEM.cs -此文件不依赖任何文件,可以单独copy来用(`RSA_Unit`里面的方法可以忽略) - -#### [静态方法] .FromPEM(`"PEM"`) -通过`PEM格式`秘钥对来创建`RSACryptoServiceProvider`,PEM可以是公钥或私钥。 - -#### [静态方法] .ToPEM(`RSACryptoServiceProvider`, `exportPublicOnly`, `usePKCS8`) -将RSA中的密钥对导出成`PEM格式`,`usePKCS8=false`时返回`PKCS#1格式`,否则返回`PKCS#8格式`。如果RSA包含私钥,默认会导出私钥,设置仅仅导出公钥时只会导出公钥;不包含私钥只会导出公钥。 +> 这几个问题是QQ:284485094 提出的,循环一下测试很容易出现这个现象,现在的代码已经兼容了这种密钥。 -## 次要文件 -## RSA_Unit.cs -封装的一些通用方法,如:base64。没有此文件也可以,引用的地方用别的代码实现。 -## Program.cs -控制台入口文件,用来测试的,里面包含了主要的使用用例。 +[​](?) +[​](?) -## 图例 +[​](?) -控制台运行: +# :star:捐赠 +如果这个库有帮助到您,请 Star 一下。 -![控制台运行](images/1.png) +您也可以使用支付宝或微信打赏作者: -RSA工具: +![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-alipay.png) ![](https://gitee.com/xiangyuecn/Recorder/raw/master/assets/donate-weixin.png) -![RSA工具](images/2.png) \ No newline at end of file diff --git a/RSA.cs b/RSA.cs deleted file mode 100644 index fc6aa42..0000000 --- a/RSA.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace RSA { - /// - /// RSA操作类 - /// - public class RSA { - /// - /// 导出XML格式密钥对,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 - /// - public string ToXML(bool convertToPublic = false) { - return rsa.ToXmlString(!rsa.PublicOnly && !convertToPublic); - } - /// - /// 导出PEM PKCS#1格式密钥对,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 - /// - public string ToPEM_PKCS1(bool convertToPublic = false) { - return RSA_PEM.ToPEM(rsa, convertToPublic, false); - } - /// - /// 导出PEM PKCS#8格式密钥对,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 - /// - public string ToPEM_PKCS8(bool convertToPublic = false) { - return RSA_PEM.ToPEM(rsa, convertToPublic, true); - } - - - - - /// - /// 加密字符串(utf-8),出错抛异常 - /// - public string Encode(string str) { - return RSA_Unit.Base64EncodeBytes(Encode(Encoding.UTF8.GetBytes(str))); - } - /// - /// 加密数据,出错抛异常 - /// - public byte[] Encode(byte[] data) { - int blockLen = rsa.KeySize / 8 - 11; - if (data.Length <= blockLen) { - return rsa.Encrypt(data, false); - } - - using (var dataStream = new MemoryStream(data)) - using (var enStream = new MemoryStream()) { - Byte[] buffer = new Byte[blockLen]; - int len = dataStream.Read(buffer, 0, blockLen); - - while (len > 0) { - Byte[] block = new Byte[len]; - Array.Copy(buffer, 0, block, 0, len); - - Byte[] enBlock = rsa.Encrypt(block, false); - enStream.Write(enBlock, 0, enBlock.Length); - - len = dataStream.Read(buffer, 0, blockLen); - } - - return enStream.ToArray(); - } - } - /// - /// 解密字符串(utf-8),解密异常返回null - /// - public string DecodeOrNull(string str) { - if (String.IsNullOrEmpty(str)) { - return null; - } - var byts = RSA_Unit.Base64DecodeBytes(str); - if (byts == null) { - return null; - } - var val = DecodeOrNull(byts); - if (val == null) { - return null; - } - return Encoding.UTF8.GetString(val); - } - /// - /// 解密数据,解密异常返回null - /// - public byte[] DecodeOrNull(byte[] data) { - try { - int blockLen = rsa.KeySize / 8; - if (data.Length <= blockLen) { - return rsa.Decrypt(data, false); - } - - using (var dataStream = new MemoryStream(data)) - using (var deStream = new MemoryStream()) { - Byte[] buffer = new Byte[blockLen]; - int len = dataStream.Read(buffer, 0, blockLen); - - while (len > 0) { - Byte[] block = new Byte[len]; - Array.Copy(buffer, 0, block, 0, len); - - Byte[] deBlock = rsa.Decrypt(block, false); - deStream.Write(deBlock, 0, deBlock.Length); - - len = dataStream.Read(buffer, 0, blockLen); - } - - return deStream.ToArray(); - } - } catch { - return null; - } - } - /// - /// 对str进行签名,并指定hash算法(如:SHA256) - /// - public string Sign(string hash, string str) { - return RSA_Unit.Base64EncodeBytes(Sign(hash, Encoding.UTF8.GetBytes(str))); - } - /// - /// 对data进行签名,并指定hash算法(如:SHA256) - /// - public byte[] Sign(string hash, byte[] data) { - return rsa.SignData(data, hash); - } - /// - /// 验证字符串str的签名是否是sgin,并指定hash算法(如:SHA256) - /// - public bool Verify(string hash, string sgin, string str) { - var byts = RSA_Unit.Base64DecodeBytes(sgin); - if (byts == null) { - return false; - } - return Verify(hash, byts, Encoding.UTF8.GetBytes(str)); - } - /// - /// 验证data的签名是否是sgin,并指定hash算法(如:SHA256) - /// - public bool Verify(string hash, byte[] sgin, byte[] data) { - try { - return rsa.VerifyData(data, hash, sgin); - } catch { - return false; - } - } - - - - - private RSACryptoServiceProvider rsa; - private void _init() { - var rsaParams = new CspParameters(); - rsaParams.Flags = CspProviderFlags.UseMachineKeyStore; - rsa = new RSACryptoServiceProvider(rsaParams); - } - - /// - /// 用指定密钥大小创建一个新的RSA,出错抛异常 - /// - public RSA(int keySize) { - _init(); - - var rsaParams = new CspParameters(); - rsaParams.Flags = CspProviderFlags.UseMachineKeyStore; - rsa = new RSACryptoServiceProvider(keySize, rsaParams); - } - /// - /// 通过指定的密钥,创建一个RSA,xml内可以只包含一个公钥或私钥,或都包含,出错抛异常 - /// - public RSA(string xml) { - _init(); - - rsa.FromXmlString(xml); - } - /// - /// 通过一个pem文件创建RSA,pem为公钥或私钥,出错抛异常 - /// - public RSA(string pem, bool noop) { - _init(); - - rsa = RSA_PEM.FromPEM(pem); - } - } -} diff --git a/RSA_PEM.cs b/RSA_PEM.cs index 701d88c..b618e32 100644 --- a/RSA_PEM.cs +++ b/RSA_PEM.cs @@ -1,31 +1,287 @@ using System; -using System.Collections.Generic; using System.IO; +using System.Numerics; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; +using System.Globalization; -namespace RSA { +namespace com.github.xiangyuecn.rsacsharp { /// - /// RSA PEM格式秘钥对的解析和导出 + /// RSA PEM格式密钥对的解析和导出,.NET Core、.NET Framework均可用 + /// GitHub: https://github.com/xiangyuecn/RSA-csharp /// public class RSA_PEM { /// - /// 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM + /// modulus 模数n,公钥、私钥都有 + /// + public byte[] Key_Modulus; + /// + /// publicExponent 公钥指数e,公钥、私钥都有 + /// + public byte[] Key_Exponent; + /// + /// privateExponent 私钥指数d,只有私钥的时候才有 + /// + public byte[] Key_D; + + //以下参数只有私钥才有 https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsaparameters?redirectedfrom=MSDN&view=netframework-4.8 + /// + /// prime1,只有私钥的时候才有 + /// + public byte[] Val_P; + /// + /// prime2,只有私钥的时候才有 + /// + public byte[] Val_Q; + /// + /// exponent1,只有私钥的时候才有 + /// + public byte[] Val_DP; + /// + /// exponent2,只有私钥的时候才有 + /// + public byte[] Val_DQ; + /// + /// coefficient,只有私钥的时候才有 + /// + public byte[] Val_InverseQ; + + private RSA_PEM() { } + + /// + /// 通过RSA中的公钥和私钥构造一个PEM,如果convertToPublic含私钥的RSA将只读取公钥,仅含公钥的RSA不受影响 + /// + public RSA_PEM(RSA rsa, bool convertToPublic = false) { + var param = rsa.ExportParameters(false); + if (!convertToPublic) { + if (!(rsa is RSACryptoServiceProvider) || !((RSACryptoServiceProvider)rsa).PublicOnly) { + try { //公钥时,填true可能会抛异常 + param = rsa.ExportParameters(true); + } catch (Exception) { } + } + } + var isPublic = convertToPublic || param.D == null; + + Key_Modulus = param.Modulus; + Key_Exponent = param.Exponent; + + if (!isPublic) { + Key_D = param.D; + + Val_P = param.P; + Val_Q = param.Q; + Val_DP = param.DP; + Val_DQ = param.DQ; + Val_InverseQ = param.InverseQ; + } + } + /// + /// 通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥) + /// 注意:所有参数首字节如果是0,必须先去掉 + /// + public RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ) { + Key_Modulus = modulus; + Key_Exponent = exponent; + + if (d != null) { + Key_D = BigL(d, modulus.Length); + + int keyLen = modulus.Length / 2; + Val_P = BigL(p, keyLen); + Val_Q = BigL(q, keyLen); + Val_DP = BigL(dp, keyLen); + Val_DQ = BigL(dq, keyLen); + Val_InverseQ = BigL(inverseQ, keyLen); + } + } + /// + /// 通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同 + /// 注意:所有参数首字节如果是0,必须先去掉 + /// 出错将会抛出异常 + /// + /// 必须提供模数 + /// 必须提供公钥指数 + /// 私钥指数可以不提供,导出的PEM就只包含公钥 + public RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull) { + Key_Modulus = modulus;//modulus + Key_Exponent = exponent;//publicExponent + + if (dOrNull != null) { + Key_D = BigL(dOrNull, modulus.Length);//privateExponent + + //反推P、Q + BigInteger n = BigX(modulus); + BigInteger e = BigX(exponent); + BigInteger d = BigX(dOrNull); + BigInteger p = FindFactor(e, d, n); + BigInteger q = n / p; + if (p.CompareTo(q) > 0) { + BigInteger t = p; + p = q; + q = t; + } + BigInteger exp1 = d % (p - BigInteger.One); + BigInteger exp2 = d % (q - BigInteger.One); + BigInteger coeff = BigInteger.ModPow(q, p - 2, p); + + int keyLen = modulus.Length / 2; + Val_P = BigL(BigB(p), keyLen);//prime1 + Val_Q = BigL(BigB(q), keyLen);//prime2 + Val_DP = BigL(BigB(exp1), keyLen);//exponent1 + Val_DQ = BigL(BigB(exp2), keyLen);//exponent2 + Val_InverseQ = BigL(BigB(coeff), keyLen);//coefficient + } + } + + /// + /// 密钥位数 /// - public static RSACryptoServiceProvider FromPEM(string pem) { + public int KeySize { + get { + return Key_Modulus.Length * 8; + } + } + /// + /// 是否包含私钥 + /// + public bool HasPrivate { + get { + return Key_D != null; + } + } + /// + /// 将PEM中的公钥私钥转成RSA对象,如果未提供私钥,RSA中就只包含公钥。返回的RSA支持跨平台使用,但只支持在.NET Core环境中使用 + /// + public RSA GetRSA_ForCore() { + RSA rsa = RSA.Create(); + if (rsa is RSACryptoServiceProvider) { + return GetRSA_ForWindows(); + } + GetRSA__ImportParameters(rsa); + return rsa; + } + /// + /// 将PEM中的公钥私钥转成RSA对象,如果未提供私钥,RSA中就只包含公钥。.NET Core、.NET Framework均可用,但返回的RSACryptoServiceProvider不支持跨平台,所以只支持在Windows系统中使用 + /// + public RSACryptoServiceProvider GetRSA_ForWindows() { var rsaParams = new CspParameters(); rsaParams.Flags = CspProviderFlags.UseMachineKeyStore; var rsa = new RSACryptoServiceProvider(rsaParams); + GetRSA__ImportParameters(rsa); + return rsa; + } + /// + /// 将密钥参数导入到RSA对象中 + /// + public void GetRSA__ImportParameters(RSA rsa) { var param = new RSAParameters(); + param.Modulus = Key_Modulus; + param.Exponent = Key_Exponent; + if (Key_D != null) { + param.D = Key_D; + param.P = Val_P; + param.Q = Val_Q; + param.DP = Val_DP; + param.DQ = Val_DQ; + param.InverseQ = Val_InverseQ; + } + rsa.ImportParameters(param); + } + /// + /// 转成正整数,如果是负数,需要加前导0转成正整数 + /// + static public BigInteger BigX(byte[] bigb) { + if (bigb[0] > 0x7F) { + byte[] c = new byte[bigb.Length + 1]; + Array.Copy(bigb, 0, c, 1, bigb.Length); + bigb = c; + } + return new BigInteger(bigb.Reverse().ToArray());//C#的二进制是反的 + } + /// + /// BigInt导出byte整数首字节>0x7F的会加0前导,保证正整数,因此需要去掉0 + /// + static public byte[] BigB(BigInteger bigx) { + byte[] val = bigx.ToByteArray().Reverse().ToArray();//C#的二进制是反的 + if (val[0] == 0) { + byte[] c = new byte[val.Length - 1]; + Array.Copy(val, 1, c, 0, c.Length); + val = c; + } + return val; + } + /// + /// 某些密钥参数可能会少一位(32个byte只有31个,目测是密钥生成器的问题,只在c#生成的密钥中发现这种参数,java中生成的密钥没有这种现象),直接修正一下就行;这个问题与BigB有本质区别,不能动BigB + /// + static public byte[] BigL(byte[] bytes, int keyLen) { + if (keyLen - bytes.Length == 1) { + byte[] c = new byte[bytes.Length + 1]; + Array.Copy(bytes, 0, c, 1, bytes.Length); + bytes = c; + } + return bytes; + } + /// + /// 由n e d 反推 P Q + /// 资料: https://stackoverflow.com/questions/43136036/how-to-get-a-rsaprivatecrtkey-from-a-rsaprivatekey + /// https://v2ex.com/t/661736 + /// + static private BigInteger FindFactor(BigInteger e, BigInteger d, BigInteger n) { + BigInteger edMinus1 = e * d - BigInteger.One; + int s = -1; + if (edMinus1 != BigInteger.Zero) { + s = (int)(BigInteger.Log(edMinus1 & -edMinus1) / BigInteger.Log(2)); + } + BigInteger t = edMinus1 >> s; + + long now = DateTime.Now.Ticks; + for (int aInt = 2; true; aInt++) { + if (aInt % 10 == 0 && DateTime.Now.Ticks - now > 3000 * 10000) { + throw new Exception(T("推算RSA.P超时", "Estimated RSA.P timeout"));//测试最多循环2次,1024位的速度很快 8ms + } + + BigInteger aPow = BigInteger.ModPow(new BigInteger(aInt), t, n); + for (int i = 1; i <= s; i++) { + if (aPow == BigInteger.One) { + break; + } + if (aPow == n - BigInteger.One) { + break; + } + BigInteger aPowSquared = aPow * aPow % n; + if (aPowSquared == BigInteger.One) { + return BigInteger.GreatestCommonDivisor(aPow - BigInteger.One, n); + } + aPow = aPowSquared; + } + } + } + + + + + + + + + + + + /// + /// 用PEM格式密钥对创建RSA,支持PKCS#1、PKCS#8格式的PEM + /// 出错将会抛出异常 + /// + static public RSA_PEM FromPEM(string pem) { + RSA_PEM param = new RSA_PEM(); var base64 = _PEMCode.Replace(pem, ""); - var data = RSA_Unit.Base64DecodeBytes(base64); + byte[] data = null; + try { data = Convert.FromBase64String(base64); } catch { } if (data == null) { - throw new Exception("PEM内容无效"); + throw new Exception(T("PEM内容无效", "Invalid PEM content")); } var idx = 0; @@ -43,7 +299,7 @@ public static RSACryptoServiceProvider FromPEM(string pem) { return data[idx++]; } } - throw new Exception("PEM未能提取到数据"); + throw new Exception(T("PEM未能提取到数据", "Failed to extract data from PEM")); }; //读取块数据 Func readBlock = () => { @@ -52,7 +308,10 @@ public static RSACryptoServiceProvider FromPEM(string pem) { idx++; len--; } - var val = data.sub(idx, len); + var val = new byte[len]; + for (var i = 0; i < len; i++) { + val[i] = data[idx + i]; + } idx += len; return val; }; @@ -76,20 +335,24 @@ public static RSACryptoServiceProvider FromPEM(string pem) { /****使用公钥****/ //读取数据总长度 readLen(0x30); - if (!eq(_SeqOID)) { - throw new Exception("PEM未知格式"); + + //检测PKCS8 + var idx2 = idx; + if (eq(_SeqOID)) { + //读取1长度 + readLen(0x03); + idx++;//跳过0x00 + //读取2长度 + readLen(0x30); + } else { + idx = idx2; } - //读取1长度 - readLen(0x03); - idx++;//跳过0x00 - //读取2长度 - readLen(0x30); //Modulus - param.Modulus = readBlock(); + param.Key_Modulus = readBlock(); //Exponent - param.Exponent = readBlock(); + param.Key_Exponent = readBlock(); } else if (pem.Contains("PRIVATE KEY")) { /****使用私钥****/ //读取数据总长度 @@ -97,7 +360,7 @@ public static RSACryptoServiceProvider FromPEM(string pem) { //读取版本号 if (!eq(_Ver)) { - throw new Exception("PEM未知版本"); + throw new Exception(T("PEM未知版本", "Unknown PEM version")); } //检测PKCS8 @@ -110,43 +373,129 @@ public static RSACryptoServiceProvider FromPEM(string pem) { //读取版本号 if (!eq(_Ver)) { - throw new Exception("PEM版本无效"); + throw new Exception(T("PEM版本无效", "Invalid PEM version")); } } else { idx = idx2; } //读取数据 - param.Modulus = readBlock(); - param.Exponent = readBlock(); - param.D = readBlock(); - param.P = readBlock(); - param.Q = readBlock(); - param.DP = readBlock(); - param.DQ = readBlock(); - param.InverseQ = readBlock(); + param.Key_Modulus = readBlock(); + param.Key_Exponent = readBlock(); + int keyLen = param.Key_Modulus.Length; + param.Key_D = BigL(readBlock(), keyLen); + keyLen = keyLen / 2; + param.Val_P = BigL(readBlock(), keyLen); + param.Val_Q = BigL(readBlock(), keyLen); + param.Val_DP = BigL(readBlock(), keyLen); + param.Val_DQ = BigL(readBlock(), keyLen); + param.Val_InverseQ = BigL(readBlock(), keyLen); } else { - throw new Exception("pem需要BEGIN END标头"); + throw new Exception(T("pem需要BEGIN END标头", "pem requires BEGIN END header")); } - rsa.ImportParameters(param); - return rsa; + return param; } - static private Regex _PEMCode = new Regex(@"--+.+?--+|\s+"); - static private byte[] _SeqOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; - static private byte[] _Ver = new byte[] { 0x02, 0x01, 0x00 }; + static private readonly Regex _PEMCode = new Regex(@"--+.+?--+|\s+"); + static private readonly byte[] _SeqOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; + static private readonly byte[] _Ver = new byte[] { 0x02, 0x01, 0x00 }; + + + + + + /// + /// 将当前PEM中的密钥对复制出一个新的PEM对象 + /// 。convertToPublic:等于true时含私钥的PEM将只返回公钥,仅含公钥的PEM不受影响 + /// + public RSA_PEM CopyToNew(bool convertToPublic = false) { + if (convertToPublic) { + return new RSA_PEM(Key_Modulus, Key_Exponent, null, null, null, null, null, null); + } + return new RSA_PEM(Key_Modulus, Key_Exponent, Key_D, Val_P, Val_Q, Val_DP, Val_DQ, Val_InverseQ); + } + /// + /// 【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新PEM对象;比如用于:私钥加密、公钥解密,这是非常规的用法 + /// 。当前对象必须含私钥,否则无法交换会直接抛异常 + /// 。注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥 + /// + public RSA_PEM SwapKey_Exponent_D__Unsafe() { + if (Key_D == null) throw new Exception(T("SwapKey只支持私钥", "SwapKey only supports private keys")); + return new RSA_PEM(Key_Modulus, Key_D, Key_Exponent); + } + + /// + /// 将RSA中的密钥对转换成PEM PKCS#1格式 + /// 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// 。公钥如:-----BEGIN RSA PUBLIC KEY-----,私钥如:-----BEGIN RSA PRIVATE KEY----- + /// 。似乎导出PKCS#1公钥用的比较少,PKCS#8的公钥用的多些,私钥#1#8都差不多 + /// + public string ToPEM_PKCS1(bool convertToPublic = false) { + return ToPEM(convertToPublic, false, false); + } + /// + /// 将RSA中的密钥对转换成PEM PKCS#8格式 + /// 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// 。公钥如:-----BEGIN PUBLIC KEY-----,私钥如:-----BEGIN PRIVATE KEY----- + /// + public string ToPEM_PKCS8(bool convertToPublic = false) { + return ToPEM(convertToPublic, true, true); + } + /// + /// 将RSA中的密钥对转换成PEM格式 + /// 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// 。privateUsePKCS8:私钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PRIVATE KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PRIVATE KEY-----),返回公钥时此参数无效;两种格式使用都比较常见 + /// 。publicUsePKCS8:公钥的返回格式,等于true时返回PKCS#8格式(-----BEGIN PUBLIC KEY-----),否则返回PKCS#1格式(-----BEGIN RSA PUBLIC KEY-----),返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式似乎比较少见公钥 + /// + public string ToPEM(bool convertToPublic, bool privateUsePKCS8, bool publicUsePKCS8) { + byte[] der = ToDER(convertToPublic, privateUsePKCS8, publicUsePKCS8); + if (Key_D == null || convertToPublic) { + var flag = " PUBLIC KEY"; + if (!publicUsePKCS8) { + flag = " RSA" + flag; + } + return "-----BEGIN" + flag + "-----\n" + TextBreak(Convert.ToBase64String(der), 64) + "\n-----END" + flag + "-----"; + } else { + var flag = " PRIVATE KEY"; + if (!privateUsePKCS8) { + flag = " RSA" + flag; + } + return "-----BEGIN" + flag + "-----\n" + TextBreak(Convert.ToBase64String(der), 64) + "\n-----END" + flag + "-----"; + } + } + // 把字符串按每行多少个字断行 + static private string TextBreak(string text, int line) { + var idx = 0; + var len = text.Length; + var str = new StringBuilder(); + while (idx < len) { + if (idx > 0) { + str.Append('\n'); + } + if (idx + line >= len) { + str.Append(text.Substring(idx)); + } else { + str.Append(text.Substring(idx, line)); + } + idx += line; + } + return str.ToString(); + } /// - /// 将RSA中的密钥对转换成PEM格式,usePKCS8=false时返回PKCS#1格式,否则返回PKCS#8格式,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// 将RSA中的密钥对转换成DER格式,DER格式为PEM中的Base64文本编码前的二进制数据 + /// 。convertToPublic:等于true时含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// 。privateUsePKCS8:私钥的返回格式,等于true时返回PKCS#8格式,否则返回PKCS#1格式,返回公钥时此参数无效;两种格式使用都比较常见 + /// 。publicUsePKCS8:公钥的返回格式,等于true时返回PKCS#8格式,否则返回PKCS#1格式,返回私钥时此参数无效;一般用的多的是true PKCS#8格式公钥,PKCS#1格式似乎比较少见公钥 /// - public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, bool usePKCS8) { + public byte[] ToDER(bool convertToPublic, bool privateUsePKCS8, bool publicUsePKCS8) { //https://www.jianshu.com/p/25803dd9527d //https://www.cnblogs.com/ylz8401/p/8443819.html //https://blog.csdn.net/jiayanhui2877/article/details/47187077 @@ -190,62 +539,67 @@ public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, b return ms.ToArray(); }; + Action writeAll = (stream, byts) => { + stream.Write(byts, 0, byts.Length); + }; - if (rsa.PublicOnly || convertToPublic) { + if (Key_D == null || convertToPublic) { /****生成公钥****/ - var param = rsa.ExportParameters(false); - //写入总字节数,不含本段长度,额外需要24字节的头,后续计算好填入 ms.WriteByte(0x30); var index1 = (int)ms.Length; - //固定内容 - // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" - ms.writeAll(_SeqOID); + //PKCS8 多一段数据 + int index2 = -1, index3 = -1; + if (publicUsePKCS8) { + //固定内容 + // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" + writeAll(ms, _SeqOID); - //从0x00开始的后续长度 - ms.WriteByte(0x03); - var index2 = (int)ms.Length; - ms.WriteByte(0x00); + //从0x00开始的后续长度 + ms.WriteByte(0x03); + index2 = (int)ms.Length; + ms.WriteByte(0x00); - //后续内容长度 - ms.WriteByte(0x30); - var index3 = (int)ms.Length; + //后续内容长度 + ms.WriteByte(0x30); + index3 = (int)ms.Length; + } //写入Modulus - writeBlock(param.Modulus); + writeBlock(Key_Modulus); //写入Exponent - writeBlock(param.Exponent); + writeBlock(Key_Exponent); //计算空缺的长度 var byts = ms.ToArray(); - byts = writeLen(index3, byts); - byts = writeLen(index2, byts); + if (index2 != -1) { + byts = writeLen(index3, byts); + byts = writeLen(index2, byts); + } byts = writeLen(index1, byts); - - return "-----BEGIN PUBLIC KEY-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END PUBLIC KEY-----"; + return byts; } else { /****生成私钥****/ - var param = rsa.ExportParameters(true); //写入总字节数,后续写入 ms.WriteByte(0x30); int index1 = (int)ms.Length; //写入版本号 - ms.writeAll(_Ver); + writeAll(ms, _Ver); //PKCS8 多一段数据 int index2 = -1, index3 = -1; - if (usePKCS8) { + if (privateUsePKCS8) { //固定内容 - ms.writeAll(_SeqOID); + writeAll(ms, _SeqOID); //后续内容长度 ms.WriteByte(0x04); @@ -256,18 +610,18 @@ public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, b index3 = (int)ms.Length; //写入版本号 - ms.writeAll(_Ver); + writeAll(ms, _Ver); } //写入数据 - writeBlock(param.Modulus); - writeBlock(param.Exponent); - writeBlock(param.D); - writeBlock(param.P); - writeBlock(param.Q); - writeBlock(param.DP); - writeBlock(param.DQ); - writeBlock(param.InverseQ); + writeBlock(Key_Modulus); + writeBlock(Key_Exponent); + writeBlock(Key_D); + writeBlock(Val_P); + writeBlock(Val_Q); + writeBlock(Val_DP); + writeBlock(Val_DQ); + writeBlock(Val_InverseQ); //计算空缺的长度 @@ -279,13 +633,124 @@ public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, b } byts = writeLen(index1, byts); + return byts; + } + } - var flag = " PRIVATE KEY"; - if (!usePKCS8) { - flag = " RSA" + flag; + + + + + + + + + + + + + + + /// + /// 将XML格式密钥转成PEM,支持公钥xml、私钥xml + /// 出错将会抛出异常 + /// + static public RSA_PEM FromXML(string xml) { + RSA_PEM rtv = new RSA_PEM(); + + Match xmlM = xmlExp.Match(xml); + if (!xmlM.Success) { + throw new Exception(T("XML内容不符合要求", "XML content does not meet requirements")); + } + + Match tagM = xmlTagExp.Match(xmlM.Groups[1].Value); + while (tagM.Success) { + string tag = tagM.Groups[1].Value; + string b64 = tagM.Groups[2].Value; + byte[] val = Convert.FromBase64String(b64); + switch (tag) { + case "Modulus": rtv.Key_Modulus = val; break; + case "Exponent": rtv.Key_Exponent = val; break; + case "D": rtv.Key_D = val; break; + + case "P": rtv.Val_P = val; break; + case "Q": rtv.Val_Q = val; break; + case "DP": rtv.Val_DP = val; break; + case "DQ": rtv.Val_DQ = val; break; + case "InverseQ": rtv.Val_InverseQ = val; break; + } + tagM = tagM.NextMatch(); + } + + if (rtv.Key_Modulus == null || rtv.Key_Exponent == null) { + throw new Exception(T("XML公钥丢失", "Public key in XML is missing")); + } + if (rtv.Key_D != null) { + if (rtv.Val_P == null || rtv.Val_Q == null || rtv.Val_DP == null || rtv.Val_DQ == null || rtv.Val_InverseQ == null) { + return new RSA_PEM(rtv.Key_Modulus, rtv.Key_Exponent, rtv.Key_D); } - return "-----BEGIN" + flag + "-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END" + flag + "-----"; } + + return rtv; + } + static private readonly Regex xmlExp = new Regex("\\s*([<>\\/\\+=\\w\\s]+)\\s*"); + static private readonly Regex xmlTagExp = new Regex("<(.+?)>\\s*([^<]+?)\\s* + /// 将RSA中的密钥对转换成XML格式 + /// ,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// + public string ToXML(bool convertToPublic) { + StringBuilder str = new StringBuilder(); + str.Append(""); + str.Append("" + Convert.ToBase64String(Key_Modulus) + ""); + str.Append("" + Convert.ToBase64String(Key_Exponent) + ""); + if (Key_D == null || convertToPublic) { + /****生成公钥****/ + //NOOP + } else { + /****生成私钥****/ + str.Append("

" + Convert.ToBase64String(Val_P) + "

"); + str.Append("" + Convert.ToBase64String(Val_Q) + ""); + str.Append("" + Convert.ToBase64String(Val_DP) + ""); + str.Append("" + Convert.ToBase64String(Val_DQ) + ""); + str.Append("" + Convert.ToBase64String(Val_InverseQ) + ""); + str.Append("" + Convert.ToBase64String(Key_D) + ""); + } + str.Append("
"); + return str.ToString(); } + + + + /// + /// 简版多语言支持,根据当前语言返回中文或英文 + /// + static public string T(string zh, string en) { + return Lang == "zh" ? zh : en; + } + static private string _lang; + /// + /// 简版多语言支持,取值:zh(简体中文)、en(English-US),默认根据系统取值 + /// + static public string Lang { + get { + if (_lang == null) { + var locale = CultureInfo.CurrentCulture.Name.Replace('_', '-').ToLower(); + if (Regex.IsMatch(locale, "\\b(zh|cn)\\b")) { + _lang = "zh"; + } else { + _lang = "en"; + } + } + return _lang; + } + set { _lang = value; } + } + } } diff --git a/RSA_Unit.cs b/RSA_Unit.cs deleted file mode 100644 index 1373389..0000000 --- a/RSA_Unit.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace RSA { - /// - /// 封装的一些通用方法 - /// - public class RSA_Unit { - static public string Base64EncodeBytes(byte[] byts) { - return Convert.ToBase64String(byts); - } - static public byte[] Base64DecodeBytes(string str) { - try { - return Convert.FromBase64String(str); - } catch { - return null; - } - } - /// - /// 把字符串按每行多少个字断行 - /// - static public string TextBreak(string text, int line) { - var idx = 0; - var len = text.Length; - var str = new StringBuilder(); - while (idx < len) { - if (idx > 0) { - str.Append('\n'); - } - if (idx + line >= len) { - str.Append(text.Substring(idx)); - } else { - str.Append(text.Substring(idx, line)); - } - idx += line; - } - return str.ToString(); - } - } - - static public class Extensions { - /// - /// 从数组start开始到指定长度复制一份 - /// - static public T[] sub(this T[] arr, int start, int count) { - T[] val = new T[count]; - for (var i = 0; i < count; i++) { - val[i] = arr[start + i]; - } - return val; - } - static public void writeAll(this Stream stream, byte[] byts) { - stream.Write(byts, 0, byts.Length); - } - } -} diff --git a/RSA_Util.cs b/RSA_Util.cs new file mode 100644 index 0000000..31ff216 --- /dev/null +++ b/RSA_Util.cs @@ -0,0 +1,1003 @@ +// 1:直接编译调用.NET Framework 4.6以上版本或.NET Core代码,0:使用反射进行调用;使用.NET Framework 4.6以上版本框架或使用.NET Core框架时可改成1 +#define RSA_Util_NewNET_CompileCode_0 +#if (RSA_BUILD__NET_CORE || NETCOREAPP || NETSTANDARD || NET) //使用.NET Core框架时自动设为1。csproj:PropertyGroup.DefineConstants + https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/preprocessor-directives +#define RSA_Util_NewNET_CompileCode_1 +#endif + +// 1:直接编译使用BouncyCastle的代码,0:使用反射进行调用;调用了RSA_Util.UseBouncyCastle方法时可改成1 +#define RSA_Util_BouncyCastle_CompileCode_0 + + +using System; +using System.IO; +using System.Numerics; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +#if RSA_Util_BouncyCastle_CompileCode_1 +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; +using BcInt = Org.BouncyCastle.Math.BigInteger; +#endif + +namespace com.github.xiangyuecn.rsacsharp { + /// + /// RSA操作类,.NET Core、.NET Framework均可用:.NET Core下由实际的RSA实现类提供支持,.NET Framework 4.5及以下由RSACryptoServiceProvider提供支持,.NET Framework 4.6及以上由RSACng提供支持;或者引入BouncyCastle加密增强库提供支持。 + /// GitHub: https://github.com/xiangyuecn/RSA-csharp + /// + public class RSA_Util { + /// + /// 导出XML格式密钥,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// + public string ToXML(bool convertToPublic = false) { + return ToPEM(convertToPublic).ToXML(convertToPublic); + } + /// + /// 将密钥导出成PEM对象,如果convertToPublic含私钥的RSA将只返回公钥,仅含公钥的RSA不受影响 + /// + public RSA_PEM ToPEM(bool convertToPublic = false) { + return PEM__.CopyToNew(convertToPublic); + } + /// + /// 【不安全、不建议使用】对调交换公钥指数(Key_Exponent)和私钥指数(Key_D):把公钥当私钥使用(new.Key_D=this.Key_Exponent)、私钥当公钥使用(new.Key_Exponent=this.Key_D),返回一个新RSA对象;比如用于:私钥加密、公钥解密,这是非常规的用法。 + ///

当前密钥如果只包含公钥,将不会发生对调,返回的新RSA将允许用公钥进行解密和签名操作;但.NET自带的RSA不支持仅含公钥的密钥进行解密和签名,必须进行指数对调(如果是.NET Framework 4.5及以下版本,公钥私钥均不支持),使用NoPadding填充方式或IsUseBouncyCastle时无此问题。 + ///

注意:把公钥当私钥使用是非常不安全的,因为绝大部分生成的密钥的公钥指数为 0x10001(AQAB),太容易被猜测到,无法作为真正意义上的私钥。 + ///

部分私钥加密实现中,比如Java自带的RSA,使用非NoPadding填充方式时,用私钥对象进行加密可能会采用EMSA-PKCS1-v1_5填充方式(用私钥指数构造成公钥对象无此问题),因此在不同程序之间互通时,可能需要自行使用对应填充算法先对数据进行填充,然后再用NoPadding填充方式进行加密(解密也按NoPadding填充进行解密,然后去除填充数据)。 + ///
+ public RSA_Util SwapKey_Exponent_D__Unsafe() { + if (PEM__.Key_D == null) { + var rsa = new RSA_Util(PEM__.CopyToNew(false)); + rsa.allowKeyDNull = true; + return rsa; + } + return new RSA_Util(PEM__.SwapKey_Exponent_D__Unsafe()); + } + + + + /// + /// 内置加密解密填充方式列表 + /// + static public string[] RSAPadding_Enc_DefaultKeys() { + string s = "NO, PKCS1"; + s += ", OAEP+SHA1, OAEP+SHA256, OAEP+SHA224, OAEP+SHA384, OAEP+SHA512"; + s += ", OAEP+SHA-512/224, OAEP+SHA-512/256"; + s += ", OAEP+SHA3-256, OAEP+SHA3-224, OAEP+SHA3-384, OAEP+SHA3-512"; + s += ", OAEP+MD5"; + return s.Split(new string[] { ", " }, StringSplitOptions.None); + } + /// + /// 将填充方式格式化成内置的RSA加密解密填充模式,padding取值和对应的填充模式: + /// + ///
null: 等同于PKCS1 + ///
"": 等同于PKCS1 + ///
RSA: 等同于PKCS1 + ///
PKCS: 等同于PKCS1 + ///
RAW: 等同于NO + ///
OAEP: 等同于OAEP+SHA1 + ///
RSA/ECB/OAEPPadding: 等同于OAEP+SHA1 + ///
+ ///
NO: RSA/ECB/NoPadding + ///
PKCS1: RSA/ECB/PKCS1Padding (默认值,等同于"RSA") + ///
OAEP+SHA1 : RSA/ECB/OAEPwithSHA-1andMGF1Padding + ///
OAEP+SHA256: RSA/ECB/OAEPwithSHA-256andMGF1Padding + ///
OAEP+SHA224: RSA/ECB/OAEPwithSHA-224andMGF1Padding + ///
OAEP+SHA384: RSA/ECB/OAEPwithSHA-384andMGF1Padding + ///
OAEP+SHA512: RSA/ECB/OAEPwithSHA-512andMGF1Padding + ///
OAEP+SHA-512/224: RSA/ECB/OAEPwithSHA-512/224andMGF1Padding (SHA-512/*** 2012年发布) + ///
OAEP+SHA-512/256: RSA/ECB/OAEPwithSHA-512/256andMGF1Padding + ///
OAEP+SHA3-256: RSA/ECB/OAEPwithSHA3-256andMGF1Padding (SHA3-*** 2015年发布) + ///
OAEP+SHA3-224: RSA/ECB/OAEPwithSHA3-224andMGF1Padding + ///
OAEP+SHA3-384: RSA/ECB/OAEPwithSHA3-384andMGF1Padding + ///
OAEP+SHA3-512: RSA/ECB/OAEPwithSHA3-512andMGF1Padding + ///
OAEP+MD5 : RSA/ECB/OAEPwithMD5andMGF1Padding + ///
+ ///
如果padding包含RSA字符串,将原样返回此值,用于提供可能支持的任何值 + ///
非以上取值,将会抛异常 + ///
+ ///
其中OAEP的掩码生成函数MGF1使用和OAEP相同的Hash算法 + ///
+ ///
以上填充模式全部可用于BouncyCastle的RSA实现;但如果是使用的.NET自带的RSA实现,将会有部分模式无法支持:不支持全部SHA224、SHA-512/256、SHA-512/224,SHA3需要.NET8以上才支持,.NET Framework 4.5及以下只持OAEP+SHA1不支持其他OAEP + ///
+ ///
参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsaencryptionpadding + ///
+ ///
+ static public string RSAPadding_Enc(string padding) { + string val = padding; + if (val == null || val.Length == 0) val = "PKCS1"; + val = val.ToUpper(); + + if ("RSA" == val || "PKCS" == val) val = "PKCS1"; + if ("OAEP" == val || val.EndsWith("/OAEPPADDING")) val = "OAEP+SHA1"; + if ("RAW" == val) val = "NO"; + if (val.IndexOf("RSA") != -1) return padding; + + switch (val) { + case "PKCS1": return "RSA/ECB/PKCS1Padding"; + case "NO": return "RSA/ECB/NoPadding"; + } + if (val.StartsWith("OAEP+")) { + val = val.Replace("OAEP+", ""); + switch (val) { + case "SHA1": + case "SHA256": + case "SHA224": + case "SHA384": + case "SHA512": + case "SHA512/224": + case "SHA512/256": + val = "SHA-" + val.Substring(3); break; + } + switch (val) { + case "SHA-1": + case "SHA-256": + case "SHA-224": + case "SHA-384": + case "SHA-512": + case "SHA3-256": + case "SHA3-224": + case "SHA3-384": + case "SHA3-512": + case "SHA-512/224": + case "SHA-512/256": + case "MD5": + return "RSA/ECB/OAEPwith" + val + "andMGF1Padding"; + } + } + throw new Exception(T("RSAPadding_Enc未定义Padding: ", "RSAPadding_Enc does not define Padding: ") + padding); + } + + /// + /// 内置签名填充方式列表 + /// + static public string[] RSAPadding_Sign_DefaultKeys() { + string s = "PKCS1+SHA1, PKCS1+SHA256, PKCS1+SHA224, PKCS1+SHA384, PKCS1+SHA512"; + s += ", PKCS1+SHA-512/224, PKCS1+SHA-512/256"; + s += ", PKCS1+SHA3-256, PKCS1+SHA3-224, PKCS1+SHA3-384, PKCS1+SHA3-512"; + s += ", PKCS1+MD5"; + s += ", PSS+SHA1, PSS+SHA256, PSS+SHA224, PSS+SHA384, PSS+SHA512"; + s += ", PSS+SHA-512/224, PSS+SHA-512/256"; + s += ", PSS+SHA3-256, PSS+SHA3-224, PSS+SHA3-384, PSS+SHA3-512"; + s += ", PSS+MD5"; + return s.Split(new string[] { ", " }, StringSplitOptions.None); + } + /// + /// 将填充方式转换成内置的RSA签名填充模式,hash取值和对应的填充模式: + /// + ///
SHA*** : 等同于PKCS1+SHA***,比如"SHA256" == "PKCS1+SHA256" + ///
MD5 : 等同于PKCS1+MD5 + ///
RSASSA-PSS: 等同于PSS+SHA1 + ///
+ ///
PKCS1+SHA1 : SHA1withRSA + ///
PKCS1+SHA256: SHA256withRSA + ///
PKCS1+SHA224: SHA224withRSA + ///
PKCS1+SHA384: SHA384withRSA + ///
PKCS1+SHA512: SHA512withRSA + ///
PKCS1+SHA-512/224: SHA512/224withRSA (SHA-512/*** 2012年发布) + ///
PKCS1+SHA-512/256: SHA512/256withRSA + ///
PKCS1+SHA3-256: SHA3-256withRSA (SHA3-*** 2015年发布) + ///
PKCS1+SHA3-224: SHA3-224withRSA + ///
PKCS1+SHA3-384: SHA3-384withRSA + ///
PKCS1+SHA3-512: SHA3-512withRSA + ///
PKCS1+MD5 : MD5withRSA + ///
+ ///
PSS+SHA1 : SHA1withRSA/PSS + ///
PSS+SHA256: SHA256withRSA/PSS + ///
PSS+SHA224: SHA224withRSA/PSS + ///
PSS+SHA384: SHA384withRSA/PSS + ///
PSS+SHA512: SHA512withRSA/PSS + ///
PSS+SHA-512/224: SHA512/224withRSA/PSS (SHA-512/*** 2012年发布) + ///
PSS+SHA-512/256: SHA512/256withRSA/PSS + ///
PSS+SHA3-256: SHA3-256withRSA/PSS (SHA3-*** 2015年发布) + ///
PSS+SHA3-224: SHA3-224withRSA/PSS + ///
PSS+SHA3-384: SHA3-384withRSA/PSS + ///
PSS+SHA3-512: SHA3-512withRSA/PSS + ///
PSS+MD5 : MD5withRSA/PSS + ///
+ ///
如果hash包含RSA字符串,将原样返回此值,用于提供可能支持的任何值 + ///
非以上取值,将会抛异常 + ///
+ ///
其中PSS的salt字节数等于使用的Hash算法字节数,PSS的掩码生成函数MGF1使用和PSS相同的Hash算法,跟踪属性TrailerField取值固定为0xBC + ///
+ ///
以上填充模式全部可用于BouncyCastle的RSA实现;但如果是使用的.NET自带的RSA实现,将会有部分模式无法支持:不支持全部SHA224、SHA-512/256、SHA-512/224,SHA3需要.NET8以上才支持,.NET Framework 4.5及以下不支持PSS + ///
+ ///
参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsasignaturepadding + ///
+ ///
+ static public string RSAPadding_Sign(string hash) { + string val = hash == null ? "" : hash; + val = val.ToUpper(); + + if ("RSASSA-PSS" == val) val = "PSS+SHA1"; + if (val.IndexOf("RSA") != -1) return hash; + + string pss = ""; + if (val.StartsWith("PSS+")) { + val = val.Substring(4); + pss = "/PSS"; + } else if (val.StartsWith("PKCS1+")) { + val = val.Substring(6); + } + switch (val) { + case "SHA-1": + case "SHA-256": + case "SHA-224": + case "SHA-384": + case "SHA-512": + case "SHA-512/224": + case "SHA-512/256": + val = val.Replace("-", ""); break; + } + switch (val) { + case "SHA1": + case "SHA256": + case "SHA224": + case "SHA384": + case "SHA512": + case "SHA3-256": + case "SHA3-224": + case "SHA3-384": + case "SHA3-512": + case "SHA512/224": + case "SHA512/256": + case "MD5": + return val + "withRSA" + pss; + } + throw new Exception(T("RSAPadding_Sign未定义Hash: ", "RSAPadding_Sign does not define Hash: ") + hash); + } + + static private string NetNotSupportMsg(string tag) { + return T(".NET不支持" + tag + ",解决办法:", ".NET does not support " + tag + ", solution: ") + Msg_Bc; + } + static private string NetLowVerSupportMsg(string tag) { + return T(".NET Framework版本低于4.6,不支持" + tag + ",解决办法:升级使用.NET Framework 4.6及以上版本,或者", "The .NET Framework version is lower than 4.6 and does not support " + tag + ". Solution: upgrade to .NET Framework 4.6 and above, or ") + Msg_Bc; + } + static private string Msg_Bc { + get { + return T("引入BouncyCastle加密增强库来扩充.NET加密功能(NuGet:Portable.BouncyCastle或BouncyCastle.Cryptography,文档 https://www.bouncycastle.org/csharp/ ),并且在程序启动时调用" + Msg_Bc_Reg + "进行注册即可得到全部支持。", "import the BouncyCastle encryption enhancement library to expand the .NET encryption function (NuGet: Portable.BouncyCastle or BouncyCastle.Cryptography, documentation https://www.bouncycastle.org/csharp/ ), and call" + Msg_Bc_Reg + "to register when the program starts to get full support."); + } + } + static private readonly string Msg_Bc_Reg = " `RSA_Util.UseBouncyCastle( typeof(RsaEngine).Assembly )` "; + /// + /// 是否是因为.NET兼容性产生的错误 + /// + static public bool IsDotNetSupportError(string errMsg) { + return errMsg.Contains(Msg_Bc_Reg); + } + /// + /// 将Hash算法名字转换成.NET对象,不支持的将返回null,hash需大写 + /// 。.NET 对 HashAlgorithm.Create 支持混乱,中间有些版本不允许调用 + /// + static public HashAlgorithm HashFromName(string hash) { + HashAlgorithm obj = null; + try { obj = HashAlgorithm.Create(hash); } catch { } + if (obj == null) try { obj = HashAlgorithm.Create(hash.Replace("SHA-", "SHA")); } catch { } + if (obj != null) return obj; + try { + var types = typeof(SHA1).Assembly.GetTypes(); + var name1 = hash.Replace("-", "");//SHA256 + var name2 = hash.Replace("-", "_");//SHA3_256 + foreach (var type in types) { + var name = type.Name.ToUpper(); + if (name == name1 || name == name2) { + var fn = type.GetMethod("Create", new Type[0]); + if (fn != null && typeof(HashAlgorithm).IsAssignableFrom(fn.ReturnType)) { + return (HashAlgorithm)fn.Invoke(null, new object[0]); + } + } + } + } catch { } + return null; + } + static private void checkHashSupport(string hash) { + if (HashFromName(hash) == null) { + throw new Exception(T("本机.NET版本不支持" + hash + "摘要算法,升级使用更高版本的.NET可能会得到支持,或者", "The native .NET version does not support the " + hash + " digest algorithm, upgrading to a later version of .NET may be supported, or ") + Msg_Bc); + } + } + /// + /// 简版多语言支持,根据当前语言返回中文或英文,简化调用 + /// + static private string T(string zh, string en) { + return RSA_PEM.T(zh, en); + } + + + + + /// + /// 加密任意长度字符串(utf-8)返回base64,出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考 + /// + public string Encrypt(string padding, string str) { + return Convert.ToBase64String(__Encrypt(padding, Encoding.UTF8.GetBytes(str))); + } + /// + /// 加密任意长度数据,出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考 + /// + public byte[] Encrypt(string padding, byte[] data) { + return __Encrypt(padding, data); + } + /// + /// 解密任意长度密文(base64)得到字符串(utf-8),出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考 + /// + public string Decrypt(string padding, string str) { + if (string.IsNullOrEmpty(str)) { + return ""; + } + byte[] byts = Convert.FromBase64String(str); + var val = __Decrypt(padding, byts); + return Encoding.UTF8.GetString(val); + } + /// + /// 解密任意长度数据,出错抛异常。本方法线程安全。padding指定填充方式(如:PKCS1、OAEP+SHA256大写),使用空值时默认为PKCS1,取值参考 + /// + public byte[] Decrypt(string padding, byte[] data) { + return __Decrypt(padding, data); + } + + + + /// + /// 对字符串str进行签名,返回base64结果,出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式(如:SHA256、PSS+SHA1大写),取值参考 + /// + public string Sign(string hash, string str) { + return Convert.ToBase64String(__Sign(hash, Encoding.UTF8.GetBytes(str))); + } + /// + /// 对data进行签名,出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式(如:SHA256、PSS+SHA1大写),取值参考 + /// + public byte[] Sign(string hash, byte[] data) { + return __Sign(hash, data); + } + /// + /// 验证字符串str的签名是否是sign(base64),出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式(如:SHA256、PSS+SHA1大写),取值参考 + /// + public bool Verify(string hash, string sign, string str) { + byte[] byts = null; + try { byts = Convert.FromBase64String(sign); } catch { } + if (byts == null) { + return false; + } + return __Verify(hash, byts, Encoding.UTF8.GetBytes(str)); + } + /// + /// 验证data的签名是否是sign,出错抛异常。本方法线程安全。hash指定签名摘要算法和填充方式(如:SHA256、PSS+SHA1大写),取值参考 + /// + public bool Verify(string hash, byte[] sign, byte[] data) { + return __Verify(hash, sign, data); + } + + + + + + /// + /// 用指定密钥大小创建一个新的RSA,会生成新密钥,出错抛异常 + /// + public RSA_Util(int keySize) { + RSA rsa = null; + if (IS_CORE) { + rsa = RSA.Create(); + rsa.KeySize = keySize; + } + if (rsa == null || rsa is RSACryptoServiceProvider) { + var rsaParams = new CspParameters(); + rsaParams.Flags = CspProviderFlags.UseMachineKeyStore; + rsa = new RSACryptoServiceProvider(keySize, rsaParams); + } + SetPEM__(new RSA_PEM(rsa, false)); + } + /// + /// 通过指定的pem文件密钥或xml字符串密钥,创建一个RSA,pem或xml内可以只包含一个公钥或私钥,或都包含,出错抛异常 + /// + public RSA_Util(string pemOrXML) { + if (pemOrXML.Trim().StartsWith("<")) { + SetPEM__(RSA_PEM.FromXML(pemOrXML)); + } else { + SetPEM__(RSA_PEM.FromPEM(pemOrXML)); + } + } + /// + /// 通过一个pem对象创建RSA,pem为公钥或私钥,出错抛异常 + /// + public RSA_Util(RSA_PEM pem) { + SetPEM__(pem); + } + /// + /// 本方法会先生成RSA_PEM再创建RSA:通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同 + /// 注意:所有参数首字节如果是0,必须先去掉 + /// 出错将会抛出异常 + /// + /// 必须提供模数 + /// 必须提供公钥指数 + /// 私钥指数可以不提供,导出的PEM就只包含公钥 + public RSA_Util(byte[] modulus, byte[] exponent, byte[] dOrNull) { + SetPEM__(new RSA_PEM(modulus, exponent, dOrNull)); + } + /// + /// 本方法会先生成RSA_PEM再创建RSA:通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥) + /// 注意:所有参数首字节如果是0,必须先去掉 + /// + public RSA_Util(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ) { + SetPEM__(new RSA_PEM(modulus, exponent, d, p, q, dp, dq, inverseQ)); + } + + + + + /// + /// 密钥位数 + /// + public int KeySize { get; private set; } + /// + /// 是否包含私钥 + /// + public bool HasPrivate { get; private set; } + + + /// + /// 获取最底层的RSA对象,.NET Core下为实际的RSA实现类,.NET Framework 4.5及以下RSACryptoServiceProvider,.NET Framework 4.6及以上RSACng;注意:IsUseBouncyCastle时将不会使用.NET的RSA + /// + public RSA RSAObject { + get { + return createRSA(); + } + } + /// + /// 最底层的RSA对象是否是使用的.NET Core(RSA),否则将是使用的.NET Framework(4.5及以下RSACryptoServiceProvider、4.6及以上RSACng);注意:IsUseBouncyCastle时将不会使用.NET的RSA + /// + public bool RSAIsUseCore { + get { + return IS_CORE && !(createRSA() is RSACryptoServiceProvider); + } + } + + + private void SetPEM__(RSA_PEM pem) { + PEM__ = pem; + KeySize = pem.KeySize; + HasPrivate = pem.HasPrivate; + } + private RSA_PEM PEM__; + private RSA createRSA() { + if (IS_CORE) return PEM__.GetRSA_ForCore(); + if (IS_CoreOr46) { //必须使用RSACng,不然新填充方式会抛出不支持 + return GetRSA_WindowsCng(PEM__); + } + return PEM__.GetRSA_ForWindows(); + } + + + private bool allowKeyDNull; + private void checkKeyD(bool usePub) { + if (usePub) return; + if (PEM__.Key_D != null) return; + if (allowKeyDNull) return; + throw new Exception(T("当前是公钥,常规情况下不允许进行Decrypt或Sign操作,可以调用SwapKey方法来允许进行此操作", "Currently it is a public key. Decrypt or Sign operations are not allowed under normal circumstances. You can call the SwapKey method to allow this operation.")); + } + + + + + + + + /******************底层加密解密调用*******************/ + + /// + /// 加密 + /// + private byte[] __Encrypt(string padding, byte[] data) { + string ctype = RSAPadding_Enc(padding), CType = ctype.ToUpper(); + + int blockLen = KeySize / 8; + if (CType.IndexOf("OAEP") != -1) { + //OAEP填充占用 2*hashLen+2 字节:https://www.rfc-editor.org/rfc/rfc8017.html#section-7.1.1 + int shaLen; string _; + __OaepParam(ctype, out _, out _, out shaLen); + int sub = 2 * shaLen / 8 + 2; + blockLen -= sub; + if (blockLen < 1) { + string min = "NaN"; if (sub > 0) min = (int)Math.Pow(2, Math.Ceiling(Math.Log(sub * 8) / Math.Log(2))) + ""; + throw new Exception("RSA[" + ctype + "][keySize=" + KeySize + "] " + T("密钥位数不能小于", "Key digits cannot be less than ") + min); + } + } else if (CType.IndexOf("NOPADDING") != -1) { + //NOOP 无填充,不够数量时会在开头给0 + } else { + //PKCS1填充占用11字节:https://www.rfc-editor.org/rfc/rfc8017.html#section-7.2.1 + blockLen -= 11; + } + + return __EncDec(true, ctype, data, blockLen); + } + /// + /// 解密 + /// + private byte[] __Decrypt(string padding, byte[] data) { + string ctype = RSAPadding_Enc(padding); + + int blockLen = KeySize / 8; + return __EncDec(false, ctype, data, blockLen); + } + static private Regex OAEP_Exp = new Regex("^RSA/(.+?)/OAEPWITHSHA(3-|-?512/)?[\\-/]?(\\d+)ANDMGF1PADDING$"); + static private void __OaepParam(string ctype, out string outType, out string outHash, out int outLen) { + string CType = ctype.ToUpper(); bool isMd5 = false; + if (CType.IndexOf("MD5") != -1) { + isMd5 = true; CType = CType.Replace("MD5", "SHA-128");//伪装成SHA简化逻辑 + } + Match m = OAEP_Exp.Match(CType); + if (!m.Success) { + throw new Exception(ctype + T("不在预定义列表内,无法识别出Hash算法", " is not in the predefined list, and the Hash algorithm cannot be recognized")); + } + int shaN = Convert.ToInt32(m.Groups[3].Value); + outLen = shaN == 1 ? 160 : shaN;//sha1 为 160位 + outType = "RSA/" + m.Groups[1].Value + "/OAEPPadding"; + + string hash; + if (isMd5) { + hash = "MD5"; + } else { + hash = "SHA-" + shaN; string m2 = m.Groups[2].Value; + if (m2 != null && m2.Length != 0) { + if (m2.IndexOf("512") != -1) { + hash = "SHA-512/" + shaN; + } else { + hash = "SHA3-" + shaN; + } + } + } + outHash = hash; + } + private byte[] __EncDec(bool isEnc, string ctype, byte[] data, int blockLen) { + checkKeyD(isEnc); + string ctype0 = ctype, CType = ctype.ToUpper(); + bool isNO = false, isOaep = false; + + string hash = null; int shaLen; + if (CType.IndexOf("OAEP") != -1) { + isOaep = true; + __OaepParam(ctype, out ctype, out hash, out shaLen); + } else if (CType.IndexOf("NOPADDING") != -1) { + isNO = true; + } + + Func process; + Action destory; + + if (rsaBouncyCastle != null) { + //使用BouncyCastle进行加密解密 +#if RSA_Util_BouncyCastle_CompileCode_1 + ICipherParameters key = Bc_Key(isEnc); + IAsymmetricBlockCipher cipher = new RsaEngine(); + if (isNO) { + //NOOP 无填充,不够数量时会在开头给0 + } else if (isOaep) { + IDigest hashObj = DigestUtilities.GetDigest(hash); + cipher = new OaepEncoding(cipher, hashObj, hashObj, null); + } else { + cipher = new Pkcs1Encoding(cipher); + } + cipher.Init(isEnc, key); + destory = () => { cipher = null; }; + process = (offset, len) => { + return cipher.ProcessBlock(data, offset, len); + }; +#else + object key = Bc_Key(isEnc); + object cipher = rsaBouncyCastle.GetType(BcName_RsaEngine).GetConstructor(new Type[0]).Invoke(new object[0]); + if (isNO) { + //NOOP 无填充,不够数量时会在开头给0 + } else if (isOaep) { + object hashObj = rsaBouncyCastle.GetType("Org.BouncyCastle.Security.DigestUtilities").GetMethod("GetDigest", new Type[] { typeof(string) }).Invoke(null, new object[] { hash }); + cipher = FindCtor(rsaBouncyCastle.GetType("Org.BouncyCastle.Crypto.Encodings.OaepEncoding"), new string[] { "iasym", "idigest", "idigest", "byte" }).Invoke(new object[] { cipher, hashObj, hashObj, null }); + } else { + cipher = FindCtor(rsaBouncyCastle.GetType("Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding"), new string[] { "iasym" }).Invoke(new object[] { cipher }); + } + FindFunc(cipher.GetType(), "Init", new string[] { "bool", "" }).Invoke(cipher, new object[] { isEnc, key }); + destory = () => { cipher = null; }; + var processBlock = FindFunc(cipher.GetType(), "ProcessBlock", new string[] { "byte", "int", "int" }); + process = (offset, len) => { + return (byte[])processBlock.Invoke(cipher, new object[] { data, offset, len }); + }; +#endif + } else if (isNO) { + //.NET不支持NoPadding,手动实现一下 + var n = RSA_PEM.BigX(PEM__.Key_Modulus); + var e = RSA_PEM.BigX(PEM__.Key_Exponent); + if (!isEnc && PEM__.Key_D != null) {//如果未提供私钥,将用公钥解密 + e = RSA_PEM.BigX(PEM__.Key_D); + } + process = (offset, len) => { + if (isEnc) { + byte[] pad0 = new byte[blockLen]; + Array.Copy(data, offset, pad0, pad0.Length - len, len); + var m = RSA_PEM.BigX(pad0); + var c = BigInteger.ModPow(m, e, n); + return RSA_PEM.BigB(c); + } else { + var enc = new byte[len]; + Array.Copy(data, offset, enc, 0, len); + var m = RSA_PEM.BigX(enc); + var c = BigInteger.ModPow(m, e, n); + return RSA_PEM.BigB(c); + } + }; + destory = () => { }; + } else if (IS_CoreOr46) { + //使用高版本RSA进行加密解密,4.6+ 或 Core + if (isNO) throw new Exception(NetNotSupportMsg(ctype0 + T("加密填充模式", " encryption padding mode"))); + string hashName = null; + if (isOaep) { + checkHashSupport(hash); + hashName = hash.Replace("SHA-", "SHA"); + } + +#if RSA_Util_NewNET_CompileCode_1 + RSAEncryptionPadding padding = RSAEncryptionPadding.Pkcs1; + if (isOaep) { + padding = RSAEncryptionPadding.CreateOaep(new HashAlgorithmName(hashName)); + } + RSA rsa = createRSA(); +#else + dynamic padding = Type_RSAEncryptionPadding.GetProperty("Pkcs1").GetValue(null); + if (isOaep) { + padding = FindFunc(Type_RSAEncryptionPadding, "CreateOaep", new string[] { "hashalg" }).Invoke(null, new object[] { Get_HashAlgorithmName(hashName) }); + } + dynamic rsa = createRSA(); +#endif + destory = () => { rsa.Dispose(); rsa = null; }; + process = (offset, len) => { + byte[] bytes = new byte[len]; + Array.Copy(data, offset, bytes, 0, len); + if (isEnc) return rsa.Encrypt(bytes, padding); + return rsa.Decrypt(bytes, padding); + }; + } else { + //使用低版本RSA进行加密解密,4.6以下版本 + if (isNO) throw new Exception(NetNotSupportMsg(ctype0 + T("加密填充模式", " encryption padding mode"))); + if (isOaep && hash != "SHA-1") throw new Exception(NetLowVerSupportMsg(ctype0 + T("加密填充模式(只支持SHA-1)", " encryption padding mode (only SHA-1 is supported)"))); + + RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)createRSA(); + destory = () => { rsa.Dispose(); rsa = null; }; + process = (offset, len) => { + byte[] bytes = new byte[len]; + Array.Copy(data, offset, bytes, 0, len); + if (isEnc) return rsa.Encrypt(bytes, isOaep); + return rsa.Decrypt(bytes, isOaep); + }; + } + + //数据分段进行加密解密 + using (var stream = new MemoryStream()) { + int start = 0; + while (start < data.Length) { + int len = blockLen; + if (start + len > data.Length) { + len = data.Length - start; + } + + byte[] val = process(start, len); + if (!isEnc && isNO) { + //没有填充时,去掉开头的0 + int idx = 0; + for (; idx < val.Length; idx++) { + if (val[idx] != 0) break; + } + byte[] val2 = new byte[val.Length - idx]; + Array.Copy(val, idx, val2, 0, val2.Length); + val = val2; + } + stream.Write(val, 0, val.Length); + start += len; + } + destory(); + return stream.ToArray(); + } + } + + + + /******************底层签名验证调用*******************/ + + private byte[] __Sign(string hash, byte[] data) { + byte[] val; bool _; + __SignVerify(true, hash, data, null, out val, out _); + return val; + } + private bool __Verify(string hash, byte[] sign, byte[] data) { + byte[] _; bool val; + __SignVerify(false, hash, data, sign, out _, out val); + return val; + } + static private Regex HS_Exp = new Regex("^SHA(3-|-?512/)?[\\-/]?(\\d+)WITHRSA$"); + private void __SignVerify(bool isSign, string hashType, byte[] data, byte[] signData, out byte[] signVal, out bool verifyVal) { + checkKeyD(!isSign); + string stype = RSAPadding_Sign(hashType), SType = stype.ToUpper(); + + bool isPss = SType.EndsWith("/PSS"); + if (isPss) { + SType = SType.Substring(0, stype.Length - 4); + } + bool isMd5 = SType.IndexOf("MD5") != -1; + if (isMd5) { + SType = SType.Replace("MD5", "SHA-128");//伪装成SHA简化逻辑 + } + + Match m = HS_Exp.Match(SType); + if (!m.Success) { + throw new Exception(stype + T("不在预定义列表内,无法识别出Hash算法", " is not in the predefined list, and the Hash algorithm cannot be recognized")); + } + int shaN = Convert.ToInt32(m.Groups[2].Value); + int shaLen = shaN == 1 ? 160 : shaN;//sha1 为 160位 + + string hash; + if (isMd5) { + hash = "MD5"; + } else { + hash = "SHA-" + shaN; string m2 = m.Groups[1].Value; + if (m2 != null && m2.Length != 0) { + if (m2.IndexOf("512") != -1) { + hash = "SHA-512/" + shaN; + } else { + hash = "SHA3-" + shaN; + } + } + } + + if (rsaBouncyCastle != null) { + //使用BouncyCastle进行签名验证 +#if RSA_Util_BouncyCastle_CompileCode_1 + ICipherParameters key = Bc_Key(!isSign); + IDigest hashObj = DigestUtilities.GetDigest(hash); + ISigner signer; + if (isPss) { + signer = new PssSigner(new RsaEngine(), hashObj, hashObj, shaLen / 8, 0xBC); + } else { + signer = new RsaDigestSigner(hashObj); + } + signer.Init(isSign, key); + signer.BlockUpdate(data, 0, data.Length); + if (isSign) { + signVal = signer.GenerateSignature(); + verifyVal = false; + } else { + signVal = null; + verifyVal = signer.VerifySignature(signData); + } +#else + object key = Bc_Key(!isSign); + object hashObj = rsaBouncyCastle.GetType("Org.BouncyCastle.Security.DigestUtilities").GetMethod("GetDigest", new Type[] { typeof(string) }).Invoke(null, new object[] { hash }); + object signer; + if (isPss) { + object cipher = rsaBouncyCastle.GetType(BcName_RsaEngine).GetConstructor(new Type[0]).Invoke(new object[0]); + signer = FindCtor(rsaBouncyCastle.GetType("Org.BouncyCastle.Crypto.Signers.PssSigner"), new string[] { "iasym", "idigest", "idigest", "int", "byte" }).Invoke(new object[] { cipher, hashObj, hashObj, shaLen / 8, (byte)0xBC }); + } else { + signer = FindCtor(rsaBouncyCastle.GetType("Org.BouncyCastle.Crypto.Signers.RsaDigestSigner"), new string[] { "idigest" }).Invoke(new object[] { hashObj }); + } + FindFunc(signer.GetType(), "Init", new string[] { "bool", "" }).Invoke(signer, new object[] { isSign, key }); + FindFunc(signer.GetType(), "BlockUpdate", new string[] { "byte", "int", "int" }).Invoke(signer, new object[] { data, 0, data.Length }); + if (isSign) { + signVal = (byte[])FindFunc(signer.GetType(), "GenerateSignature", new string[0]).Invoke(signer, new object[0]); + verifyVal = false; + } else { + signVal = null; + verifyVal = (bool)FindFunc(signer.GetType(), "VerifySignature", new string[] { "byte" }).Invoke(signer, new object[] { signData }); + } +#endif + return; + } + + var hashName = hash.Replace("SHA-", "SHA"); + if (IS_CoreOr46) { + //使用高版本RSA进行加密解密,4.6+ 或 Core + checkHashSupport(hash); + +#if RSA_Util_NewNET_CompileCode_1 + RSA rsa = createRSA(); + var hashObj = new HashAlgorithmName(hashName); + var padding = RSASignaturePadding.Pkcs1; + if (isPss) { + padding = RSASignaturePadding.Pss; + } +#else + Type SP = typeof(RSA).Assembly.GetType(Space_Cryptography + "RSASignaturePadding"); + dynamic rsa = createRSA(); + dynamic hashObj = Get_HashAlgorithmName(hashName); + dynamic padding = SP.GetProperty("Pkcs1").GetValue(null); + if (isPss) { + padding = SP.GetProperty("Pss").GetValue(null); + } +#endif + if (isSign) { + signVal = rsa.SignData(data, hashObj, padding); + verifyVal = false; + } else { + signVal = null; + verifyVal = rsa.VerifyData(data, signData, hashObj, padding); + } + rsa.Dispose(); + return; + } else { + //使用低版本RSA进行加密解密,4.6以下版本 + if (isPss) throw new Exception(NetLowVerSupportMsg(T("所有PSS签名填充模式", "All PSS signature padding modes"))); + checkHashSupport(hash); + + RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)createRSA(); + if (isSign) { + signVal = rsa.SignData(data, hashName); + verifyVal = false; + } else { + signVal = null; + verifyVal = rsa.VerifyData(data, hashName, signData); + } + rsa.Dispose(); + return; + } + } + + + + + /// + /// 反射查找出参数匹配的方法,方法名字为".ctor"时查找构造方法;参数名字为空匹配任意参数,小写前缀匹配 + /// + static public MethodBase FindFunc(Type type, string func, string[] paramNames) { + MethodBase[] arr; bool isCtor = false; + if (func == ".ctor") { + arr = type.GetConstructors(); isCtor = true; + } else { + arr = type.GetMethods(); + } + foreach (var m in arr) { + if (!isCtor && m.Name != func) continue; + var ps = m.GetParameters(); MethodBase find = null; + if (ps.Length == paramNames.Length) { + find = m; + for (int i = 0; i < ps.Length; i++) { + var n = paramNames[i]; + if (n.Length > 0 && !ps[i].ParameterType.Name.ToLower().StartsWith(n)) { + find = null; break; + } + } + } + if (find != null) return find; + } + throw new Exception(T(type.FullName + "中未找到方法", "Method not found in " + type.FullName + ": ") + func + "(" + string.Join(",", paramNames) + ")"); + } + static public ConstructorInfo FindCtor(Type type, string[] paramNames) { + return (ConstructorInfo)FindFunc(type, ".ctor", paramNames); + } + + + + + /****************平台差异兼容处理****************/ + + /// + /// 使用BouncyCastle的RSA实现进行加密,提供BouncyCastle的程序集 + /// + static private Assembly rsaBouncyCastle; + /// + /// 是否强制使用BouncyCastle加密增强库进行RSA操作,为true时将不会使用.NET的RSA + /// + static public bool IsUseBouncyCastle { + get { return rsaBouncyCastle != null; } + } + /// + /// 强制使用BouncyCastle加密增强库进行RSA操作。只需在程序启动后调用一次即可,直接调用一下BouncyCastle里面的类,传入程序集:UseBouncyCastle(typeof(RsaEngine).Assembly),传入null取消使用 + /// + static public void UseBouncyCastle(Assembly bouncyCastleAssembly) { + if (bouncyCastleAssembly != null && bouncyCastleAssembly.GetType(BcName_RsaEngine) == null) { + throw new Exception(T("UseBouncyCastle方法必须传入BouncyCastle的Assembly", "The UseBouncyCastle method must pass in the Assembly of BouncyCastle")); + } + rsaBouncyCastle = bouncyCastleAssembly; + } + static private readonly string BcName_RsaEngine = "Org.BouncyCastle.Crypto.Engines.RsaEngine"; + private dynamic Bc_Key(bool usePub) { + var k = PEM__; + Func BigX = (bytes) => { + byte[] val = new byte[bytes.Length + 1]; + Array.Copy(bytes, 0, val, 1, bytes.Length); + return val; + }; +#if RSA_Util_BouncyCastle_CompileCode_1 + BcInt[] ks = new BcInt[8]; + ks[0] = new BcInt(BigX(k.Key_Modulus)); + ks[1] = new BcInt(BigX(k.Key_Exponent)); + checkKeyD(usePub); + if (usePub || k.Key_D == null) { + return new RsaKeyParameters(!usePub, ks[0], ks[1]); + } + ks[2] = new BcInt(BigX(k.Key_D)); + ks[3] = new BcInt(BigX(k.Val_P)); + ks[4] = new BcInt(BigX(k.Val_Q)); + ks[5] = new BcInt(BigX(k.Val_DP)); + ks[6] = new BcInt(BigX(k.Val_DQ)); + ks[7] = new BcInt(BigX(k.Val_InverseQ)); + return new RsaPrivateCrtKeyParameters(ks[0], ks[1], ks[2], ks[3], ks[4], ks[5], ks[6], ks[7]); +#else + var BInt = rsaBouncyCastle.GetType("Org.BouncyCastle.Math.BigInteger").GetConstructor(new Type[] { typeof(byte[]) }); + object[] ks = new object[8]; + ks[0] = BInt.Invoke(new object[] { BigX(k.Key_Modulus) }); + ks[1] = BInt.Invoke(new object[] { BigX(k.Key_Exponent) }); + checkKeyD(usePub); + if (usePub || k.Key_D == null) {//如果未提供私钥,将用公钥解密、签名 + return FindCtor(rsaBouncyCastle.GetType("Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters"), new string[] { "bool", "big", "big" }).Invoke(new object[] { !usePub, ks[0], ks[1] }); + } + ks[2] = BInt.Invoke(new object[] { BigX(k.Key_D) }); + ks[3] = BInt.Invoke(new object[] { BigX(k.Val_P) }); + ks[4] = BInt.Invoke(new object[] { BigX(k.Val_Q) }); + ks[5] = BInt.Invoke(new object[] { BigX(k.Val_DP) }); + ks[6] = BInt.Invoke(new object[] { BigX(k.Val_DQ) }); + ks[7] = BInt.Invoke(new object[] { BigX(k.Val_InverseQ) }); + return FindCtor(rsaBouncyCastle.GetType("Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters"), new string[] { "big", "big", "big", "big", "big", "big", "big", "big" }).Invoke(ks); +#endif + } + + + /// + /// 当前运行环境是否为.NET Core,false为.NET Framework + /// + static public bool IS_CORE { + get { + if (is__core == null) { + is__core = new bool[] { !typeof(RSA).Assembly.ToString().ToLower().Contains("mscorlib") }; + } + return is__core[0]; + } + } + static private bool[] is__core; + /// + /// 当前运行环境是否是.NET Framework 4.6以上或.NET Core + /// + static public bool IS_CoreOr46 { + get { + if (IS_CORE) return true; + if (is__core_or_46 == null) { + Type type = Type_RSAEncryptionPadding; + is__core_or_46 = new int[] { type != null ? 1 : -1 }; + } + return is__core_or_46[0] > 0; + } + } + static private int[] is__core_or_46; + /// + /// .NET Framework 下测试,可以指定以高版本运行还是低版本运行,方便测试,取值:0重设为默认,1高版本,-1低版本 + /// + static public void IS_CoreOr46_Test_Set(int val) { + is__core_or_46 = val == 0 ? null : new int[] { val }; + } + + + + //.NET Framework 低版本兼容,4.6以上或Core才有的类 + /// + /// 4.6以上使用RSACng,RSACng支持的部分填充方式如果换成RSACryptoServiceProvider会抛不支持的异常 + /// + static private RSA GetRSA_WindowsCng(RSA_PEM pem) { + //.NET Core里面没有RSACng,兼容编译 + //统一反射进行获取,Framework全在System.Core.dll里面 + var type = typeof(ECDsa).Assembly.GetType(Space_Cryptography + "RSACng"); + if (type == null) return null; //Core + var rsa = (RSA)type.GetConstructor(new Type[0]).Invoke(new object[0]); + pem.GetRSA__ImportParameters(rsa); + return rsa; + } + static private readonly string Space_Cryptography = "System.Security.Cryptography."; + static private Type Type_RSAEncryptionPadding { + get { + return typeof(RSA).Assembly.GetType(Space_Cryptography + "RSAEncryptionPadding"); + } + } + static private dynamic Get_HashAlgorithmName(string hash) { + return typeof(RSA).Assembly.GetType(Space_Cryptography + "HashAlgorithmName").GetConstructor(new Type[] { typeof(string) }).Invoke(new object[] { hash }); + } + + + } +} diff --git a/Test-Build-Run.bat b/Test-Build-Run.bat new file mode 100644 index 0000000..90857ce --- /dev/null +++ b/Test-Build-Run.bat @@ -0,0 +1,189 @@ +@echo off +::[zh_CN] ��Windowsϵͳ��˫����������ű��ļ����Զ����.cs�ļ���������С����Ȱ�װ��.NET Framework 4.5+������.NET Core SDK��֧��.NET Core 2.0�����ϰ汾��.NET 5+�� +::�������BouncyCastle������ǿ�⣨BouncyCastle.xxxx.dll������ֱ�Ӹ��ƶ�Ӧ�汾��dll�ļ�����Դ���Ŀ¼���������к󼴿ɻ��ȫ������ǩ��ģʽ֧�� + +::[en_US] Double-click to run this script file in the Windows system, and automatically complete the compilation and operation of the .cs file. Need to install .NET Framework 4.5+ or .NET Core SDK (support .NET Core 2.0 and above, .NET 5+) +::If you have BouncyCastle encryption enhancement library (BouncyCastle.xxxx.dll), please directly copy the corresponding version of the dll file to the root directory of this source code. After compiling and running, you can get all encryption signature mode support + + +cls +::chcp 437 +set isZh=0 +ver | find "�汾%qjkTTT%" > nul && set isZh=1 +goto Run +:echo2 + if "%isZh%"=="1" echo %~1 + if "%isZh%"=="0" echo %~2 + goto:eof + +:Run +cd /d %~dp0 +call:echo2 "��ʾ���ԣ��������� %cd%" "Language: English %cd%" +echo. +call:echo2 "ѡ���������ģʽ���������ţ� " "Select the compilation and running mode, please enter the number:" +call:echo2 " 1. ʹ��.NET Framework���б��루֧��.NET Framework 4.5�����ϰ汾�� " " 1. Use .NET Framework to compile (support .NET Framework 4.5 and above)" +call:echo2 " 2. ʹ��.NET Core���б��루֧��.NET Core 2.0�����ϰ汾��.NET 5+�� " " 2. Use .NET Core to compile (support .NET Core 2.0 and above, .NET 5+)" +call:echo2 " 3. �˳� " " 3. Exit" + +set step=&set /p step=^> + if "%step%"=="1" goto RunFramework + if "%step%"=="2" goto RunDotnet + if "%step%"=="3" goto End + call:echo2 "�����Ч�����������룡 " "The number is invalid, please re-enter!" + goto Run + + +:findDLL + set dllName=%~1 + set dllPath=target\%~1 + if not exist %dllPath% set dllPath= + if "%dllPath%"=="" goto dllPath_End + call:echo2 "��⵽�����ɵ�dll��%dllPath%���Ƿ�ʹ�ô�dll������ԣ�(Y/N) N " "Generated dll detected: %dllPath%, do you want to use this dll to participate in the test? (Y/N) N" + set step=&set /p step=^> + if /i not "%step%"=="Y" set dllPath= + if not "%dllPath%"=="" ( + call:echo2 "dll������ԣ�%dllPath%" "dll participates in the test: %dllPath%" + echo. + ) + :dllPath_End + goto:eof + + +:RunDotnet +call:findDLL "RSA-CSharp.NET-Standard.dll" + +::.NET CLI telemetry https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry +set DOTNET_CLI_TELEMETRY_OPTOUT=1 + +call:echo2 "���ڶ�ȡ.NET Core�汾�� " "Reading .NET Core Version:" + +dotnet --version +if errorlevel 1 ( + echo. + call:echo2 "��Ҫ��װ.NET Core SDK [֧��.NET Core 2.0�����ϰ汾��.NET 5+] ����ʹ��.NET Coreģʽ��������.cs�ļ������߳���ѡ��.NET Frameworkģʽ���б��롣���Ե� https://dotnet.microsoft.com/zh-cn/download/dotnet ���ذ�װ.NET Core SDK " "You need to install .NET Core SDK [support .NET Core 2.0 and above, .NET 5+] to compile and run .cs files using .NET Core mode, or try to select .NET Framework mode for compilation. You can go to https://dotnet.microsoft.com/en-us/download/dotnet to download and install the .NET Core SDK" + goto Pause +) + + +set rootDir=rsaTest +echo. +call:echo2 "���ڴ�.NET Core��Ŀ%rootDir%..." "Creating .NET Core project %rootDir%..." +if not exist %rootDir% ( + md %rootDir% +) else ( + del %rootDir%\* /Q > nul +) + +cd %rootDir% +dotnet new console +if errorlevel 1 goto if_dncE +if not exist %rootDir%*proj goto if_dncE + goto dncE_if + :if_dncE + echo. + call:echo2 "������Ŀ����ִ��ʧ�� " "The command to create a project failed to execute" + goto Pause + :dncE_if +echo. + +setlocal enabledelayedexpansion +for /f "delims=" %%f in ('dir /b %rootDir%*proj') do ( + for /f "delims=" %%v in (%%f) do ( + set a=%%v + set "a=!a:= $(DefineConstants);RSA_BUILD__NET_CORE!" + set "a=!a:Nullable>enable=Nullable>disable!" + if not "%dllPath%"=="" ( + set "a=!a:=%dllName%True!" + ) + echo !a!>>tmp.txt + ) + move tmp.txt %%f > nul + call:echo2 "���޸�proj��Ŀ�����ļ���%%f��������RSA_BUILD__NET_CORE����������� " "Modified proj project configuration file: %%f, enabled RSA_BUILD__NET_CORE conditional compilation symbol" + echo. +) + +cd .. +if "%dllPath%"=="" ( + xcopy *.cs %rootDir% /Y > nul +) else ( + xcopy Program.cs %rootDir% /Y > nul + xcopy %dllPath% %rootDir% /Y > nul +) +if exist *.dll ( + xcopy *.dll %rootDir% /Y > nul +) +cd %rootDir% + + + +echo. +call:echo2 "���ڱ���.NET Core��Ŀ%rootDir%..." "Compiling .NET Core project %rootDir%..." +echo. +dotnet run -cmd=1 -zh=%isZh% +goto Pause + + + + + + +:RunFramework +call:findDLL "RSA-CSharp.NET-Framework.dll" +if not exist target\RSA-CSharp.NET-Framework.dll ( + call:findDLL "RSA-CSharp.NET-Standard.dll" +) + +cd /d C:\Windows\Microsoft.NET\Framework\v4.* +set FwDir=%cd%\ +::set FwDir=C:\Windows\Microsoft.NET\Framework\xxxx\ +echo .NET Framework Path: %FwDir% + +call:echo2 "���ڶ�ȡ.NET Framework�汾�� " "Reading .NET Framework Version:" +%FwDir%MSBuild /ver +if errorlevel 1 ( + echo. + call:echo2 "��Ҫ��װ.NET Framework 4.5�����ϰ汾����ʹ��.NET Frameworkģʽ��������.cs�ļ������߳���ѡ��.NET Coreģʽ���б��롣���Ե� https://dotnet.microsoft.com/zh-cn/download/dotnet-framework ���ذ�װ.NET Framework " "You need to install .NET Framework 4.5 or above to compile and run .cs files using .NET Framework mode, or try to select .NET Core mode for compilation. You can go to https://dotnet.microsoft.com/en-us/download/dotnet-framework to download and install .NET Framework" + goto Pause +) +cd /d %~dp0 + + +set rootDir=rsaTestFw +echo. +call:echo2 "���ڴ���.NET Framework��Ŀ%rootDir%..." "Creating .NET Framework project %rootDir%..." +if not exist %rootDir% ( + md %rootDir% +) else ( + del %rootDir%\* /Q > nul +) + +if "%dllPath%"=="" ( + xcopy *.cs %rootDir% /Y > nul +) else ( + xcopy Program.cs %rootDir% /Y > nul + xcopy %dllPath% %rootDir% /Y > nul +) +if exist *.dll ( + xcopy *.dll %rootDir% /Y > nul +) +cd %rootDir% + +echo. +call:echo2 "���ڱ���.NET Framework��Ŀ%rootDir%..." "Compiling .NET Framework project %rootDir%..." +echo. +::https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/ +set rd= +if not "%dllPath%"=="" set rd=/r:"%dllName%" +%FwDir%csc /t:exe /r:"%FwDir%System.Numerics.dll" %rd% /out:%rootDir%.exe *.cs +if errorlevel 1 ( + echo. + call:echo2 "��Ŀ%rootDir%����ʧ�� " "Compilation failed for project %rootDir%" + goto Pause +) + +%rootDir%.exe -cmd=1 -zh=%isZh% + +:Pause +pause +goto Run +:End \ No newline at end of file diff --git a/Test-Build-Run.sh b/Test-Build-Run.sh new file mode 100644 index 0000000..42f5c3d --- /dev/null +++ b/Test-Build-Run.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +#[zh_CN] 在Linux、macOS系统终端中运行这个脚本文件,自动完成.cs文件编译和运行。需先安装了.NET Core SDK,支持.NET Core 2.0及以上版本(.NET 5+) +#如果你有BouncyCastle加密增强库(BouncyCastle.xxxx.dll),请直接复制对应版本的dll文件到源码根目录,编译运行后即可获得全部加密签名模式支持 + +#[en_US] Run this script file in the Linux and macOS system terminals to automatically complete the compilation and operation of the .cs file. .NET Core SDK needs to be installed first, support .NET Core 2.0 and above (.NET 5+) +#If you have BouncyCastle encryption enhancement library (BouncyCastle.xxxx.dll), please directly copy the corresponding version of the dll file to the root directory of this source code. After compiling and running, you can get all encryption signature mode support + + +clear + +isZh=0 +if [ $(echo ${LANG/_/-} | grep -Ei "\\b(zh|cn)\\b") ]; then isZh=1; fi + +function echo2(){ + if [ $isZh == 1 ]; then echo $1; + else echo $2; fi +} +cd `dirname $0` +echo2 "显示语言:简体中文 `pwd`" "Language: English `pwd`" +function err(){ + if [ $isZh == 1 ]; then echo -e "\e[31m$1\e[0m"; + else echo -e "\e[31m$2\e[0m"; fi +} +function exit2(){ + if [ $isZh == 1 ]; then read -n1 -rp "请按任意键退出..." key; + else read -n1 -rp "Press any key to exit..."; fi + exit +} + + +dllName="RSA-CSharp.NET-Standard.dll" +dllPath="target/$dllName" +if [ ! -e $dllPath ]; then dllPath=""; fi +if [ "$dllPath" != "" ]; then + echo2 "检测到已生成的dll:${dllPath},是否使用此dll参与测试?(Y/N) N" "Generated dll detected: ${dllPath}, do you want to use this dll to participate in the test? (Y/N) N" + read -rp "> " step + if [ "${step^^}" != "Y" ]; then dllPath=""; fi + if [ "$dllPath" != "" ]; then + echo2 "dll参与测试:$dllPath" "dll participates in the test: $dllPath" + echo + fi +fi + + +#.NET CLI telemetry https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry +DOTNET_CLI_TELEMETRY_OPTOUT=1 + +echo2 "正在读取.NET Core版本:" "Reading .NET Core Version:" + +dotnet --version +[ ! $? -eq 0 ] && { + echo + err "需要安装.NET Core SDK [支持.NET Core 2.0及以上版本,.NET 5+] 才能使用本脚本编译运行.cs文件,可以到 https://learn.microsoft.com/zh-cn/dotnet/core/install/ 下载安装SDK"\ + "You need to install .NET Core SDK [support .NET Core 2.0 and above, .NET 5+] to use this script to compile and run the .cs file. You can download and install the SDK at https://learn.microsoft.com/en-us/dotnet/core/install/"; + exit2; +} + + +rootDir=rsaTest +echo +echo2 "正在创.NET Core项目${rootDir}..." "Creating .NET Core project ${rootDir}..." +if [ ! -e $rootDir ]; then + mkdir -p $rootDir +else + rm ${rootDir}/* > /dev/null 2>&1 +fi + +cd $rootDir +dotnet new console +[ ! $? -eq 0 -o ! -e $rootDir*proj ] && { + echo + err "创建项目命令执行失败" "The command to create a project failed to execute" + exit2; +} +echo + +projFile=`ls $rootDir*proj`; +sed -i -e 's// \$(DefineConstants);RSA_BUILD__NET_CORE<\/DefineConstants>/g' $projFile; +sed -i -e 's/Nullable>enable/Nullable>disable/g' $projFile +if [ "$dllPath" != "" ]; then + sed -i -e 's/<\/Project>/'"${dllName}"'<\/HintPath>True<\/Private><\/Reference><\/ItemGroup><\/Project>/g' $projFile +fi +echo2 "已修改proj项目配置文件:${projFile},已启用RSA_BUILD__NET_CORE条件编译符号" "Modified proj project configuration file: ${projFile}, enabled RSA_BUILD__NET_CORE conditional compilation symbol" +echo + +cd .. +if [ "$dllPath" == "" ]; then + cp *.cs $rootDir > /dev/null +else + cp Program.cs $rootDir > /dev/null + cp $dllPath $rootDir > /dev/null +fi +if [ -e *.dll ]; then + cp *.dll $rootDir > /dev/null +fi +cd $rootDir + + + +echo2 "正在编译.NET Core项目${rootDir}..." "Compiling .NET Core project ${rootDir}..." +echo +dotnet run -cmd=1 -zh=${isZh} + +exit2; diff --git a/images/1-en.png b/images/1-en.png new file mode 100644 index 0000000..f3565fc Binary files /dev/null and b/images/1-en.png differ diff --git a/images/1.png b/images/1.png index dc7c468..aa54014 100644 Binary files a/images/1.png and b/images/1.png differ diff --git a/images/2.png b/images/2.png deleted file mode 100644 index bd16a20..0000000 Binary files a/images/2.png and /dev/null differ diff --git a/scripts/Create-dll.bat b/scripts/Create-dll.bat new file mode 100644 index 0000000..9f24b26 --- /dev/null +++ b/scripts/Create-dll.bat @@ -0,0 +1,254 @@ +@echo off +::[zh_CN] ��Windowsϵͳ��˫����������ű��ļ����Զ����.cs�ļ���������ͨ�õ�dll��exe�����Ȱ�װ��.NET Framework 4.5+����.NET Core SDK��֧��.NET Core 2.0�����ϰ汾��.NET 5+�� +::[en_US] Double-click to run this script file in the Windows system, and automatically complete the compilation of the .cs file to generate common dll and exe. Need to install .NET Framework 4.5+, and .NET Core SDK (support .NET Core 2.0 and above, .NET 5+) + + +::.NET Core --framework: https://learn.microsoft.com/en-us/dotnet/standard/frameworks +set CoreTarget=netstandard2.0 +::TargetFrameworkVersion +set FrameworkTarget=v4.5 + + +cls +::chcp 437 +set isZh=0 +ver | find "�汾%qjkTTT%" > nul && set isZh=1 +goto Run +:echo2 + if "%isZh%"=="1" echo %~1 + if "%isZh%"=="0" echo %~2 + goto:eof + +:Run +cd /d %~dp0 +cd ..\ +call:echo2 "��ʾ���ԣ��������� %cd%" "Language: English %cd%" +echo. +call:echo2 "�������ţ� " "Please enter the number:" +call:echo2 " 1. ��������dll�İ汾�ţ���ǰ��%dllVer%�� " " 1. Configure the version number of the generated dll (currently: %dllVer%)" +call:echo2 " 2. ʹ��.NET Core���б�������.NET Standard 2.0��dll��֧��.NET Core 2.0�����ϰ汾��.NET 5+��������.NET Framework 4.6.2�����ϰ汾 " " 2. Use .NET Core to compile and generate .NET Standard 2.0 dll (support .NET Core 2.0 and above, .NET 5+), compatible with .NET Framework 4.6.2 and above" +call:echo2 " 3. ʹ��.NET Framework 4.5���б�������Framework���õ�dll " " 3. Use .NET Framework 4.5 to compile and generate dll available to Framework" +call:echo2 " 4. ʹ��.NET Framework 4.5���б�������exe " " 4. Use .NET Framework 4.5 to compile and generate exe" +call:echo2 " 5. �˳� " " 5. Exit" + +set step=&set /p step=^> + if "%step%"=="1" goto SetVer + if "%step%"=="5" goto End + + if "%dllVer%"=="" ( + call:echo2 "�������ð汾�ţ� " "Please configure the version number first!" + goto Run + ) + if "%step%"=="2" goto RunDotnet + + set FrameworkType=dll + if "%step%"=="3" goto RunFramework + if "%step%"=="4" ( + set FrameworkType=exe + goto RunFramework + ) + + call:echo2 "�����Ч�����������룡 " "The number is invalid, please re-enter!" + goto Run + +:SetVer + call:echo2 "����������dll�İ汾�ţ� " "Please enter the version number of the generated dll:" + set dllVer=&set /p dllVer=^> + goto Run + +:RunDotnet +::.NET CLI telemetry https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry +set DOTNET_CLI_TELEMETRY_OPTOUT=1 + +set projName=RSA-CSharp.NET-Standard +set rootDir=target\%projName% +echo. +call:echo2 "���ڴ�.NET Core��Ŀ%rootDir%..." "Creating .NET Core project %rootDir%..." +if not exist %rootDir% ( + md %rootDir% +) else ( + del %rootDir%\* /Q > nul +) + +cd %rootDir% +dotnet new classlib -f %CoreTarget% +if errorlevel 1 goto if_dncE +if not exist %projName%*proj goto if_dncE + goto dncE_if + :if_dncE + echo. + call:echo2 "������Ŀ����ִ��ʧ�ܣ������Ƿ�װ��.NET Core 2.0�����ϰ汾��SDK��.NET 5+�� " "The execution of the command to create a project failed. Please check whether the SDK of .NET Core 2.0 and above (.NET 5+) is installed" + goto Pause + :dncE_if +echo. + + +set prop="%dllVer% %date:~,10%, MIT, Copyright %date:~,4% xiangyuecn xiangyuecn %CoreTarget%, https://github.com/xiangyuecn/RSA-csharp RSA-csharp, %CoreTarget%, %date:~,10%" + +setlocal enabledelayedexpansion +for /f "delims=" %%f in ('dir /b %projName%*proj') do ( + for /f "delims=" %%v in (%%f) do ( + set a=%%v + set "a=!a:= $(DefineConstants);RSA_BUILD__NET_CORE %prop:~1,-1%!" + set "a=!a:Nullable>enable=Nullable>disable!" + echo !a!>>tmp.txt + ) + move tmp.txt %%f > nul + call:echo2 "���޸�proj��Ŀ�����ļ���%%f��������RSA_BUILD__NET_CORE����������� " "Modified proj project configuration file: %%f, enabled RSA_BUILD__NET_CORE conditional compilation symbol" + echo. +) + +del *.cs /Q > nul +xcopy ..\..\RSA_PEM.cs /Y > nul +xcopy ..\..\RSA_Util.cs /Y > nul + + + +echo. +call:echo2 "���ڱ���.NET Core��Ŀ%rootDir%..." "Compiling .NET Core project %rootDir%..." +echo. +dotnet build -c Release +if errorlevel 1 ( + echo. + call:echo2 "����dllʧ�� " "Failed to generate dll" + goto Pause +) + +cd ..\.. +set dllRaw=%rootDir%\bin\Release\%CoreTarget%\%projName%.dll +set dllPath=target\%projName%.dll +del %dllPath% /Q > nul 2>&1 +if exist %dllPath% ( + echo. + call:echo2 "�޷�ɾ�����ļ���%dllPath% " "Unable to delete old file: %dllPath%" + goto Pause +) +xcopy %dllRaw% target /Y + +if not exist %dllPath% ( + echo. + call:echo2 "δ��λ�����ɵ�dll�ļ�·�����뵽%rootDir%\binѰ�����ɵ�dll�ļ� " "The generated dll file path is not located, please go to %rootDir%\bin to find the generated dll file" + goto Pause +) +echo. +call:echo2 "������dll���ļ���Դ���Ŀ¼��%dllPath%�� " "The dll has been generated, and the file is in the root directory of the source code: %dllPath%." +echo. +goto Pause + + + +:RunFramework +cd /d C:\Windows\Microsoft.NET\Framework\v4.* +set FwDir=%cd%\ +::set FwDir=C:\Windows\Microsoft.NET\Framework\xxxx\ +echo .NET Framework Path: %FwDir% + +call:echo2 "���ڶ�ȡ.NET Framework�汾�� " "Reading .NET Framework Version:" +%FwDir%MSBuild /ver +if errorlevel 1 ( + echo. + call:echo2 "��Ҫ��װ.NET Framework 4.5�����ϰ汾����ʹ��.NET Frameworkģʽ��������.cs�ļ������߳���ѡ��.NET Coreģʽ���б��롣���Ե� https://dotnet.microsoft.com/zh-cn/download/dotnet-framework ���ذ�װ.NET Framework " "You need to install .NET Framework 4.5 or above to compile and run .cs files using .NET Framework mode, or try to select .NET Core mode for compilation. You can go to https://dotnet.microsoft.com/en-us/download/dotnet-framework to download and install .NET Framework" + goto Pause +) +cd /d %~dp0 +cd ..\ + + +if "%FrameworkType%"=="exe" ( + set projName=RSA-CSharp.NET-Framework-v%dllVer%-Test +) else ( + set projName=RSA-CSharp.NET-Framework +) +set rootDir=target\%projName% +echo. +call:echo2 "���ڴ���.NET Framework��Ŀ%rootDir%..." "Creating .NET Framework project %rootDir%..." +if not exist %rootDir% ( + md %rootDir% +) else ( + del %rootDir%\* /Q > nul +) +cd %rootDir% + +xcopy ..\..\RSA_PEM.cs /Y > nul +xcopy ..\..\RSA_Util.cs /Y > nul +if "%FrameworkType%"=="exe" xcopy ..\..\Program.cs /Y > nul +call:createFrameworkProj + +echo. +call:echo2 "���ڱ���.NET Framework��Ŀ%rootDir%..." "Compiling .NET Framework project %rootDir%..." +echo. +::https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference +%FwDir%MSBuild build.proj -property:Configuration=Release +if errorlevel 1 ( + echo. + call:echo2 "��Ŀ%rootDir%����ʧ�� " "Compilation failed for project %rootDir%" + goto Pause +) + +cd ..\.. +set fileExt=%FrameworkType% +set dllRaw=%rootDir%\bin\Release\%projName%.%fileExt% +set dllPath=target\%projName%.%fileExt% +del %dllPath% /Q > nul 2>&1 +if exist %dllPath% ( + echo. + call:echo2 "�޷�ɾ�����ļ���%dllPath% " "Unable to delete old file: %dllPath%" + goto Pause +) +xcopy %dllRaw% target /Y + +if not exist %dllPath% ( + echo. + call:echo2 "δ��λ�����ɵ�%fileExt%�ļ�·�����뵽%rootDir%\binѰ�����ɵ�%fileExt%�ļ� " "The generated %fileExt% file path is not located, please go to %rootDir%\bin to find the generated %fileExt% file" + goto Pause +) +echo. +call:echo2 "������%fileExt%���ļ���Դ���Ŀ¼��%dllPath%�� " "The %fileExt% has been generated, and the file is in the root directory of the source code: %dllPath%." +echo. +goto Pause + + + + +:createFrameworkProj + echo using System.Reflection;>AssemblyInfo.cs + echo using System.Runtime.CompilerServices;>>AssemblyInfo.cs + echo using System.Runtime.InteropServices;>>AssemblyInfo.cs + echo [assembly: AssemblyTitle("%projName%")]>>AssemblyInfo.cs + echo [assembly: AssemblyCopyright("%date:~,10%, MIT, Copyright %date:~,4% xiangyuecn")]>>AssemblyInfo.cs + echo [assembly: AssemblyCompany("xiangyuecn")]>>AssemblyInfo.cs + echo [assembly: AssemblyProduct(".NET Framework %FrameworkTarget%, https://github.com/xiangyuecn/RSA-csharp")]>>AssemblyInfo.cs + echo [assembly: AssemblyDescription("RSA-csharp, .NET Framework %FrameworkTarget%, %date:~,10%")]>>AssemblyInfo.cs + echo [assembly: AssemblyVersion("%dllVer%.0.0")]>>AssemblyInfo.cs + echo [assembly: AssemblyFileVersion("%dllVer%.0.0")]>>AssemblyInfo.cs + + + echo ^>build.proj + echo ^>>build.proj + echo ^%FrameworkTarget%^>>build.proj + if "%FrameworkType%"=="exe" ( + echo ^Exe^>>build.proj + ) else ( + echo ^Library^>>build.proj + ) + echo ^bin\Release\^>>build.proj + echo ^%projName%^>>build.proj + echo ^>>build.proj + + echo ^>>build.proj + echo ^>>build.proj + echo ^>>build.proj + echo ^>>build.proj + echo ^>>build.proj + + echo ^>>build.proj + echo ^>>build.proj + echo ^>>build.proj + echo ^>>build.proj + goto:eof + + +:Pause +pause +goto Run +:End \ No newline at end of file diff --git a/scripts/Create-dll.sh b/scripts/Create-dll.sh new file mode 100644 index 0000000..a6d9180 --- /dev/null +++ b/scripts/Create-dll.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +#[zh_CN] 在Linux、macOS系统终端中运行这个脚本文件,自动完成.cs文件编译生成.NET Standard 2.0的dll。需先安装了.NET Core SDK(支持.NET Core 2.0及以上版本,.NET 5+) +#[en_US] Run this script file in the terminal of Linux and macOS system to automatically compile the .cs file and generate the dll of .NET Standard 2.0. The .NET Core SDK needs to be installed first (supports .NET Core 2.0 and above, .NET 5+) + + +#.NET Core --framework: https://learn.microsoft.com/en-us/dotnet/standard/frameworks +CoreTargetSDK=netstandard2.0 + + +clear + +isZh=0 +if [ $(echo ${LANG/_/-} | grep -Ei "\\b(zh|cn)\\b") ]; then isZh=1; fi + +function echo2(){ + if [ $isZh == 1 ]; then echo $1; + else echo $2; fi +} +cd `dirname $0` +cd ../ +echo2 "显示语言:简体中文 `pwd`" "Language: English `pwd`" +function err(){ + if [ $isZh == 1 ]; then echo -e "\e[31m$1\e[0m"; + else echo -e "\e[31m$2\e[0m"; fi +} +function exit2(){ + if [ $isZh == 1 ]; then read -n1 -rp "请按任意键退出..." key; + else read -n1 -rp "Press any key to exit..."; fi + exit +} + + +echo2 "本脚本默认生成.NET Standard 2.0的dll,支持.NET Core 2.0及以上版本(.NET 5+),兼容.NET Framework 4.6.2及以上版本(请使用同名的bat脚本来创建Framework专用的dll)。" "This script generates .NET Standard 2.0 dll by default, supports .NET Core 2.0 and above (.NET 5+), and is compatible with .NET Framework 4.6.2 and above (Please use the bat script with the same name to create a Framework-specific dll)." +echo +echo2 "请输入需要生成的dll版本号:" "Please enter the version number of the dll that needs to be generated:" +read -rp "> " dllVer + + +#.NET CLI telemetry https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry +DOTNET_CLI_TELEMETRY_OPTOUT=1 + + +projName=RSA-CSharp.NET-Standard +rootDir=target/$projName + +echo2 "正在创.NET Core项目${rootDir}..." "Creating .NET Core project ${rootDir}..." +if [ ! -e $rootDir ]; then + mkdir -p $rootDir +else + rm ${rootDir}/* > /dev/null 2>&1 +fi + +cd $rootDir +dotnet new classlib -f $CoreTargetSDK +[ ! $? -eq 0 -o ! -e $projName*proj ] && { + echo + err "创建项目命令执行失败,请检查是否安装了.NET Core 2.0及以上版本的SDK(.NET 5+)" "The execution of the command to create a project failed. Please check whether the SDK of .NET Core 2.0 and above (.NET 5+) is installed" + exit2; +} +echo + +prop="${dllVer}<\\/Version> `date '+%Y-%m-%d'`, MIT, Copyright `date '+%Y'` xiangyuecn<\\/Copyright> xiangyuecn<\\/Authors> ${CoreTargetSDK}, https:\\/\\/github.com\\/xiangyuecn\\/RSA-csharp<\\/Product> RSA-csharp, ${CoreTargetSDK}, `date '+%Y-%m-%d'`<\\/Description>" + +projFile=`ls $projName*proj`; +sed -i -e 's// \$(DefineConstants);RSA_BUILD__NET_CORE<\/DefineConstants> '"${prop}"'/g' $projFile; +sed -i -e 's/Nullable>enable/Nullable>disable/g' $projFile +echo2 "已修改proj项目配置文件:${projFile},已启用RSA_BUILD__NET_CORE条件编译符号" "Modified proj project configuration file: ${projFile}, enabled RSA_BUILD__NET_CORE conditional compilation symbol" +echo + +rm *.cs > /dev/null 2>&1 +cp ../../RSA_PEM.cs ./ > /dev/null +cp ../../RSA_Util.cs ./ > /dev/null + + + +echo2 "正在编译.NET Core项目${rootDir}..." "Compiling .NET Core project ${rootDir}..." +echo +dotnet build -c Release +[ ! $? -eq 0 ] && { + echo + err "生成dll失败" "Failed to generate dll" + exit2; +} + +cd ../../ +dllRaw=${rootDir}/bin/Release/${CoreTargetSDK}/${projName}.dll +dllPath=target/${projName}.dll +rm $dllPath > /dev/null 2>&1 +[ -e $dllPath ] && { + echo + err "无法删除旧文件:${dllPath}" "Unable to delete old file: ${dllPath}" + exit2; +} +cp $dllRaw target + +[ ! -e $dllPath ] && { + echo + err "未定位到生成的dll文件路径,请到${rootDir}/bin寻找生成的dll文件 " "The generated dll file path is not located, please go to ${rootDir}/bin to find the generated dll file" + exit2; +} +echo +echo2 "已生成dll,文件在源码根目录:${dllPath}。 " "The dll has been generated, and the file is in the root directory of the source code: ${dllPath}." +echo + +exit2; diff --git a/vs.csproj b/vs.csproj deleted file mode 100644 index bed5d19..0000000 --- a/vs.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Debug - AnyCPU - {2DAD0BFE-E417-4AD8-8C44-7578F6758947} - Exe - Properties - RSA - RSA - v4.5 - 512 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vs.sln b/vs.sln deleted file mode 100644 index 7317ac4..0000000 --- a/vs.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RSA", "vs.csproj", "{2DAD0BFE-E417-4AD8-8C44-7578F6758947}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2DAD0BFE-E417-4AD8-8C44-7578F6758947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DAD0BFE-E417-4AD8-8C44-7578F6758947}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DAD0BFE-E417-4AD8-8C44-7578F6758947}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DAD0BFE-E417-4AD8-8C44-7578F6758947}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal