Podstawowe trzy obszary zastosowań kryptografii, którymi są: wyliczanie haszy, kryptografia symetryczna oraz kryptografia asymetryczna zostały w bardzo dobry sposób zaimplementowane w dot Necie w namespace
System.Security.Cryptography. Podstawowe pojęcia związane z kryptografią to:
plaintext - wiadomość do zaszyfrowania,
szyfrowanie - proces obfuskacji danych,
deszyfracja - proces odtwarzania oryginalnych danych oraz
ciphertext - dane w postaci zaszyfrowanej. Różne techniki kryprograficzne zapewniają:
- poufność - dane nie mogą być przeczytane przez nieodpowiednie osoby
- integralność - możliwość weryfikacji, czy dane były modyfikowane
- uwierzytelnianie - potwierdzenie tożsamości użytkownika
- niezaprzeczalność - autor wiadomości nie może się jej wyprzeć
Hashing:
Funkcje haszujące konwertują wejście o zmiennej długości w wyjściowy ciąg bajtów o stałej długości tworząc skrót (nazywany także hashem) z wiadomości. Operacja taka jest nieodwracalna i charakteryzuje się tym, że małe zmiany ciągu wejściowego powodują duże zmiany w ciągu wyjściowym. Haszowanie stosuje się w celu zapewnienia integralności. W .NET mamy dostępne 6 klas dziedziczących po abstrakcyjnej
HashAlgorithm. Wybór powinien być kompromisem pomiędzy długością klucza (ataki typu bruteforce), a szybkością wykonywania.
Console.WriteLine("Please enter secret message:");
var msg = Console.ReadLine();
var msgBytes = Encoding.UTF8.GetBytes(msg);
HashAlgorithm[] algorithms = {
new MD5Cng(),
new MD5CryptoServiceProvider(),
new SHA1Managed(),
new SHA256Managed(),
new SHA384Managed(),
new SHA512Managed(),
new RIPEMD160Managed()
};
var watch = new Stopwatch();
foreach (var hashAlgorithm in algorithms)
{
watch.Start();
byte[] hash = null;
for (int i = 0; i < 100000; i++)
{
hash = hashAlgorithm.ComputeHash(msgBytes);
}
watch.Stop();
Console.WriteLine("{0} ({1} bit): {2} ms ",
hashAlgorithm.GetType().Name, 8*hash.Length, watch.ElapsedMilliseconds);
watch.Reset();
Console.WriteLine(BitConverter.ToString(hash).Replace("-", ""));
}
Uzyskane wyniki:
Please enter secret message:
Hello world!
MD5Cng (128 bit): 933 ms
86FB269D190D2C85F6E0468CECA42A20
MD5CryptoServiceProvider (128 bit): 812 ms
86FB269D190D2C85F6E0468CECA42A20
SHA1Managed (160 bit): 257 ms
D3486AE9136E7856BC42212385EA797094475802
SHA256Managed (256 bit): 375 ms
C0535E4BE2B79FFD93291305436BF889314E4A3FAEC05ECFFCBB7DF31AD9E51A
SHA384Managed (384 bit): 2696 ms
86255FA2C36E4B30969EAE17DC34C772CBEBDFC58B58403900BE87614EB1A34B8780263F255EB5E65CA9BBB8641CCCFE
SHA512Managed (512 bit): 3323 ms
F6CDE2A0F819314CDDE55FC227D8D7DAE3D28CC556222A0A8AD66D91CCAD4AAD6094F517A2182360C9AACF6A3DC323162CB6FD8CDFFEDB0FE038F55E85FFB5B6
RIPEMD160Managed (160 bit): 423 ms
7F772647D88750ADD82D8E1A7A3E5C0902A346A3
Symmetric Algorithms:
Algorytmy symetryczne, to takie, w których do procesu szyfrowania i deszyfrowania używany jest ten sam klucz. Klucz taki musi być przechowywany w odpowiednio bezpiecznych warunkach i mieć odpowiednią długość, żeby uniknąć ataków typu brute-force. Obecnie powszechnie używanym algorytmem jest AES (Rijndael z min.128 bitowymi kluczami). Im dłuższy klucz, tym dłuższe szyfrowanie. Jeżeli chcemy wygenerować sobie klucz, najlepiej skorzystać z klasy
RNGCryptoServiceProvider zapewniającej dużo lepszą "losowość", niż pozostałe narzędzia do generacji liczb pseudolosowych. Algorytm Rijndael często używany jest w trybie
CBC, gdzie wynik kolejnej rundy szyfrowania zależy od rundy poprzedniej. Stąd obok klucza musimy znać wektor wejściowy (przed pierwszą rundą). Wektor taki - wektor
IV (
Initialization Vector) podaje się obok klucza przy dystrybucji. Konstruktor klasy
RijndaelManaged wygeneruje taki wektor.
private static void Symmetric(byte[] msgBytes)
{
var key = new byte[256/8];
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
provider.GetBytes(key);
var cipher = CreateCipher();
cipher.Key = key;
var encryptor = cipher.CreateEncryptor();
var result = encryptor.TransformFinalBlock(msgBytes, 0, msgBytes.Length);
Console.WriteLine("Secret key: " + BytesToString(key));
Console.WriteLine("IV: " + BytesToString(cipher.IV));
Console.WriteLine("Encrypted message: " + BytesToString(result));
var cipher2 = CreateCipher();
cipher2.Key = key;
cipher2.IV = cipher.IV;
var decryptor = cipher.CreateDecryptor();
var originalMessage = decryptor.TransformFinalBlock(result, 0, result.Length);
var s1 = BytesToString(originalMessage);
var s2 = BytesToString(msgBytes);
Console.WriteLine("Are messages equal ? " + s1.Equals(s2));
}
private static RijndaelManaged CreateCipher()
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.KeySize = 256;
cipher.BlockSize = 256;
cipher.Padding = PaddingMode.ISO10126;
cipher.Mode = CipherMode.CBC;
return cipher;
}
Asymmetric algorithms:
Kryptografia asymetryczna nazywana także kryptografią klucza publicznego polega na tym, że mamy wygenerowaną parę kluczy: publiczny i prywatny. Klucze te uzupełniają się, to znaczy, że wiadomość zaszyfrowaną kluczem prywatnym można odszyfrować kluczem publicznym i na odwrót. Operacje takie są od 100 do 1000 razy wolniejsze od kryptografii symetrycznej, dlatego często algorytmów asymetrycznych używa się tylko do przesłania symetrycznego klucza sesji. Najpopularniejszy algorytm to
RSA. Para kluczy przechowywana jest w kontenerze, przeważnie w systemie operacyjnym, ale można ją też eksportować do pliku XML. Wczytujemy ją z Xml metodę FromString lub podając nazwę kontenera systemowego przy użyciu klasy
CspParameters.
CspParameters cp = new CspParameters();
cp.KeyContainerName = "SampleKeys";
cp.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider cipher = new RSACryptoServiceProvider(cp);
var bytes = cipher.Encrypt(msgBytes, true);
Console.WriteLine("Encrypted message: " + BytesToString(bytes));
Console.WriteLine(cipher.ToXmlString(includePrivateParameters:true));
var s1 = BytesToString(msgBytes);
var s2 = BytesToString(cipher.Decrypt(bytes, true));
Console.WriteLine("Are messages equal ? " + s1.Equals(s2));