@iEricLee 创建时间 : 2024-09-11 12:21:25 最后修改时间 : 2024-09-11 12:54:05
ES256(ECDSA SHA-256)通常用于数字签名,而不是传统的加密和解密(如AES或RSA那样)。ES256算法用于生成JWT(JSON Web Tokens)的签名,以确保令牌的真实性和完整性。
在.NET Core 中,使用 JWT (JSON Web Tokens) 并通过 ES256 (即 ECDSA with SHA-256) 算法进行签名是一种常见的做法,特别是在需要高安全性的场合。
首先,你需要在你的项目中安装必要的 NuGet 包。如果你使用的是命令行,可以运行以下命令:
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.IdentityModel.Tokens
在 .NET Core 中提供 ECDsa
类来生成ECDSA密钥对 ECDsaSecurityKey
。
为了演示 ES256 算法进行数字签名,需要先做一些准备工作,首先生成一个秘钥对。
ES256 是非对称算法,会同时生成私钥和公钥,可以分别保存到对应的文件中。
private static string privateKey ="private_ecdsa.key";
private static string publicKey = "public_ecdsa.pub";
public static void CreateEsKey()
{
var rootPath = Directory.GetCurrentDirectory();
var privateKeyPath = Path.Combine(rootPath, privateKey);
var publicKeyPath = Path.Combine(rootPath, publicKey);
if(!Path.Exists(privateKeyPath) || !Path.Exists(publicKeyPath))
{
//创建一个 ECDsa 实例用于签名,使用 P-256 曲线
using (var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256))
{
var keyPair = new ECDsaSecurityKey(ecdsa);
// 导出私钥 PKCS#8 格式,.NET Core不直接支持导出为PEM
byte[] privateKeyBytes = ecdsa.ExportPkcs8PrivateKey();
File.WriteAllBytes(privateKeyPath, privateKeyBytes);
// 导出公钥 X.509格式,同样支持PEM
byte[] publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
File.WriteAllBytes(publicKeyPath, publicKeyBytes);
// 如果需要PEM格式,可以使用 BouncyCastle类库或自定义代码来转换
}
}
}
在.NET Core 中,ECDsa
类可能提供了 ImportPkcs8PrivateKey
方法来直接从PKCS#8
格式的私钥数据导入密钥。
private static ECDsaSecurityKey getPrivateKey()
{
var rootPath = Directory.GetCurrentDirectory();
var privateKeyPath = Path.Combine(rootPath, privateKey);
if(Path.Exists(privateKeyPath))
{
ECDsa ecdsa = ECDsa.Create();
var pkcs8PrivateKeyBytes = File.ReadAllBytes(privateKeyPath);
ecdsa.ImportPkcs8PrivateKey(pkcs8PrivateKeyBytes, out _);
var sk= new ECDsaSecurityKey(ecdsa);
return sk;
}
else
{
Console.WriteLine("私钥文件不存在");
return null;
}
}
在.NET Core 中,ECDsa
类同样也提供导入公钥的方法 ImportSubjectPublicKeyInfo()
。
private static ECDsaSecurityKey getPublicKey()
{
var rootPath = Directory.GetCurrentDirectory();
var publicKeyPath = Path.Combine(rootPath, publicKey);
if (Path.Exists(publicKeyPath))
{
ECDsa ecdsa = ECDsa.Create();
ecdsa.ImportSubjectPublicKeyInfo(File.ReadAllBytes(publicKeyPath), out _);
var sk = new ECDsaSecurityKey(ecdsa);
return sk;
}
else
{
Console.WriteLine("公钥文件不存在");
return null;
}
}
public static string GetJwtToken()
{
var claims = new List<Claim>() {
new Claim("ID",Guid.NewGuid().ToString()),
new Claim("Name","ZhiShiLe")
};
//获取私钥
var privateKey = getPrivateKey();
var jwt = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddSeconds(10),
signingCredentials: new SigningCredentials(privateKey, SecurityAlgorithms.EcdsaSha256));
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
校验Token的参数设置,将 IssuerSigningKey
属性设置为公钥。
public static bool VerifyJwtToken(string token, out ClaimsPrincipal principal)
{
principal = null;
//获取公钥
var publicKey = getPublicKey();
//校验token
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Issuer,
ValidAudience = Audience,
IssuerSigningKey = publicKey,
ClockSkew = TimeSpan.Zero//校验过期时间必须加此属性
};
bool success = false;
try
{
//校验并解析token,validatedToken是解密后的对象
principal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out SecurityToken validatedToken);
success = true;
}
catch (SecurityTokenExpiredException ex)
{
Console.WriteLine(ex.Message);
//表示过期
success = false;
}
catch (SecurityTokenException ex)
{
Console.WriteLine(ex.Message);
//表示token错误
success = false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
success = false;
}
return success;
}
至此,我们已经分别使用 HS256、RS256、ES256 三种不同的算法对令牌进行签名,在 .NET Core 提供 JwtSecurityToken
和 JwtSecurityTokenHandler
类实现签名和校验的流程,使用不同的签名算法只需正确设置对应的私钥和公钥,例如:SymmetricSecurityKey
、RsaSecurityKey
、ECDsaSecurityKey
。
在JWT的上下文中,使用私钥来签名JWT,并使用公钥来验证签名是否有效。不需要“解密”签名,因为签名本身不是加密的数据。
在实际应用中,应该安全地存储和访问密钥,而不是像示例中那样在运行时生成一个新的密钥。