Szyfrowanie i deszyfrowanie łańcucha w C# [duplikat]

To pytanie ma już odpowiedź tutaj:

Jaki jest najnowocześniejszy (najlepszy) sposób spełniania poniższych warunków w C#?

string encryptedString = SomeStaticClass.Encrypt(sourceString);

string decryptedString = SomeStaticClass.Decrypt(encryptedString);

Ale z minimalnym zamieszaniem związanym z solami, kluczami, bałaganem z bajtem [] itp.

Wygooglowałem i pogubiłem się w tym, co znajduję (możesz zobaczyć lista podobnych so Qs, aby zobaczyć, że jest to zwodnicze pytanie do zadawania).

 268
Author: Will, 2012-04-16

7 answers

UPDATE 23/Dec / 2015: ponieważ ta odpowiedź wydaje się być coraz wiele upvotes, zaktualizowałem go naprawić głupie błędy i ogólnie poprawić kod na podstawie komentarzy i opinii. Lista konkretnych ulepszeń znajduje się na końcu postu.

Jak mówili inni, Kryptografia nie jest prosta, więc najlepiej unikać "kręcenia własnego" algorytmu szyfrowania.

Możesz jednak "zwijać własną" klasę owijania wokół czegoś takiego jak wbudowany RijndaelManaged zajęcia z kryptografii.

Rijndael to algorytmiczna nazwa obecnego standardu Advanced Encryption Standard, więc z pewnością używasz algorytmu, który można uznać za"najlepszą praktykę".

The RijndaelManaged Klasa rzeczywiście normalnie wymaga "obijania się" z tablicami bajtów, solami, kluczami, wektorami inicjalizacyjnymi itp. ale jest to dokładnie ten rodzaj szczegółów, który można nieco wyabstrahować w ramach klasy "owijarki".

Następująca Klasa to jeden I napisał jakiś czas temu, aby wykonać dokładnie to, czego szukasz, proste wywołanie pojedynczej metody, aby umożliwić szyfrowanie tekstu zwykłego za pomocą hasła opartego na łańcuchu, a wynikowy zaszyfrowany ciąg jest również reprezentowany jako ciąg. Oczywiście istnieje równoważna metoda odszyfrowania zaszyfrowanego ciągu za pomocą tego samego hasła.

W przeciwieństwie do pierwszej wersji tego kodu, która używała dokładnie tych samych wartości salt I IV za każdym razem, ta nowsza wersja wygeneruje losowy sól i kroplówki za każdym razem. Ponieważ salt I IV muszą być takie same między szyfrowaniem a deszyfrowaniem danego ciągu, salt I IV są dołączane do zaszyfrowanego tekstu po zaszyfrowaniu i wydobywane z niego ponownie w celu wykonania deszyfrowania. Rezultatem tego jest to, że zaszyfrowanie dokładnie tego samego zwykłego tekstu za pomocą tego samego hasła daje za każdym razem zupełnie inny wynik zaszyfrowania.

"Siła" używania tego pochodzi z używania RijndaelManaged Klasa do wykonania szyfrowanie dla ciebie, wraz z użyciem funkcji Rfc2898DeriveBytes przestrzeni nazw System.Security.Cryptography, która wygeneruje Twój klucz szyfrowania przy użyciu standardowego i bezpiecznego algorytmu (w szczególności PBKDF2) w oparciu o podane hasło oparte na łańcuchu znaków. (Zauważ, że jest to poprawa wykorzystania w pierwszej wersji starszego algorytmu PBKDF1).

Wreszcie, ważne jest, aby pamiętać, że to wciąż nieautoryzowane szyfrowanie. Samo szyfrowanie zapewnia tylko prywatność (tzn. wiadomość jest nieznana stronom trzecim), podczas gdy uwierzytelnione szyfrowanie ma na celu zapewnienie zarówno prywatności, jak i autentyczności (tzn. odbiorca wie, że wiadomość została wysłana przez nadawcę).

Nie znając Twoich dokładnych wymagań, trudno powiedzieć, czy kod tutaj jest wystarczająco bezpieczny dla Twoich potrzeb, jednak został wyprodukowany, aby zapewnić dobrą równowagę między względną prostotą implementacji a "jakością". Na przykład, jeśli "odbiorca" zaszyfrowanego ciągu jest otrzymanie łańcucha bezpośrednio od zaufanego "nadawcy", wtedy uwierzytelnienie może nawet nie być konieczne.

Jeśli potrzebujesz czegoś bardziej złożonego i oferującego uwierzytelnione szyfrowanie, sprawdź ten post dla implementacji.

Oto kod:

using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Linq;

namespace EncryptStringSample
{
    public static class StringCipher
    {
        // This constant is used to determine the keysize of the encryption algorithm in bits.
        // We divide this by 8 within the code below to get the equivalent number of bytes.
        private const int Keysize = 256;

        // This constant determines the number of iterations for the password bytes generation function.
        private const int DerivationIterations = 1000;

        public static string Encrypt(string plainText, string passPhrase)
        {
            // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
            // so that the same Salt and IV values can be used when decrypting.  
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
                                var cipherTextBytes = saltStringBytes;
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                // Fill the array with cryptographically secure random bytes.
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }
    }
}

Powyższa klasa może być użyta w prosty sposób z kodem podobnym do następującego:

using System;

namespace EncryptStringSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter a password to use:");
            string password = Console.ReadLine();
            Console.WriteLine("Please enter a string to encrypt:");
            string plaintext = Console.ReadLine();
            Console.WriteLine("");

            Console.WriteLine("Your encrypted string is:");
            string encryptedstring = StringCipher.Encrypt(plaintext, password);
            Console.WriteLine(encryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Your decrypted string is:");
            string decryptedstring = StringCipher.Decrypt(encryptedstring, password);
            Console.WriteLine(decryptedstring);
            Console.WriteLine("");

            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }
}

(możesz pobrać proste przykładowe rozwiązanie VS2013 (które zawiera kilka testów jednostkowych) tutaj ).

Aktualizacja 23 / Grudzień/2015: Lista konkretnych ulepszeń w kodzie to:

  • Naprawiono głupi błąd, w którym kodowanie różniło się między szyfrowaniem a odszyfrowuję. Ponieważ mechanizm generowania wartości salt I IV uległ zmianie, kodowanie nie jest już konieczne.
  • ze względu na zmianę salt / IV, poprzedni komentarz do kodu, który nieprawidłowo wskazywał, że kodowanie UTF8 16 znaków daje 32 bajty, nie ma już zastosowania (jako kodowanie nie jest już konieczne).
  • użycie zastąpionego algorytmu PBKDF1 zostało zastąpione użyciem bardziej nowoczesnego algorytmu PBKDF2.
  • wyprowadzenie hasła jest teraz odpowiednio solone, podczas gdy wcześniej nie było solone w ogóle (kolejny głupi błąd zgnieciony).
 611
Author: CraigTP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:25
using System.IO;
using System.Text;
using System.Security.Cryptography;

public static class EncryptionHelper
{
    public static string Encrypt(string clearText)
    {
        string EncryptionKey = "abc123";
        byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
        using (Aes encryptor = Aes.Create())
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(clearBytes, 0, clearBytes.Length);
                    cs.Close();
                }
                clearText = Convert.ToBase64String(ms.ToArray());
            }
        }
        return clearText;
    }
    public static string Decrypt(string cipherText)
    {
        string EncryptionKey = "abc123";
        cipherText = cipherText.Replace(" ", "+");
        byte[] cipherBytes = Convert.FromBase64String(cipherText);
        using (Aes encryptor = Aes.Create())
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(cipherBytes, 0, cipherBytes.Length);
                    cs.Close();
                }
                cipherText = Encoding.Unicode.GetString(ms.ToArray());
            }
        }
        return cipherText;
    }
}
 59
Author: A Ghazal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-15 12:44:56

Spróbuj tej klasy:

public class DataEncryptor
{
    TripleDESCryptoServiceProvider symm;

    #region Factory
    public DataEncryptor()
    {
        this.symm = new TripleDESCryptoServiceProvider();
        this.symm.Padding = PaddingMode.PKCS7;
    }
    public DataEncryptor(TripleDESCryptoServiceProvider keys)
    {
        this.symm = keys;
    }

    public DataEncryptor(byte[] key, byte[] iv)
    {
        this.symm = new TripleDESCryptoServiceProvider();
        this.symm.Padding = PaddingMode.PKCS7;
        this.symm.Key = key;
        this.symm.IV = iv;
    }

    #endregion

    #region Properties
    public TripleDESCryptoServiceProvider Algorithm
    {
        get { return symm; }
        set { symm = value; }
    }
    public byte[] Key
    {
        get { return symm.Key; }
        set { symm.Key = value; }
    }
    public byte[] IV
    {
        get { return symm.IV; }
        set { symm.IV = value; }
    }

    #endregion

    #region Crypto

    public byte[] Encrypt(byte[] data) { return Encrypt(data, data.Length); }
    public byte[] Encrypt(byte[] data, int length)
    {
        try
        {
            // Create a MemoryStream.
            var ms = new MemoryStream();

            // Create a CryptoStream using the MemoryStream 
            // and the passed key and initialization vector (IV).
            var cs = new CryptoStream(ms,
                symm.CreateEncryptor(symm.Key, symm.IV),
                CryptoStreamMode.Write);

            // Write the byte array to the crypto stream and flush it.
            cs.Write(data, 0, length);
            cs.FlushFinalBlock();

            // Get an array of bytes from the 
            // MemoryStream that holds the 
            // encrypted data.
            byte[] ret = ms.ToArray();

            // Close the streams.
            cs.Close();
            ms.Close();

            // Return the encrypted buffer.
            return ret;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine("A cryptographic error occured: {0}", ex.Message);
        }
        return null;
    }

    public string EncryptString(string text)
    {
        return Convert.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(text)));
    }

    public byte[] Decrypt(byte[] data) { return Decrypt(data, data.Length); }
    public byte[] Decrypt(byte[] data, int length)
    {
        try
        {
            // Create a new MemoryStream using the passed 
            // array of encrypted data.
            MemoryStream ms = new MemoryStream(data);

            // Create a CryptoStream using the MemoryStream 
            // and the passed key and initialization vector (IV).
            CryptoStream cs = new CryptoStream(ms,
                symm.CreateDecryptor(symm.Key, symm.IV),
                CryptoStreamMode.Read);

            // Create buffer to hold the decrypted data.
            byte[] result = new byte[length];

            // Read the decrypted data out of the crypto stream
            // and place it into the temporary buffer.
            cs.Read(result, 0, result.Length);
            return result;
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine("A cryptographic error occured: {0}", ex.Message);
        }
        return null;
    }

    public string DecryptString(string data)
    {
        return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(data))).TrimEnd('\0');
    }

    #endregion

}

I użyj go tak:

string message="A very secret message here.";
DataEncryptor keys=new DataEncryptor();
string encr=keys.EncryptString(message);

// later
string actual=keys.DecryptString(encr);
 23
Author: ja72,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 15:22:35

Jeśli chcesz zapisać hasło w pamięci i chcesz je zaszyfrować, użyj SecureString :

Http://msdn.microsoft.com/en-us/library/system.security.securestring.aspx

Do bardziej ogólnych zastosowań użyłbym algorytmu zatwierdzonego przez FIPS, takiego jak Advanced Encryption Standard, wcześniej znany jako Rijndael. Zobacz tę stronę dla realizacji przykład:

Http://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael.aspx

 17
Author: Ulises,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 03:21:58

Możesz szukać klasy ProtectedData, która szyfruje dane za pomocą poświadczeń logowania użytkownika.

 8
Author: SLaks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 03:04:29

Jeśli kierujesz ASP.NET rdzeń, który nie obsługuje RijndaelManaged, możesz użyć IDataProtectionProvider.

Najpierw skonfiguruj aplikację tak, aby korzystała z ochrony danych:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection();
    }
    // ...
}

Wtedy będziesz mógł wprowadzić instancję IDataProtectionProvider i użyć jej do szyfrowania / deszyfrowania danych:

public class MyService : IService
{
    private const string Purpose = "my protection purpose";
    private readonly IDataProtectionProvider _provider;

    public MyService(IDataProtectionProvider provider)
    {
        _provider = provider;
    }

    public string Encrypt(string plainText)
    {
        var protector = _provider.CreateProtector(Purpose);
        return protector.Protect(plainText);
    }

    public string Decrypt(string cipherText)
    {
        var protector = _provider.CreateProtector(Purpose);
        return protector.Unprotect(cipherText);
    }
}

Zobaczten artykuł aby uzyskać więcej szczegółów.

 8
Author: Sergey Kolodiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-21 17:41:03

Najprostszym sposobem na szyfrowanie jest RSA

Zobacz na MSDN: http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx

Wiąże się to z używaniem bajtów, ale jeśli chodzi o to, chcesz, aby szyfrowanie i deszyfrowanie było trudne do zrozumienia, w przeciwnym razie będzie łatwe do zhakowania.

 -1
Author: Mike Calvert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 03:06:06