Repository navigation
提个建议,有关登录安全: #3906
Copy link
Copy link
Open
Description
目前登录时密码通过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:
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
[-]提个建议,有关登录:[/-][+]提个建议,有关登录安全:[/+]