Files
MoShiro/backend/fleet/src/Fleet.Identity/SessionKeys.cs
T
2026-10-04 21:46:39 +08:00

56 lines
2.4 KiB
C#

using System.Security.Cryptography;
using System.Text;
namespace Fleet.Identity;
/// <summary>点对点会话派生出的数据面凭据。<c>SessionId</c> 8 字节,<c>Key</c> 32 字节(HMAC-SHA256 的密钥)。</summary>
public sealed record SessionCredentials(byte[] SessionId, byte[] Key)
{
public long SessionIdValue => BitConverter.ToInt64(SessionId, 0);
}
/// <summary>临时 ECDH 密钥:每个会话各生成一个,用完即弃,所以会话密钥具有前向保密。</summary>
public sealed class EphemeralKey : IDisposable
{
private readonly ECDiffieHellman _key = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
public byte[] PublicKeySpki => _key.ExportSubjectPublicKeyInfo();
/// <summary>
/// 与对端的临时公钥协商,再经 HKDF-SHA256 派生会话凭据。
/// 盐是服务端的随机数,info 绑定双方的临时公钥,防止凭据被挪用到别的握手上。
/// </summary>
public SessionCredentials Derive(ReadOnlySpan<byte> remotePublicKeySpki, ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> clientPublic, ReadOnlySpan<byte> serverPublic)
{
using ECDiffieHellman remote = ECDiffieHellman.Create();
remote.ImportSubjectPublicKeyInfo(remotePublicKeySpki, out _);
byte[] secret = _key.DeriveRawSecretAgreement(remote.PublicKey);
byte[] info = [.. Encoding.UTF8.GetBytes("fleet|v1|session|"), .. SHA256.HashData(clientPublic), .. SHA256.HashData(serverPublic)];
byte[] material = HKDF.DeriveKey(HashAlgorithmName.SHA256, secret, 40, nonce.ToArray(), info);
CryptographicOperations.ZeroMemory(secret);
return new SessionCredentials(material[..8], material[8..]);
}
public void Dispose() => _key.Dispose();
}
/// <summary>数据面的消息认证:HMAC-SHA256 截断到 16 字节。</summary>
public static class DataPlaneMac
{
public const int Bytes = 16;
public static void Compute(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> destination)
{
Span<byte> full = stackalloc byte[32];
HMACSHA256.HashData(key, data, full);
full[..Bytes].CopyTo(destination);
}
public static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, ReadOnlySpan<byte> mac)
{
Span<byte> expected = stackalloc byte[Bytes];
Compute(key, data, expected);
return CryptographicOperations.FixedTimeEquals(expected, mac);
}
}