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();
}
}
}
}