菜单栏

在 .NET Core 中生成和解析JWT令牌(4):使用 ES256(ECDSA-SHA256)算法签名

@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;
    }
}

使用私钥生成JWT令牌

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 提供 JwtSecurityTokenJwtSecurityTokenHandler 类实现签名和校验的流程,使用不同的签名算法只需正确设置对应的私钥和公钥,例如:SymmetricSecurityKeyRsaSecurityKeyECDsaSecurityKey

在JWT的上下文中,使用私钥来签名JWT,并使用公钥来验证签名是否有效。不需要“解密”签名,因为签名本身不是加密的数据。

在实际应用中,应该安全地存储和访问密钥,而不是像示例中那样在运行时生成一个新的密钥。


JWT
在本文档中
Copyright © 2024 知识乐 湘ICP备2022022129号-1