Skip to content

提个建议,有关登录安全: #3906

Open

Description

@fsea

目前登录时密码通过md5后发送到接口,但md5本质上并不是加密算法并且md5可被爆破,目前也不再允许使用在这种场合。
建议将密码调整为SM2加密发送,在提交事件里先获取公钥,然后加密密码,加密后再发送到接口,确保密码在传输过程中不被泄露和爆破。
同步在后台增加SM公钥、私钥的配置项。

例如:
login.js添加:
新增变量:var $urlPublicKey = '/login/actions/publicKey';
新增方法:

  passwordSM2: function (pwd) {
    var sm2pwd = "";
    $.ajax({
      type: "GET",
      url: $apiUrl + $urlPublicKey,
      data: {},
      dataType: "json",
      async: false,
      success: function (data) {
        sm2pwd = "04" + sm2.doEncrypt(pwd, data.value, 0);
      }
    });
    return sm2pwd;
  },

apiSubmit方法中修改为:
password: this.passwordSM2(this.password),

/login/actions/publicKey:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SSCMS.Configuration;
using SSCMS.Dto;
using System.Threading.Tasks;

namespace SSCMS.Web.Controllers.Admin
{
    public partial class LoginController
    {
        [HttpGet, Route(RouteGetSM2PublicKey)]
        public async Task<ActionResult<StringResult>> GetSM2PublicKey()
        {
            var config = await _configRepository.GetAsync();
            if (string.IsNullOrEmpty(config.Sm2PublicKey))
            {
                Utils.SMCryptoUtil.GenerateSm2Key(out var sM2PublicKey, out var sM2PrivateKey);
                config.Sm2PublicKey = sM2PublicKey;
                config.Sm2PrivateKey = sM2PrivateKey;
                _configRepository.UpdateAsync(config);
            }
            return new StringResult()
            {
                Value = config.Sm2PublicKey
            };
        }
    }
}

SSCMS项目的Util下增加SMCryptoUtil.cs:

using System;
using System.Text;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;

namespace SSCMS.Utils
{
    public class SMCryptoUtil
    {
        #region SM2 密钥生成
        /// <summary>
        /// 生成SM2公钥私钥(输出十六进制字符串)
        /// </summary>
        /// <param name="publicKeyHex">SM2公钥(16进制字符串)</param>
        /// <param name="privateKeyHex">SM2私钥(16进制字符串)</param>
        public static void GenerateSm2Key(out string publicKeyHex, out string privateKeyHex)
        {
            // 生成字节数组密钥,转十六进制字符串
            GenerateSm2Key(out byte[] pubBytes, out byte[] priBytes);
            publicKeyHex = Hex.ToHexString(pubBytes).ToUpper();
            privateKeyHex = Hex.ToHexString(priBytes).ToUpper();
        }

        /// <summary>
        /// 生成SM2密钥对(内部字节数组版,对外不暴露)
        /// </summary>
        private static void GenerateSm2Key(out byte[] publicKey, out byte[] privateKey)
        {
            var curveParams = GMNamedCurves.GetByName("SM2P256V1");
            var ecDomainParams = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H);

            var g = new ECKeyPairGenerator();
            g.Init(new ECKeyGenerationParameters(ecDomainParams, new SecureRandom()));
            var k = g.GenerateKeyPair();

            publicKey = ((ECPublicKeyParameters)k.Public).Q.GetEncoded(false);
            privateKey = ((ECPrivateKeyParameters)k.Private).D.ToByteArrayUnsigned();
        }
        #endregion

        #region SM2 加密
        /// <summary>
        /// SM2加密(输入输出均为十六进制字符串)
        /// </summary>
        /// <param name="plainText">数据源(16进制字符串)</param>
        /// <param name="publicKeyHex">公钥(16进制字符串)</param>
        /// <returns>加密结果(16进制字符串)</returns>
        public static string Sm2Encrypt(string plainText, string publicKeyHex)
        {
            byte[] sourceBytes = Decode(Utf8ToHexString(plainText));
            byte[] pubBytes = Decode(publicKeyHex);
            byte[] encryptBytes = Sm2Encrypt(pubBytes, sourceBytes);
            // 加密结果转十六进制字符串
            return Hex.ToHexString(encryptBytes).ToUpper();
        }

        /// <summary>
        /// SM2加密(内部字节数组版)
        /// </summary>
        private static byte[] Sm2Encrypt(byte[] publicKey, byte[] sourceData)
        {
            var curveParams = GMNamedCurves.GetByName("SM2P256V1");
            var ecDomainParams = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H);
            ECPublicKeyParameters pubKeyParams = new ECPublicKeyParameters(curveParams.Curve.DecodePoint(publicKey), ecDomainParams);

            var sm2 = new SM2Engine(new SM3Digest());
            sm2.Init(true, new ParametersWithRandom(pubKeyParams));
            return sm2.ProcessBlock(sourceData, 0, sourceData.Length);
        }
        #endregion

        #region SM2 解密
        /// <summary>
        /// SM2解密(输入输出均为十六进制字符串)
        /// </summary>
        /// <param name="encryptDataHex">加密数据(16进制字符串)</param>
        /// <param name="privateKeyHex">私钥(16进制字符串)</param>
        /// <returns>解密结果(16进制字符串)</returns>
        public static string Sm2DecryptToHex(string encryptDataHex, string privateKeyHex)
        {
            byte[] encryptBytes = Decode(encryptDataHex);
            byte[] priBytes = Decode(privateKeyHex);
            byte[] decryptBytes = Sm2Decrypt(priBytes, encryptBytes);
            // 解密结果转十六进制字符串
            return Hex.ToHexString(decryptBytes).ToUpper();
        }

        /// <summary>
        /// SM2解密(重载:解密结果直接返回UTF8字符串)
        /// </summary>
        /// <param name="encryptDataHex">加密数据(16进制字符串)</param>
        /// <param name="privateKeyHex">私钥(16进制字符串)</param>
        /// <returns>解密结果(UTF8字符串)</returns>
        public static string Sm2Decrypt(string encryptDataHex, string privateKeyHex)
        {
            byte[] decryptBytes = Decode(Sm2DecryptToHex(encryptDataHex, privateKeyHex));
            return Encoding.UTF8.GetString(decryptBytes);
        }

        /// <summary>
        /// SM2解密(内部字节数组版)
        /// </summary>
        private static byte[] Sm2Decrypt(byte[] privateKey, byte[] sourceData)
        {
            var curveParams = GMNamedCurves.GetByName("SM2P256V1");
            var ecDomainParams = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H);
            ECPrivateKeyParameters priKeyParams = new ECPrivateKeyParameters(new BigInteger(1, privateKey), ecDomainParams);

            var sm2 = new SM2Engine(new SM3Digest());
            sm2.Init(false, priKeyParams);
            return sm2.ProcessBlock(sourceData, 0, sourceData.Length);
        }
        #endregion

        #region SM2 签名
        /// <summary>
        /// SM2加签(输入输出均为字符串,标准C1C2C3模式)
        /// </summary>
        /// <param name="sourceDataUtf8">源数据(UTF8字符串)</param>
        /// <param name="privateKeyHex">私钥(16进制字符串)</param>
        /// <param name="userIdUtf8">用户标识(UTF8字符串,可选)</param>
        /// <returns>签名结果(16进制字符串)</returns>
        public static string Sm2Sign(string sourceDataUtf8, string privateKeyHex, string userIdUtf8 = null)
        {
            byte[] sourceBytes = Encoding.UTF8.GetBytes(sourceDataUtf8);
            byte[] priBytes = Decode(privateKeyHex);
            byte[] userIdBytes = userIdUtf8 == null ? null : Encoding.UTF8.GetBytes(userIdUtf8);

            byte[] signBytes = Sign(sourceBytes, priBytes, userIdBytes);
            // 签名结果转十六进制字符串
            return Hex.ToHexString(signBytes).ToUpper();
        }

        /// <summary>
        /// SM2加签(内部字节数组版)
        /// </summary>
        private static byte[] Sign(byte[] sourceData, byte[] privateKey, byte[] userId = null)
        {
            var curveParams = GMNamedCurves.GetByName("SM2P256V1");
            var ecDomainParams = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H);
            ECPrivateKeyParameters priKeyParams = new ECPrivateKeyParameters(new BigInteger(1, privateKey), ecDomainParams);

            var sm2 = new SM2Signer(new SM3Digest());
            ICipherParameters cp = userId != null
                ? new ParametersWithID(new ParametersWithRandom(priKeyParams), userId)
                : new ParametersWithRandom(priKeyParams);

            sm2.Init(true, cp);
            sm2.BlockUpdate(sourceData, 0, sourceData.Length);
            return sm2.GenerateSignature();
        }
        #endregion

        #region SM2 验签
        /// <summary>
        /// SM2验签(输入均为字符串,标准C1C2C3模式)
        /// </summary>
        /// <param name="sourceDataUtf8">源数据(UTF8字符串)</param>
        /// <param name="publicKeyHex">公钥(16进制字符串)</param>
        /// <param name="signDataHex">签名数据(16进制字符串)</param>
        /// <param name="userIdUtf8">用户标识(UTF8字符串,可选)</param>
        /// <returns>验签结果(true/false)</returns>
        public static bool Sm2VerifySign(string sourceDataUtf8, string publicKeyHex, string signDataHex, string userIdUtf8 = null)
        {
            byte[] sourceBytes = Encoding.UTF8.GetBytes(sourceDataUtf8);
            byte[] pubBytes = Decode(publicKeyHex);
            byte[] signBytes = Decode(signDataHex);
            byte[] userIdBytes = userIdUtf8 == null ? null : Encoding.UTF8.GetBytes(userIdUtf8);

            return VerifySign(sourceBytes, pubBytes, signBytes, userIdBytes);
        }

        /// <summary>
        /// SM2验签(内部字节数组版)
        /// </summary>
        private static bool VerifySign(byte[] sourceData, byte[] publicKey, byte[] signData, byte[] userId = null)
        {
            var curveParams = GMNamedCurves.GetByName("SM2P256V1");
            var ecDomainParams = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H);
            ECPublicKeyParameters pubKeyParams = new ECPublicKeyParameters(curveParams.Curve.DecodePoint(publicKey), ecDomainParams);

            var sm2 = new SM2Signer(new SM3Digest());
            ICipherParameters cp = userId != null
                ? new ParametersWithID(pubKeyParams, userId)
                : pubKeyParams;

            sm2.Init(false, cp);
            sm2.BlockUpdate(sourceData, 0, sourceData.Length);
            return sm2.VerifySignature(signData);
        }
        #endregion

        #region SM3 哈希算法
        /// <summary>
        /// SM3 哈希计算(字节数组输入,返回字节数组结果)
        /// </summary>
        /// <param name="sourceData">源数据字节数组</param>
        /// <returns>SM3 哈希结果(32字节)</returns>
        public static byte[] Sm3Hash(byte[] sourceData)
        {
            if (sourceData == null || sourceData.Length == 0)
                return Array.Empty<byte>();

            // 初始化 SM3 摘要算法
            SM3Digest sm3Digest = new SM3Digest();
            // 写入数据
            sm3Digest.BlockUpdate(sourceData, 0, sourceData.Length);
            // 计算哈希
            byte[] result = new byte[sm3Digest.GetDigestSize()];
            sm3Digest.DoFinal(result, 0);
            return result;
        }

        /// <summary>
        /// SM3 哈希计算(UTF8 字符串输入,返回十六进制结果)
        /// </summary>
        /// <param name="sourceDataUtf8">源数据(UTF8 字符串)</param>
        /// <returns>SM3 哈希结果(64位十六进制字符串)</returns>
        public static string Sm3Hash(string sourceDataUtf8)
        {
            if (string.IsNullOrEmpty(sourceDataUtf8))
                return string.Empty;

            byte[] sourceBytes = Encoding.UTF8.GetBytes(sourceDataUtf8);
            byte[] hashBytes = Sm3Hash(sourceBytes);
            // 转十六进制字符串(大写)
            return Hex.ToHexString(hashBytes).ToUpper();
        }

        #endregion

        #region SM4 加密解密
        /// <summary>
        /// 随机生成SM4密钥(16位UTF8字符串)
        /// </summary>
        /// <returns>SM4密钥(16位字符串)</returns>
        public static string GenerateSm4Key()
        {
            return Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 16);
        }

        /// <summary>
        /// SM4加密(输入输出均为字符串,SM4/ECB/PKCS5Padding)
        /// </summary>
        /// <param name="plaintextUtf8">明文(UTF8字符串)</param>
        /// <param name="sm4Key">SM4密钥(16位字符串)</param>
        /// <returns>加密结果(Base64字符串)</returns>
        public static string Sm4Encrypt(string plaintextUtf8, string sm4Key)
        {
            byte[] sourceData = Encoding.UTF8.GetBytes(plaintextUtf8);
            byte[] keyBytes = Encoding.UTF8.GetBytes(sm4Key);

            KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
            IBufferedCipher inCipher = CipherUtilities.GetCipher("SM4/ECB/PKCS5Padding");
            inCipher.Init(true, key);

            byte[] cipherBytes = inCipher.DoFinal(sourceData);
            return Convert.ToBase64String(cipherBytes);
        }

        /// <summary>
        /// SM4解密(输入输出均为字符串,SM4/ECB/PKCS5Padding)
        /// </summary>
        /// <param name="cipherBase64">密文(Base64字符串)</param>
        /// <param name="sm4Key">SM4密钥(16位字符串)</param>
        /// <returns>解密结果(UTF8字符串)</returns>
        public static string Sm4Decrypt(string cipherBase64, string sm4Key)
        {
            byte[] sourceData = Convert.FromBase64String(cipherBase64);
            byte[] keyBytes = Encoding.UTF8.GetBytes(sm4Key);

            KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
            IBufferedCipher outCipher = CipherUtilities.GetCipher("SM4/ECB/PKCS5Padding");
            outCipher.Init(false, key);

            byte[] plainBytes = outCipher.DoFinal(sourceData);
            return Encoding.UTF8.GetString(plainBytes);
        }
        #endregion

        #region 辅助方法
        /// <summary>
        /// 16进制/Base64字符串转字节数组(自动识别)
        /// </summary>
        /// <param name="str">16进制或Base64字符串</param>
        /// <returns>字节数组</returns>
        private static byte[] Decode(string str)
        {
            if (string.IsNullOrEmpty(str)) return Array.Empty<byte>();
            return Regex.IsMatch(str, "^[0-9a-fA-F]+$") ? Hex.Decode(str) : Convert.FromBase64String(str);
        }

        /// <summary>
        /// 字节数组转16进制字符串(大写)
        /// </summary>
        /// <param name="bytes">字节数组</param>
        /// <returns>16进制字符串</returns>
        private static string ToHexString(byte[] bytes)
        {
            return Hex.ToHexString(bytes).ToUpper();
        }

        /// <summary>
        /// UTF8字符串转16进制字符串
        /// </summary>
        /// <param name="utf8Str">UTF8字符串</param>
        /// <returns>16进制字符串</returns>
        private static string Utf8ToHexString(string utf8Str)
        {
            return ToHexString(Encoding.UTF8.GetBytes(utf8Str));
        }

        /// <summary>
        /// 16进制字符串转UTF8字符串
        /// </summary>
        /// <param name="hexStr">16进制字符串</param>
        /// <returns>UTF8字符串</returns>
        private static string HexStringToUtf8(string hexStr)
        {
            return Encoding.UTF8.GetString(Decode(hexStr));
        }
        #endregion
    }
}

SSCMS\Models\Config.cs中新增:

        public string Sm2PublicKey { get; set; }
        public string Sm2PrivateKey { get; set; }

SSCMS.Web\Controllers\Admin\Settings\Administrators\AdministratorsConfigController.cs中新增:
SubmitRequest类新增:

            public string Sm2PublicKey { get; set; } 
            public string Sm2PrivateKey { get; set; } 

SSCMS.Web\Pages\ss-admin\settings\administratorsConfig.cshtml中新增:
el-form中新增:

  <el-form-item label="SM2公钥" prop="sm2PublicKey" rules="{ required: true, message: '请输入SM2公钥'  }">
      <el-input v-model="form.sm2PublicKey"
        type="textarea"
        size="small"
        placeholder="请输入SM2公钥"
        :autosize="{ minRows: 4, maxRows: 10}">
      </el-input>
      <small class="tips">仅秘钥字符,并删除换行</small>
  </el-form-item>
  <el-form-item label="SM2私钥" prop="sm2PrivateKey" rules="{ required: true, message: '请输入SM2私钥'  }">
      <el-input v-model="form.sm2PrivateKey"
        type="textarea"
        size="small"
        placeholder="请输入SM2私钥"
        :autosize="{ minRows: 4, maxRows: 10}">
      </el-input>
      <small class="tips">仅秘钥字符,并删除换行</small>
  </el-form-item>

SSCMS.Web\Controllers\Admin\LoginController.Submit.cs的Submit方法修改:

                var pwd = request.Password;//从解密从前端获取的密码
                try
                {
                    pwd = SSCMS.Utils.SMCryptoUtil.Sm2Decrypt(request.Password, config.Sm2PrivateKey);
                }
                catch
                {
                    return this.Error("系统配置异常,请联系管理员!");
                }

                string userName;
                string errorMessage;
                (administrator, userName, errorMessage) = await _administratorRepository.ValidateAsync(request.Account, Utils.AuthUtils.Md5ByString(pwd), true);

用到的sm相关js:

sm2.js
sm3.js
sm4.js

Activity

  1. changed the title [-]提个建议,有关登录:[/-] [+]提个建议,有关登录安全:[/+] on Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions