runliuv

runliuv@cnblogs

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

重庆银行,国密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” ,否则尾部有方块。

 

posted on 2026-02-03 10:41  runliuv  阅读(0)  评论(0)    收藏  举报