using System.Security.Cryptography;
using System.Text;
namespace Rmsys.Infrastructure;
///
/// 国密算法实现(GB/T 32907-2016 SM4、GB/T 32905-2016 SM3)。
///
/// 为什么自己实现而不引 BouncyCastle:
/// 1) PAD 端要在 Kotlin 里跑同一套算法,两边实现必须逐字节一致 —— 自己写一份照着移植最可控;
/// 2) APK 不因引入 bcprov 从 1.1MB 涨到 5-9MB;
/// 3) 后端少一个第三方依赖。
/// 正确性由官方标准测试向量保证(见 CryptoVectors 与 Rmsys.Tools 的 self-test-crypto)。
///
public static class Sm4
{
// S 盒
private static readonly byte[] S =
{
0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
};
private static readonly uint[] FK = { 0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc };
private static readonly uint[] CK =
{
0x00070e15,0x1c232a31,0x383f464d,0x545b6269,0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};
private static uint Rotl(uint x, int n) => (x << n) | (x >> (32 - n));
private static uint Tau(uint a) =>
((uint)S[(a >> 24) & 0xff] << 24) | ((uint)S[(a >> 16) & 0xff] << 16) |
((uint)S[(a >> 8) & 0xff] << 8) | S[a & 0xff];
private static uint TEnc(uint x)
{
var b = Tau(x);
return b ^ Rotl(b, 2) ^ Rotl(b, 10) ^ Rotl(b, 18) ^ Rotl(b, 24);
}
private static uint TKey(uint x)
{
var b = Tau(x);
return b ^ Rotl(b, 13) ^ Rotl(b, 23);
}
private static uint[] ExpandKey(byte[] key)
{
var rk = new uint[32];
var k = new uint[4];
for (var i = 0; i < 4; i++)
k[i] = ((uint)key[i * 4] << 24) | ((uint)key[i * 4 + 1] << 16) |
((uint)key[i * 4 + 2] << 8) | key[i * 4 + 3];
for (var i = 0; i < 4; i++) k[i] ^= FK[i];
var kk = new uint[36];
for (var i = 0; i < 4; i++) kk[i] = k[i];
for (var i = 0; i < 32; i++)
{
kk[i + 4] = kk[i] ^ TKey(kk[i + 1] ^ kk[i + 2] ^ kk[i + 3] ^ CK[i]);
rk[i] = kk[i + 4];
}
return rk;
}
/// 单块加密(16 字节 → 16 字节)
public static byte[] EncryptBlock(byte[] key, byte[] block)
{
var rk = ExpandKey(key);
var x = new uint[36];
for (var i = 0; i < 4; i++)
x[i] = ((uint)block[i * 4] << 24) | ((uint)block[i * 4 + 1] << 16) |
((uint)block[i * 4 + 2] << 8) | block[i * 4 + 3];
for (var i = 0; i < 32; i++)
x[i + 4] = x[i] ^ TEnc(x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[i]);
return Pack(x[35], x[34], x[33], x[32]);
}
/// 单块解密
public static byte[] DecryptBlock(byte[] key, byte[] block)
{
var rk = ExpandKey(key);
var x = new uint[36];
for (var i = 0; i < 4; i++)
x[i] = ((uint)block[i * 4] << 24) | ((uint)block[i * 4 + 1] << 16) |
((uint)block[i * 4 + 2] << 8) | block[i * 4 + 3];
for (var i = 0; i < 32; i++)
x[i + 4] = x[i] ^ TEnc(x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[31 - i]);
return Pack(x[35], x[34], x[33], x[32]);
}
private static byte[] Pack(uint a, uint b, uint c, uint d)
{
var o = new byte[16];
WriteU32(o, 0, a); WriteU32(o, 4, b); WriteU32(o, 8, c); WriteU32(o, 12, d);
return o;
}
private static void WriteU32(byte[] buf, int off, uint v)
{
buf[off] = (byte)(v >> 24); buf[off + 1] = (byte)(v >> 16);
buf[off + 2] = (byte)(v >> 8); buf[off + 3] = (byte)v;
}
/// SM4-CBC 加密(PKCS7 填充)
public static byte[] EncryptCbc(byte[] key, byte[] iv, byte[] plain)
{
var pad = 16 - plain.Length % 16;
var buf = new byte[plain.Length + pad];
Buffer.BlockCopy(plain, 0, buf, 0, plain.Length);
for (var i = plain.Length; i < buf.Length; i++) buf[i] = (byte)pad;
var rk = ExpandKey(key);
var prev = (byte[])iv.Clone();
var outp = new byte[buf.Length];
var block = new byte[16];
for (var off = 0; off < buf.Length; off += 16)
{
for (var i = 0; i < 16; i++) block[i] = (byte)(buf[off + i] ^ prev[i]);
var cipher = EncryptWithRk(rk, block);
Buffer.BlockCopy(cipher, 0, outp, off, 16);
prev = cipher;
}
return outp;
}
/// SM4-CBC 解密(去 PKCS7 填充)
public static byte[] DecryptCbc(byte[] key, byte[] iv, byte[] cipher)
{
if (cipher.Length == 0 || cipher.Length % 16 != 0)
throw new ArgumentException("密文长度不是 16 的整数倍");
var rk = ExpandKey(key);
var prev = (byte[])iv.Clone();
var outp = new byte[cipher.Length];
var block = new byte[16];
for (var off = 0; off < cipher.Length; off += 16)
{
Buffer.BlockCopy(cipher, off, block, 0, 16);
var plain = DecryptWithRk(rk, block);
for (var i = 0; i < 16; i++) outp[off + i] = (byte)(plain[i] ^ prev[i]);
prev = (byte[])block.Clone();
}
var pad = outp[^1];
if (pad < 1 || pad > 16) throw new CryptographicException("填充非法");
return outp[..(outp.Length - pad)];
}
private static byte[] EncryptWithRk(uint[] rk, byte[] block)
{
var x = new uint[36];
for (var i = 0; i < 4; i++)
x[i] = ((uint)block[i * 4] << 24) | ((uint)block[i * 4 + 1] << 16) |
((uint)block[i * 4 + 2] << 8) | block[i * 4 + 3];
for (var i = 0; i < 32; i++)
x[i + 4] = x[i] ^ TEnc(x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[i]);
return Pack(x[35], x[34], x[33], x[32]);
}
private static byte[] DecryptWithRk(uint[] rk, byte[] block)
{
var x = new uint[36];
for (var i = 0; i < 4; i++)
x[i] = ((uint)block[i * 4] << 24) | ((uint)block[i * 4 + 1] << 16) |
((uint)block[i * 4 + 2] << 8) | block[i * 4 + 3];
for (var i = 0; i < 32; i++)
x[i + 4] = x[i] ^ TEnc(x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[31 - i]);
return Pack(x[35], x[34], x[33], x[32]);
}
}
/// SM3 杂凑(GB/T 32905-2016)
public static class Sm3
{
private static readonly uint[] IV =
{
0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600,
0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e
};
private static uint Rotl(uint x, int n) => n == 0 ? x : (x << n) | (x >> (32 - n));
private static uint P0(uint x) => x ^ Rotl(x, 9) ^ Rotl(x, 17);
private static uint P1(uint x) => x ^ Rotl(x, 15) ^ Rotl(x, 23);
public static byte[] Hash(byte[] data)
{
var bitLen = (ulong)data.Length * 8;
var padLen = (data.Length % 64 < 56) ? 56 - data.Length % 64 : 120 - data.Length % 64;
var buf = new byte[data.Length + padLen + 8];
Buffer.BlockCopy(data, 0, buf, 0, data.Length);
buf[data.Length] = 0x80;
for (var i = 0; i < 8; i++) buf[buf.Length - 1 - i] = (byte)(bitLen >> (8 * i));
var v = (uint[])IV.Clone();
var w = new uint[68];
var w1 = new uint[64];
for (var off = 0; off < buf.Length; off += 64)
{
for (var i = 0; i < 16; i++)
w[i] = ((uint)buf[off + i * 4] << 24) | ((uint)buf[off + i * 4 + 1] << 16) |
((uint)buf[off + i * 4 + 2] << 8) | buf[off + i * 4 + 3];
for (var i = 16; i < 68; i++)
w[i] = P1(w[i - 16] ^ w[i - 9] ^ Rotl(w[i - 3], 15)) ^ Rotl(w[i - 13], 7) ^ w[i - 6];
for (var i = 0; i < 64; i++) w1[i] = w[i] ^ w[i + 4];
uint a = v[0], b = v[1], c = v[2], d = v[3], e = v[4], f = v[5], g = v[6], h = v[7];
for (var j = 0; j < 64; j++)
{
var t = j < 16 ? 0x79cc4519u : 0x7a879d8au;
var ss1 = Rotl(Rotl(a, 12) + e + Rotl(t, j % 32), 7);
var ss2 = ss1 ^ Rotl(a, 12);
var ff = j < 16 ? (a ^ b ^ c) : ((a & b) | (a & c) | (b & c));
var gg = j < 16 ? (e ^ f ^ g) : ((e & f) | (~e & g));
var tt1 = ff + d + ss2 + w1[j];
var tt2 = gg + h + ss1 + w[j];
d = c; c = Rotl(b, 9); b = a; a = tt1;
h = g; g = Rotl(f, 19); f = e; e = P0(tt2);
}
v[0] ^= a; v[1] ^= b; v[2] ^= c; v[3] ^= d;
v[4] ^= e; v[5] ^= f; v[6] ^= g; v[7] ^= h;
}
var o = new byte[32];
for (var i = 0; i < 8; i++)
{
o[i * 4] = (byte)(v[i] >> 24); o[i * 4 + 1] = (byte)(v[i] >> 16);
o[i * 4 + 2] = (byte)(v[i] >> 8); o[i * 4 + 3] = (byte)v[i];
}
return o;
}
/// HMAC-SM3(RFC 2104 结构,块长 64 字节)
public static byte[] Hmac(byte[] key, byte[] data)
{
const int B = 64;
var k = key.Length > B ? Hash(key) : key;
var ipad = new byte[B];
var opad = new byte[B];
for (var i = 0; i < B; i++)
{
var kb = i < k.Length ? k[i] : (byte)0;
ipad[i] = (byte)(kb ^ 0x36);
opad[i] = (byte)(kb ^ 0x5c);
}
var inner = new byte[B + data.Length];
Buffer.BlockCopy(ipad, 0, inner, 0, B);
Buffer.BlockCopy(data, 0, inner, B, data.Length);
var ih = Hash(inner);
var outer = new byte[B + 32];
Buffer.BlockCopy(opad, 0, outer, 0, B);
Buffer.BlockCopy(ih, 0, outer, B, 32);
return Hash(outer);
}
public static string Hex(byte[] b) => Convert.ToHexString(b).ToLowerInvariant();
public static byte[] FromHex(string s) => Convert.FromHexString(s.Replace(" ", "").Replace("-", ""));
}
/// 标准测试向量:用于两端实现的一致性自检
public static class CryptoVectors
{
public static (bool ok, string report) SelfTest()
{
var sb = new StringBuilder();
var ok = true;
// ---- SM4(GB/T 32907 附录 A)----
var key = Sm3.FromHex("0123456789abcdeffedcba9876543210");
var plain = Sm3.FromHex("0123456789abcdeffedcba9876543210");
var c1 = Sm3.Hex(Sm4.EncryptBlock(key, plain));
var exp1 = "681edf34d206965e86b3e94f536e4246";
var r1 = c1 == exp1;
ok &= r1;
sb.AppendLine($" SM4 单块加密 {(r1 ? "✔" : "✖")} {c1}");
// 1000000 次迭代向量太长,改用「加密后再解密回原文」验证可逆
var back = Sm3.Hex(Sm4.DecryptBlock(key, Sm3.FromHex(c1)));
var r2 = back == Sm3.Hex(plain);
ok &= r2;
sb.AppendLine($" SM4 解密回原文 {(r2 ? "✔" : "✖")} {back}");
// ---- SM3(GB/T 32905 附录 A)----
var h1 = Sm3.Hex(Sm3.Hash(Encoding.ASCII.GetBytes("abc")));
var e1 = "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0";
var r3 = h1 == e1;
ok &= r3;
sb.AppendLine($" SM3(\"abc\") {(r3 ? "✔" : "✖")} {h1}");
var h2 = Sm3.Hex(Sm3.Hash(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz")));
var e2 = "b80fe97a4da24afc277564f66a359ef440462ad28dcc6d63adb24d5c20a61595";
var r4 = h2 == e2;
ok &= r4;
sb.AppendLine($" SM3(\"a-z\") {(r4 ? "✔" : "✖")} {h2}");
// GB/T 32905 附录 A.2:输入为 "abcd" 重复 16 次(64 字节,正好一个块边界)
var abcd16 = string.Concat(Enumerable.Repeat("abcd", 16));
var h3 = Sm3.Hex(Sm3.Hash(Encoding.ASCII.GetBytes(abcd16)));
var e3 = "debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732";
var r5 = h3 == e3;
ok &= r5;
sb.AppendLine($" SM3(\"abcd\"x16) {(r5 ? "✔" : "✖")} {h3}");
// 跨块(>64 字节)再验一次
var lng = string.Concat(Enumerable.Repeat("a", 1000));
var h4 = Sm3.Hex(Sm3.Hash(Encoding.ASCII.GetBytes(lng)));
sb.AppendLine($" SM3(1000个a) {h4}");
// ---- SM4-CBC 往返 ----
var iv = new byte[16];
var data = Encoding.UTF8.GetBytes("海东市委组织部 干部任免上会系统 · 国密测试 · 2026");
var enc = Sm4.EncryptCbc(key, iv, data);
var dec = Sm4.DecryptCbc(key, iv, enc);
var r6 = Encoding.UTF8.GetString(dec) == Encoding.UTF8.GetString(data);
ok &= r6;
sb.AppendLine($" SM4-CBC 往返 {(r6 ? "✔" : "✖")} {data.Length}B → {enc.Length}B → {dec.Length}B");
// ---- HMAC-SM3 稳定性(固定输入必须固定输出,供两端比对)----
var mac = Sm3.Hex(Sm3.Hmac(Encoding.ASCII.GetBytes("rmsys-test-key"), Encoding.ASCII.GetBytes("haidong-zzb")));
sb.AppendLine($" HMAC-SM3 指纹 {mac}");
return (ok, sb.ToString());
}
}