Digitális aláírások használatára két lehetőségünk van .NET esetén. Használhatjuk az RSA alapú DSA (Digital Signature Algorithm) vagy az ECDSA algoritmust. Utóbbi elliptikus görbékkel dolgozik és a kisebb kulcsmérete igazán alkalmassá teszi digitális aláírásokhoz. Szimpla DSA esetén szintén legalább 2048 bites kulcsot kellene alkalmaznunk.
Digitális aláírások esetén ez a kulcsméret problémás tud lenni, ha kis méretű adatokat kell aláírnunk viszonylag sűrűn. A 2048 bites kulcs lényegében azt jelenti, hogy minden egyes aláírt üzenet 256 byte mérettel növekszik az aláírás miatt. Azonban ha az üzenet csupán 8 byte méretű, akkor a 2048 bites kulcsunkkal a tényleges átvitt byte-ok 3%-a az adat, a többi csak a biztonságot garantálja. Ez nem egy jó arány.
.NET esetén a DSA algoritmus a DSA osztály néven keresztül érhető el. Az algoritmus használatához itt is szükségünk lesz egy kulcspárra. Ezt kódból az alábbi metódus segítségével könnyen megkaphatjuk:
public static DSAParameters GenerateKeyPair(int keySize)
{
using (var dsa = DSA.Create())
{
dsa.KeySize = keySize;
return dsa.ExportParameters(true);
}
}
A kód először példányosítja az DSA algoritmust az DSA.Create() metódushívással, majd a keySize paraméter beállítja a titkosítás bitjeinek a számát. Ez legalább 2048 legyen, mivel kisebb bitszám esetén nem garantálható a biztonság. A kulcs maximális mérete 3072 bit lehet és 64 bites lépésekben változtatható. A legkisebb támogatott kulcsméret 512 bit.
A kód a kulcsot egy DSAParameters struktúrában adja vissza, ami lényegében a kulcspár adatait írja le. Az ExportParameters esetén a true paraméter arra utasítja a rendszert, hogy a kimeneti DSAParameters tartalmazza a privát kulcs paramétereit is.
A kapott kulcspárból itt is csak a publikust kell továbbadni. Ennek előállításában az alábbi metódus segít:
public static DSAParameters GetPublicKey(this DSAParameters parameters)
{
return new DSAParameters
{
Counter = parameters.Counter,
G = parameters.G,
P = parameters.P,
Q = parameters.Q,
Seed = parameters.Seed,
Y = parameters.Y,
};
}
Az RSA-val ellentétben itt a publikus kulcs tartalmaz több paramétert.
A kapott kulcsokat, hogy vissza is tudjunk állítani dolgokat, célszerű valamilyen formátumban a háttértárolón is tárolni. Erre a .NET a kezdetektől itt is az XML formátumot alkalmazza. Az alábbi extension metódusok az XML alapú beolvasást és kiírást könnyítik meg számunkra:
public static void ExportKeysInXMLFormat(this DSAParameters parameters,
TextWriter xmlOutput,
bool includePrivateKey)
{
using (var dsa = DSA.Create())
{
dsa.ImportParameters(parameters);
xmlOutput.Write(dsa.ToXmlString(includePrivateKey));
}
}
public static DSAParameters ReadKeysFromXmlFormat(TextReader xmlInput)
{
using (var dsa = DSA.Create())
{
dsa.FromXmlString(xmlInput.ReadToEnd());
return dsa.ExportParameters(true);
}
}
PEM formátumú kulcsok használatára is lehetőségünk van .NET Core 3 óta. A korábbi .NET változatok nem rendelkeznek beépítetten PEM támogatással. Az alábbi metódusok a PEM formátumú kulcsok importálását könnyítik meg számunkra:
public static DSAParameters ReadKeysFromPemFormat(string pem)
{
using (var dsa = DSA.Create())
{
dsa.ImportFromPem(pem);
return dsa.ExportParameters(false);
}
}
Eltérően az RSA algoritmustól, a fenti metódus csak a publikus kulcs beolvasására alkalmas. Ennek oka az, hogy az ImportFromPem metódus csak a publikus kulcsok PEM formátumát támogatja DSA esetén.
DSA aláírás
public static byte[] DSASignData(this Stream input,
DSAParameters parameters,
HashAlgorithmName hashAlgorithm)
{
using (var dsa = DSA.Create())
{
dsa.ImportParameters(parameters);
return dsa.SignData(input, hashAlgorithm);
}
}
A kulcs és a használni kívánt hash algoritmus meghatározása után az aláírást a SignData metódus végzi el. Ez sajnos aszinkron változatban nem érhető el. Visszatérési értéke egy byte[] tömb lesz, ami a bemeneti stream tartalmából képzett hash érték, titkosítva a kulcs segítségével.
public static bool DSAVerifyData(this Stream input,
DSAParameters parameters,
byte[] signature,
HashAlgorithmName hashAlgorithm)
{
using (var dsa = DSA.Create())
{
dsa.ImportParameters(parameters);
return dsa.VerifyData(input, signature, hashAlgorithm);
}
}
A visszaellenőrzés hasonlóan egyszerű. Meg kell adni a DSA kulcsot, az ellenőrizni kívánt aláírást és az aláírásban használt hash típusát, majd a VerifyData hívás egy bool értékben jelzi, hogy a bemeneti stream-re számított hash és az aláírásból kibontott egyezik-e.
ECDSA
ECDSA esetén szintén szükségünk van egy kulcspárra. Hasonlóan a korábbi metódusokhoz az alábbi metódust használhatjuk kulcs generálásra. A kulcs mérete vagy 256 vagy 384 bit lehet.
public static ECParameters GenerateKeyPair(int keySize)
{
using (var ecdsa = ECDsa.Create())
{
ecdsa.KeySize = keySize;
return ecdsa.ExportParameters(true);
}
}
A kulcsok visszaállíthatósága miatt itt is érdemes exportálni őket. Erre szintén az XML formátum preferált .NET esetén, így ehhez is elkészítettem a már megszokott kulcs XML beolvasó és exportáló metódusokat:
public static void ExportKeysInXMLFormat(this ECParameters parameters,
StreamWriter xmlOutput,
bool includePrivateKey)
{
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportParameters(parameters);
xmlOutput.Write(ecdsa.ToXmlString(includePrivateKey));
}
}
public static ECParameters ReadKeysFromXmlFormat(StreamReader xmlInput)
{
using (var ecdsa = ECDsa.Create())
{
ecdsa.FromXmlString(xmlInput.ReadToEnd());
return ecdsa.ExportParameters(true);
}
}
A DSA-val ellentétben OpenSSl által generált PEM formátum esetén a privát és publikus kulcs beolvasása is támogatott.
public static ECParameters ReadKeysFromPemFormat(string pem,
bool includesPrivate = false)
{
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportFromPem(pem);
return ecdsa.ExportParameters(includesPrivate);
}
}
ECDSA aláírás
Az aláírás és visszaellenőrzésének menete ugyanaz, mint DSA esetén, annyi különbséggel, hogy a DSA algoritmus helyett az ECDsa osztályt példányosítjuk és a kulcsot ECParameters osztályban adjuk meg.
public static byte[] EcdsaSignData(this Stream input,
ECParameters parameters,
HashAlgorithmName hashAlgorithm)
{
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportParameters(parameters);
return ecdsa.SignData(input, hashAlgorithm);
}
}
public static bool EcdsaVerifyData(this Stream input,
ECParameters parameters,
byte[] signature,
HashAlgorithmName hashAlgorithm)
{
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportParameters(parameters);
return ecdsa.VerifyData(input, signature, hashAlgorithm);
}
}