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