重庆银行,国密SM4,自定义算法,C#.NET版
应该是自定义的算法,SM4/ECB/NoPadding没空调,用KIMI AI 翻译了份,实测可用。
SMS4.cs:
using System; namespace Com.Qgs.Sms4 { public class SMS4 { private const int ENCRYPT = 1; private const int DECRYPT = 0; public const int ROUND = 32; private const int BLOCK = 16; // 使用 sbyte 保持与 Java 有符号 byte 相同的数值(-128 ~ 127) private static readonly sbyte[] Sbox = new sbyte[] { -42, -112, -23, -2, -52, -31, 61, -73, 22, -74, 20, -62, 40, -5, 44, 5, 43, 103, -102, 118, 42, -66, 4, -61, -86, 68, 19, 38, 73, -122, 6, -103, -100, 66, 80, -12, -111, -17, -104, 122, 51, 84, 11, 67, -19, -49, -84, 98, -28, -77, 28, -87, -55, 8, -24, -107, -128, -33, -108, -6, 117, -113, 63, -90, 71, 7, -89, -4, -13, 115, 23, -70, -125, 89, 60, 25, -26, -123, 79, -88, 104, 107, -127, -78, 113, 100, -38, -117, -8, -21, 15, 75, 112, 86, -99, 53, 30, 36, 14, 94, 99, 88, -47, -94, 37, 34, 124, 59, 1, 33, 120, -121, -44, 0, 70, 87, -97, -45, 39, 82, 76, 54, 2, -25, -96, -60, -56, -98, -22, -65, -118, -46, 64, -57, 56, -75, -93, -9, -14, -50, -7, 97, 21, -95, -32, -82, 93, -92, -101, 52, 26, 85, -83, -109, 50, 48, -11, -116, -79, -29, 29, -10, -30, 46, -126, 102, -54, 96, -64, 41, 35, -85, 13, 83, 78, 111, -43, -37, 55, 69, -34, -3, -114, 47, 3, -1, 106, 114, 109, 108, 91, 81, -115, 27, -81, -110, -69, -35, -68, 127, 17, -39, 92, 65, 31, 16, 90, -40, 10, -63, 49, -120, -91, -51, 123, -67, 45, 116, -48, 18, -72, -27, -76, -80, -119, 105, -105, 74, 12, -106, 119, 126, 101, -71, -15, 9, -59, 110, -58, -124, 24, -16, 125, -20, 58, -36, 77, 32, 121, -18, 95, 62, -41, -53, 57, 72 }; private static readonly int[] CK = new int[] { 462357, 472066609, 943670861, 1415275113, 1886879365, -1936483679, -1464879427, -993275175, -521670923, -66909679, 404694573, 876298825, 1347903077, 1819507329, -2003855715, -1532251463, -1060647211, -589042959, -117504499, 337322537, 808926789, 1280531041, 1752135293, -2071227751, -1599623499, -1128019247, -656414995, -184876535, 269950501, 741554753, 1213159005, 1684763257 }; private int Rotl(int x, int y) { // 实现 Java 的 >>>(无符号右移):先转 uint 再右移 return (x << y) | (int)((uint)x >> (32 - y)); } private int ByteSub(int A) { // & 255 确保将 sbyte 转为 0-255 范围的整数 return ((Sbox[((uint)A >> 24) & 255] & 255) & 255) << 24 | ((Sbox[((uint)A >> 16) & 255] & 255) & 255) << 16 | ((Sbox[((uint)A >> 8) & 255] & 255) & 255) << 8 | ((Sbox[A & 255] & 255) & 255); } private int L1(int B) { return B ^ Rotl(B, 2) ^ Rotl(B, 10) ^ Rotl(B, 18) ^ Rotl(B, 24); } private int L2(int B) { return B ^ Rotl(B, 13) ^ Rotl(B, 23); } public void SMS4Crypt(byte[] Input, byte[] Output, int[] rk) { int[] x = new int[4]; int[] tmp = new int[4]; for (int i = 0; i < 4; ++i) { tmp[0] = Input[0 + 4 * i] & 255; tmp[1] = Input[1 + 4 * i] & 255; tmp[2] = Input[2 + 4 * i] & 255; tmp[3] = Input[3 + 4 * i] & 255; x[i] = (tmp[0] << 24) | (tmp[1] << 16) | (tmp[2] << 8) | tmp[3]; } for (int r = 0; r < 32; r += 4) { int mid = x[1] ^ x[2] ^ x[3] ^ rk[r + 0]; mid = ByteSub(mid); x[0] ^= L1(mid); mid = x[2] ^ x[3] ^ x[0] ^ rk[r + 1]; mid = ByteSub(mid); x[1] ^= L1(mid); mid = x[3] ^ x[0] ^ x[1] ^ rk[r + 2]; mid = ByteSub(mid); x[2] ^= L1(mid); mid = x[0] ^ x[1] ^ x[2] ^ rk[r + 3]; mid = ByteSub(mid); x[3] ^= L1(mid); } for (int j = 0; j < 16; j += 4) { Output[j] = (byte)((x[3 - j / 4] >> 24) & 255); Output[j + 1] = (byte)((x[3 - j / 4] >> 16) & 255); Output[j + 2] = (byte)((x[3 - j / 4] >> 8) & 255); Output[j + 3] = (byte)(x[3 - j / 4] & 255); } } private void SMS4KeyExt(byte[] Key, int[] rk, int CryptFlag) { int[] x = new int[4]; int[] tmp = new int[4]; for (int i = 0; i < 4; ++i) { tmp[0] = Key[0 + 4 * i] & 255; tmp[1] = Key[1 + 4 * i] & 255; tmp[2] = Key[2 + 4 * i] & 255; tmp[3] = Key[3 + 4 * i] & 255; x[i] = (tmp[0] << 24) | (tmp[1] << 16) | (tmp[2] << 8) | tmp[3]; } x[0] ^= -1548633402; x[1] ^= 1453994832; x[2] ^= 1736282519; x[3] ^= -1301273892; for (int r = 0; r < 32; r += 4) { int mid = x[1] ^ x[2] ^ x[3] ^ CK[r + 0]; mid = ByteSub(mid); rk[r + 0] = x[0] ^= L2(mid); mid = x[2] ^ x[3] ^ x[0] ^ CK[r + 1]; mid = ByteSub(mid); rk[r + 1] = x[1] ^= L2(mid); mid = x[3] ^ x[0] ^ x[1] ^ CK[r + 2]; mid = ByteSub(mid); rk[r + 2] = x[2] ^= L2(mid); mid = x[0] ^ x[1] ^ x[2] ^ CK[r + 3]; mid = ByteSub(mid); rk[r + 3] = x[3] ^= L2(mid); } if (CryptFlag == 0) { for (int i = 0; i < 16; ++i) { int mid = rk[i]; rk[i] = rk[31 - i]; rk[31 - i] = mid; } } } public int sms4(byte[] inn, int inLen, byte[] key, byte[] outt, int CryptFlag) { int point = 0; int[] round_key = new int[32]; SMS4KeyExt(key, round_key, CryptFlag); byte[] input = new byte[16]; byte[] output = new byte[16]; while (inLen >= 16) { Array.Copy(inn, point, input, 0, 16); SMS4Crypt(input, output, round_key); Array.Copy(output, 0, outt, point, 16); inLen -= 16; point += 16; } return 0; } } }
SMS4Util.cs:
using System; using System.Text; namespace Com.Qgs.Sms4 { public class SMS4Util { public static byte[] EncodeSMS4(string plaintext, byte[] key) { if (!string.IsNullOrEmpty(plaintext)) { #region 2种方法都可以 //byte[] bytes = Encoding.UTF8.GetBytes(plaintext); //// 填充 \0 使长度为 16 的倍数 //StringBuilder sb = new StringBuilder(plaintext); //int remainder = bytes.Length % 16; //for (int i = remainder; i < 16; ++i) //{ // sb.Append('\0'); //} //byte[] inputBytes = Encoding.UTF8.GetBytes(sb.ToString()); #endregion // 使用 UTF-8 编码(跨平台一致) byte[] inputBytes = Encoding.UTF8.GetBytes(plaintext); int padding = 16 - (inputBytes.Length % 16); if (padding != 16) { Array.Resize(ref inputBytes, inputBytes.Length + padding); // 填充 \0(C# 中数组新元素默认为 0,无需额外处理) } return EncodeSMS4(inputBytes, key); } else { return null; } } public static byte[] EncodeSMS4(byte[] plaintext, byte[] key) { byte[] ciphertext = new byte[plaintext.Length]; int k = 0; for (int plainLen = plaintext.Length; k + 16 <= plainLen; k += 16) { byte[] cellPlain = new byte[16]; for (int i = 0; i < 16; ++i) { cellPlain[i] = plaintext[k + i]; } byte[] cellCipher = Encode16(cellPlain, key); for (int i = 0; i < cellCipher.Length; ++i) { ciphertext[k + i] = cellCipher[i]; } } return ciphertext; } public static byte[] DecodeSMS4(byte[] ciphertext, byte[] key) { byte[] plaintext = new byte[ciphertext.Length]; int k = 0; for (int cipherLen = ciphertext.Length; k + 16 <= cipherLen; k += 16) { byte[] cellCipher = new byte[16]; for (int i = 0; i < 16; ++i) { cellCipher[i] = ciphertext[k + i]; } byte[] cellPlain = Decode16(cellCipher, key); for (int i = 0; i < cellPlain.Length; ++i) { plaintext[k + i] = cellPlain[i]; } } return plaintext; } public static string DecodeSMS4toString(byte[] ciphertext, byte[] key) { byte[] plaintext = DecodeSMS4(ciphertext, key); string plain = Encoding.UTF8.GetString(plaintext); return plain.Trim(); } private static byte[] Encode16(byte[] plaintext, byte[] key) { byte[] cipher = new byte[16]; SMS4 sm4 = new SMS4(); int ENCRYPT = 1; sm4.sms4(plaintext, 16, key, cipher, ENCRYPT); return cipher; } private static byte[] Decode16(byte[] ciphertext, byte[] key) { byte[] plain = new byte[16]; SMS4 sm4 = new SMS4(); int DECRYPT = 0; sm4.sms4(ciphertext, 16, key, plain, DECRYPT); return plain; } } }
用法:
using Com.Qgs.Sms4; using Org.BouncyCastle.Utilities.Encoders; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleCQSM4 { internal class Program { static void Main(string[] args) { Console.WriteLine("--"); try { TestDecrypt(); string inputStr = "{\"_MCHTimestamp\":\"2026-01-01 00:00:00.000\",\"_ChannelJnlNo\":\"1234\",\"ChannelUuid\":\"字符串\"}"; string SM4_SALT = "9FE3291C77474769AB49794176DDC1DD"; byte[] keyBytes = Encoding.UTF8.GetBytes(SM4_SALT); byte[] sourceData = Encoding.UTF8.GetBytes(inputStr); byte[] encryptBytes = SMS4Util.EncodeSMS4(inputStr, keyBytes); String payloadEncrypt = Hex.ToHexString(encryptBytes); Console.WriteLine(".NET 加密结果:" + payloadEncrypt); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } static void TestDecrypt() { string intputStr = "09ddc944012201622519d5eab8a897b9b13d8c2c83d0b082f5514506e55ff83d4794e5a0f6ea48694e25e78d661487b0da13ded0c709db93e1fad517a02446570914a04bff5bfbaa5231dd1a6509e572f14b7e89fde074bc0a4f0644a507bdd57ce7f4a6912da07964b599e68da2f11598718463e3432338c1664eea845e1c910222eff300f21a8e4f0c9eab449b3ccbf7597288c6218577c4e44470a52ef580f42e9096df031eca40115b432bc0d2f003c774a04959938165bfd728fe40d120216ba5d1bfaa31da70bc7fdfa250cecb8cb85cb6b5895b8c6ce7a04f49bec2d700a15171707b69b5aa988b71d71e34526ffd7251c9c6e348eee833139cce85accb510c956c8279884a947f6d38d2e74e"; Console.WriteLine("JAVA 加密结果:" + intputStr); string SM4_SALT = "9FE3291C77474769AB49794176DDC1DD"; byte[] keyBytes = Encoding.UTF8.GetBytes(SM4_SALT); byte[] sourceData = Hex.Decode(intputStr); byte[] encryptBytes = SMS4Util.DecodeSMS4(sourceData, keyBytes); string myRst = Encoding.UTF8.GetString(encryptBytes); //string myRst = Hex.ToHexString(encryptBytes); Console.WriteLine("解密结果:" + myRst); } } }
要注意,.NET要引用 BouncyCastle.Crypto 库。
JAVA 那边解密要用 “SMS4Util.decodeSMS4toString” ,不能用 “SMS4Util.decodeSMS4” ,否则尾部有方块。

浙公网安备 33010602011771号