using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Prng; using Org.BouncyCastle.Security; using System; using System.Security.Cryptography; using System.Text; namespace Laservall.Solidworks.Common { /// /// 国密4加密 /// 专供plm对接 /// public class SM4Helper { /// /// 定制的随机数生成器 /// plm对接用 /// /// /// public static byte[] CustomGenerateBytes(byte[] seed) { return CustomGenerateBytes(seed, 128); } /// /// 定制的随机数生成器 /// plm对接用 /// /// /// /// public static byte[] CustomGenerateBytes(byte[] seed, int keySize) { var kg = GeneratorUtilities.GetKeyGenerator("SM4"); var rng = new CustomSHA1PRNG(); rng.AddSeedMaterial(seed); var sr = new SecureRandom(rng); kg.Init(new KeyGenerationParameters(sr, keySize)); return kg.GenerateKey(); } /// /// 16进制字符串转bytes /// /// /// public static byte[] HexConvert(string hexString) { byte[] byteArray = new byte[hexString.Length / 2]; for (int i = 0; i < hexString.Length; i += 2) { byteArray[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); } return byteArray; } /// /// bytes转16进制字符串 /// /// /// public static string HexConvert(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", "").ToLower(); } /// /// 签名 /// https://flowus.cn/dnge/share/7a475b74-bbdc-4686-b6bd-36ce3ee47f02 /// /// /// /// /// public static string Sign(string sk, string ak, string ts) { var md5 = $"{ak}{ts}{{}}"; md5 = HexConvert(new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(md5))); var lastMD5 = HexConvert(new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(ts))) .Substring(16); byte[] plaintext = Encoding.UTF8.GetBytes(md5); byte[] keyBytes = CustomGenerateBytes(Encoding.UTF8.GetBytes(sk)); byte[] iv = CustomGenerateBytes(Encoding.UTF8.GetBytes(lastMD5)); KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes); ParametersWithIV keyParamWithIv = new ParametersWithIV(key, iv); IBufferedCipher inCipher = CipherUtilities.GetCipher("SM4/CBC/PKCS5Padding"); inCipher.Init(true, keyParamWithIv); return HexConvert(inCipher.DoFinal(plaintext)); } /// /// 验签 /// /// /// /// /// /// public static bool CheckSign(string sk, string ak, string ts, string target) { var md5 = $"{ak}{ts}{{}}"; md5 = HexConvert(new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(md5))); var lastMD5 = HexConvert(new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(ts))) .Substring(16); //byte[] plaintext = Encoding.UTF8.GetBytes(md5); byte[] keyBytes = CustomGenerateBytes(Encoding.UTF8.GetBytes(sk)); byte[] iv = CustomGenerateBytes(Encoding.UTF8.GetBytes(lastMD5)); KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes); ParametersWithIV keyParamWithIv = new ParametersWithIV(key, iv); IBufferedCipher inCipher = CipherUtilities.GetCipher("SM4/CBC/PKCS5Padding"); inCipher.Init(false, keyParamWithIv); return md5 == Encoding.UTF8.GetString(inCipher.DoFinal(HexConvert(target))); } /// /// 定制的sha1随机数生成器 /// plm那边用seed控制随机数来稳定加密,很奇怪 /// java和dotnet随机数实现不一样,加解密不通,所以得搬一个 /// 找到一个其他人的实现,修修补补用起来了 /// https://stackoverflow.com/questions/70587126/how-to-get-the-same-result-in-c-sharp-with-securerandom-getinstancesha1prng /// private class CustomSHA1PRNG : IRandomGenerator { private const int DIGEST_SIZE = 20; public CustomSHA1PRNG() { } private static void updateState(byte[] state, byte[] output) { int last = 1; int v; byte t; bool zf = false; // state(n + 1) = (state(n) + output(n) + 1) % 2^160; for (int i = 0; i < state.Length; i++) { // Add two bytes v = (int)(sbyte)state[i] + (int)(sbyte)output[i] + last; // Result is lower 8 bits t = (byte)(sbyte)v; // Store result. Check for state collision. zf = zf | (state[i] != t); state[i] = t; // High 8 bits are carry. Store for next iteration. last = v >> 8; } // Make sure at least one bit changes! if (!zf) { state[0] = (byte)(sbyte)(state[0] + 1); } } private static void GetBytes(byte[] seed, byte[] result) { byte[] state; byte[] remainder = null; int remCount; int index = 0; int todo; byte[] output = remainder; using (var sha1 = new SHA1CryptoServiceProvider()) { state = sha1.ComputeHash(seed); remCount = 0; // Use remainder from last time int r = remCount; if (r > 0) { // How many bytes? todo = (result.Length - index) < (DIGEST_SIZE - r) ? (result.Length - index) : (DIGEST_SIZE - r); // Copy the bytes, zero the buffer for (int i = 0; i < todo; i++) { result[i] = output[r]; output[r++] = 0; } remCount += todo; index += todo; } // If we need more bytes, make them. while (index < result.Length) { // Step the state output = sha1.ComputeHash(state); updateState(state, output); // How many bytes? todo = (result.Length - index) > DIGEST_SIZE ? DIGEST_SIZE : result.Length - index; // Copy the bytes, zero the buffer for (int i = 0; i < todo; i++) { result[index++] = output[i]; output[i] = 0; } remCount += todo; } // Store remainder for next time //remainder = output; //remCount %= DIGEST_SIZE; } } private static byte[] Seed { get; set; } public void AddSeedMaterial(byte[] seed) { Seed = seed; } public void AddSeedMaterial(long seed) { throw new NotImplementedException(); } public void NextBytes(byte[] bytes) { GetBytes(Seed, bytes); } public void NextBytes(byte[] bytes, int start, int len) { throw new NotImplementedException(); } } } }