|
|
@@ -0,0 +1,287 @@
|
|
|
+using System;
|
|
|
+using System.Text;
|
|
|
+using System.Security.Cryptography;
|
|
|
+using Newtonsoft.Json.Linq;
|
|
|
+using Org.BouncyCastle.Crypto;
|
|
|
+using Org.BouncyCastle.Crypto.Digests;
|
|
|
+using Org.BouncyCastle.Crypto.Engines;
|
|
|
+using Org.BouncyCastle.Crypto.Macs;
|
|
|
+using Org.BouncyCastle.Crypto.Parameters;
|
|
|
+using Org.BouncyCastle.Crypto.Generators;
|
|
|
+using Org.BouncyCastle.Crypto.Prng;
|
|
|
+using Org.BouncyCastle.Security;
|
|
|
+using Org.BouncyCastle.Crypto.Paddings;
|
|
|
+using Org.BouncyCastle.Crypto.Modes;
|
|
|
+using Org.BouncyCastle.Math;
|
|
|
+using Org.BouncyCastle.Asn1.Sec;
|
|
|
+using Org.BouncyCastle.Asn1.X9;
|
|
|
+using Org.BouncyCastle.Asn1.X509;
|
|
|
+using Org.BouncyCastle.Asn1.Pkcs;
|
|
|
+using Org.BouncyCastle.Pkcs;
|
|
|
+using Org.BouncyCastle.Utilities.Encoders;
|
|
|
+
|
|
|
+namespace MySystem
|
|
|
+{
|
|
|
+ public class SMUtil
|
|
|
+ {
|
|
|
+ private const string KEY_ALGORITHM = "AES";
|
|
|
+ public const string SM4_ECB_PKCS5PADDING = "SM4/ECB/PKCS5Padding";
|
|
|
+ public const string ALGORITHM_NAME = "SM4";
|
|
|
+
|
|
|
+ static SMUtil()
|
|
|
+ {
|
|
|
+ // 在 .NET 中不需要像 Java 那样显式注册提供者,BouncyCastle 通过 NuGet 引用即可使用
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成一个对称秘钥(返回字符串,行为与 Java 版保持一致:base64 后取前 16 个字符)
|
|
|
+ public static string GetSymmetrickey()
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ byte[] keyBytes = new byte[32]; // 256 bits
|
|
|
+ using (var rng = new RNGCryptoServiceProvider())
|
|
|
+ {
|
|
|
+ rng.GetBytes(keyBytes);
|
|
|
+ }
|
|
|
+ string key = Convert.ToBase64String(keyBytes);
|
|
|
+ if (key.Length >= 16)
|
|
|
+ key = key.Substring(0, 16);
|
|
|
+ return key;
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ return string.Empty;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成 SM2 公私钥对,返回 { publicKeyBase64, privateKeyBase64 }
|
|
|
+ public static string[] GenerateSmKey()
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ X9ECParameters ecP = ECNamedCurveTable.GetByName("sm2p256v1");
|
|
|
+ ECDomainParameters domainParams = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H);
|
|
|
+
|
|
|
+ var gen = new ECKeyPairGenerator();
|
|
|
+ var secureRandom = new SecureRandom();
|
|
|
+ var keyGenParam = new ECKeyGenerationParameters(domainParams, secureRandom);
|
|
|
+ gen.Init(keyGenParam);
|
|
|
+ AsymmetricCipherKeyPair pair = gen.GenerateKeyPair();
|
|
|
+
|
|
|
+ // 构造 X.509 SubjectPublicKeyInfo(使用公钥点),以及 PKCS#8 PrivateKeyInfo
|
|
|
+ var pubParam = pair.Public as ECPublicKeyParameters;
|
|
|
+ var privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
|
|
|
+ // AlgorithmIdentifier: id-ecPublicKey + named curve OID
|
|
|
+ var curveOid = ECNamedCurveTable.GetOid("sm2p256v1");
|
|
|
+ var algId = new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(X9ObjectIdentifiers.IdECPublicKey, curveOid);
|
|
|
+ var spki = new SubjectPublicKeyInfo(algId, pubParam.Q.GetEncoded(false));
|
|
|
+ byte[] pubBytes = spki.ToAsn1Object().GetEncoded();
|
|
|
+ byte[] privBytes = privInfo.ToAsn1Object().GetEncoded();
|
|
|
+
|
|
|
+ return new string[] { Convert.ToBase64String(pubBytes), Convert.ToBase64String(privBytes) };
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从 base64 字符串恢复公钥参数(AsymmetricKeyParameter)
|
|
|
+ public static AsymmetricKeyParameter CreatePublicKey(string publicKeyBase64)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ byte[] data = Convert.FromBase64String(publicKeyBase64);
|
|
|
+ return PublicKeyFactory.CreateKey(data);
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从 base64 字符串恢复私钥参数
|
|
|
+ public static AsymmetricKeyParameter CreatePrivateKey(string privateKeyBase64)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ byte[] data = Convert.FromBase64String(privateKeyBase64);
|
|
|
+ return PrivateKeyFactory.CreateKey(data);
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // SM2 加密(对称秘钥字符串使用 SM2 公钥加密,返回 base64)
|
|
|
+ public static string Sm2Encrypt(string symmetrickey, string pubKeyBase64)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var pubParam = CreatePublicKey(pubKeyBase64) as ECPublicKeyParameters;
|
|
|
+ if (pubParam == null) return null;
|
|
|
+ var engine = new SM2Engine();
|
|
|
+ engine.Init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
|
|
|
+ byte[] inBytes = Encoding.UTF8.GetBytes(symmetrickey);
|
|
|
+ byte[] outBytes = engine.ProcessBlock(inBytes, 0, inBytes.Length);
|
|
|
+ return Convert.ToBase64String(outBytes);
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // SM2 解密(传入 base64 密文和私钥 base64)
|
|
|
+ public static string Sm2Decrypt(string cipherBase64, string priKeyBase64)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var priParam = CreatePrivateKey(priKeyBase64) as ECPrivateKeyParameters;
|
|
|
+ if (priParam == null) return null;
|
|
|
+ var engine = new SM2Engine();
|
|
|
+ engine.Init(false, priParam);
|
|
|
+ byte[] inBytes = Convert.FromBase64String(cipherBase64);
|
|
|
+ byte[] outBytes = engine.ProcessBlock(inBytes, 0, inBytes.Length);
|
|
|
+ return Encoding.UTF8.GetString(outBytes);
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // SM3 摘要
|
|
|
+ public static byte[] Sm3(byte[] srcData)
|
|
|
+ {
|
|
|
+ var digest = new SM3Digest();
|
|
|
+ digest.BlockUpdate(srcData, 0, srcData.Length);
|
|
|
+ byte[] result = new byte[digest.GetDigestSize()];
|
|
|
+ digest.DoFinal(result, 0);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string Sm3Hex(string srcData)
|
|
|
+ {
|
|
|
+ byte[] hash = Sm3(Encoding.UTF8.GetBytes(srcData));
|
|
|
+ return Hex.ToHexString(hash);
|
|
|
+ }
|
|
|
+
|
|
|
+ // HMAC-SM3
|
|
|
+ public static byte[] HmacSm3(byte[] key, byte[] srcData)
|
|
|
+ {
|
|
|
+ var mac = new HMac(new SM3Digest());
|
|
|
+ mac.Init(new KeyParameter(key));
|
|
|
+ mac.BlockUpdate(srcData, 0, srcData.Length);
|
|
|
+ byte[] result = new byte[mac.GetMacSize()];
|
|
|
+ mac.DoFinal(result, 0);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string HmacSm3Hex(string key, string srcData)
|
|
|
+ {
|
|
|
+ byte[] hash = HmacSm3(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(srcData));
|
|
|
+ return Hex.ToHexString(hash);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 另一种调用 MessageDigest 的 SM3 实现(使用 BouncyCastle 的 Digest 就足够了)
|
|
|
+ public static byte[] Sm3Bc(byte[] srcData)
|
|
|
+ {
|
|
|
+ return Sm3(srcData);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string Sm3BcHex(string srcData)
|
|
|
+ {
|
|
|
+ return Sm3Hex(srcData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // SM4 加解密(使用 BouncyCastle 的块加密器和填充)
|
|
|
+ public static string Sm4Encrypt(string symmetrickey, string data)
|
|
|
+ {
|
|
|
+ string algorithmName = SM4_ECB_PKCS5PADDING;
|
|
|
+ byte[] key = Encoding.UTF8.GetBytes(symmetrickey);
|
|
|
+ byte[] iv = Encoding.UTF8.GetBytes(symmetrickey);
|
|
|
+ byte[] cipher = Sm4Core(algorithmName, true, key, iv, Encoding.UTF8.GetBytes(data));
|
|
|
+ return Convert.ToBase64String(cipher);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string Sm4Decrypt(string symmetrickey, string data)
|
|
|
+ {
|
|
|
+ string algorithmName = SM4_ECB_PKCS5PADDING;
|
|
|
+ byte[] key = Encoding.UTF8.GetBytes(symmetrickey);
|
|
|
+ byte[] iv = Encoding.UTF8.GetBytes(symmetrickey);
|
|
|
+ byte[] plain = Sm4Core(algorithmName, false, key, iv, Convert.FromBase64String(data));
|
|
|
+ return Encoding.UTF8.GetString(plain);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] Sm4Core(string algorithmName, bool forEncrypt, byte[] key, byte[] iv, byte[] input)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // 将 Java 的 "SM4/ECB/PKCS5Padding" 换为 BouncyCastle 可识别的形式
|
|
|
+ string bcName = algorithmName.Replace("PKCS5Padding", "PKCS7Padding");
|
|
|
+ var cipher = CipherUtilities.GetCipher(bcName);
|
|
|
+ ICipherParameters parameters = null;
|
|
|
+ if (algorithmName.Contains("/ECB/"))
|
|
|
+ {
|
|
|
+ parameters = new KeyParameter(key);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ parameters = new ParametersWithIV(new KeyParameter(key), iv);
|
|
|
+ }
|
|
|
+ cipher.Init(forEncrypt, parameters);
|
|
|
+ byte[] outBytes = cipher.DoFinal(input);
|
|
|
+ return outBytes;
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ throw new Exception("SM4 operation failed", ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将 JObject 的 key 转为小写(null 或空值替换为 "")
|
|
|
+ public static JObject DealJSONObj(JObject jsonObj)
|
|
|
+ {
|
|
|
+ var result = new JObject();
|
|
|
+ foreach (var prop in jsonObj.Properties())
|
|
|
+ {
|
|
|
+ JToken val = prop.Value;
|
|
|
+ if (!HasValue(val))
|
|
|
+ val = "";
|
|
|
+ result[prop.Name.ToLower()] = val;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static JArray DealJSONArray(JArray jsonArray)
|
|
|
+ {
|
|
|
+ var arr = new JArray();
|
|
|
+ foreach (var item in jsonArray)
|
|
|
+ {
|
|
|
+ if (item is JObject jo)
|
|
|
+ {
|
|
|
+ arr.Add(DealJSONObj(jo));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ arr.Add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return arr;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool HasValue(JToken token)
|
|
|
+ {
|
|
|
+ if (token == null) return false;
|
|
|
+ if (token.Type == JTokenType.Null) return false;
|
|
|
+ if (token.Type == JTokenType.String)
|
|
|
+ {
|
|
|
+ var s = token.ToString();
|
|
|
+ return !string.IsNullOrWhiteSpace(s);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|