Encrypting & Decrypting a String in C# [duplicate]
Asked Answered
C

7

441

What is the most modern (best) way of satisfying the following in C#?

string encryptedString = SomeStaticClass.Encrypt(sourceString);

string decryptedString = SomeStaticClass.Decrypt(encryptedString);

BUT with a minimum of fuss involving salts, keys, mucking about with byte[], etc.

Been Googling and confused at what I'm finding (you can see the list of similar SO Qs to see this is a deceptive question to ask).

Charcuterie answered 16/4, 2012 at 3:2 Comment(5)
Crypto is not simple. Read blogs.msdn.com/b/ericlippert/archive/2011/09/27/…Ahmedahmedabad
All crypto operates on byte arrays. Use Encoding.UTF8.Ahmedahmedabad
Any answer to this question that just suggests some crypto algorithm and fails to discuss identity, key management, integrity ... is completely worthless.Boldface
@dtb: I think you're exaggerating the situation here, OP only needs a simple class which might take a chiper, salt, vs. and provides a symmetric encryption. He apparently doesn't care much about the purpose of the functionality.Felicidad
@Felicidad No, he isn't exaggerating the situation. Cryptography can be used to secure a system. Without describing the system, applied cryptography is useless (as is the accepted answer).Urbanus
M
933

UPDATE 23/Dec/2015: Since this answer seems to be getting a lot of upvotes, I've updated it to fix silly bugs and to generally improve the code based upon comments and feedback. See the end of the post for a list of specific improvements.

As other people have said, Cryptography is not simple so it's best to avoid "rolling your own" encryption algorithm.

You can, however, "roll your own" wrapper class around something like the built-in RijndaelManaged cryptography class.

Rijndael is the algorithmic name of the current Advanced Encryption Standard, so you're certainly using an algorithm that could be considered "best practice".

The RijndaelManaged class does indeed normally require you to "muck about" with byte arrays, salts, keys, initialization vectors etc. but this is precisely the kind of detail that can be somewhat abstracted away within your "wrapper" class.

The following class is one I wrote a while ago to perform exactly the kind of thing you're after, a simple single method call to allow some string-based plaintext to be encrypted with a string-based password, with the resulting encrypted string also being represented as a string. Of course, there's an equivalent method to decrypt the encrypted string with the same password.

Unlike the first version of this code, which used the exact same salt and IV values every time, this newer version will generate random salt and IV values each time. Since salt and IV must be the same between the encryption and decryption of a given string, the salt and IV is prepended to the cipher text upon encryption and extracted from it again in order to perform the decryption. The result of this is that encrypting the exact same plaintext with the exact same password gives and entirely different ciphertext result each time.

The "strength" of using this comes from using the RijndaelManaged class to perform the encryption for you, along with using the Rfc2898DeriveBytes function of the System.Security.Cryptography namespace which will generate your encryption key using a standard and secure algorithm (specifically, PBKDF2) based upon the string-based password you supply. (Note this is an improvement of the first version's use of the older PBKDF1 algorithm).

Finally, it's important to note that this is still unauthenticated encryption. Encryption alone provides only privacy (i.e. message is unknown to 3rd parties), whilst authenticated encryption aims to provide both privacy and authenticity (i.e. recipient knows message was sent by the sender).

Without knowing your exact requirements, it's difficult to say whether the code here is sufficiently secure for your needs, however, it has been produced to deliver a good balance between relative simplicity of implementation vs "quality". For example, if your "receiver" of an encrypted string is receiving the string directly from a trusted "sender", then authentication may not even be necessary.

If you require something more complex, and which offers authenticated encryption, check out this post for an implementation.

Here's the code:

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))
                            using (var streamReader = new StreamReader(cryptoStream, Encoding.UTF8))
                            {
                                return streamReader.ReadToEnd();
                            }
                        }
                    }
                }
            }
        }

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

The above class can be used quite simply with code similar to the following:

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();
        }
    }
}

(You can download a simple VS2013 sample solution (which includes a few unit tests) here).

UPDATE 23/Dec/2015: The list of specific improvements to the code are:

  • Fixed a silly bug where encoding was different between encrypting and decrypting. As the mechanism by which salt & IV values are generated has changed, encoding is no longer necessary.
  • Due to the salt/IV change, the previous code comment that incorrectly indicated that UTF8 encoding a 16 character string produces 32 bytes is no longer applicable (as encoding is no longer necessary).
  • Usage of the superseded PBKDF1 algorithm has been replaced with usage of the more modern PBKDF2 algorithm.
  • The password derivation is now properly salted whereas previously it wasn't salted at all (another silly bug squished).
Mccutchen answered 16/4, 2012 at 15:24 Comment(56)
If I convert to a long or an int instead of a string, how do I make sure that the encrypted number doesn't overflow?Dit
@IanWarburton I'm not too sure what you mean. You won't be able to convert the encrypted output to a number as the output is Base64 encoded (i.e. it'll look something like: iBqFa0LvBIsXiyMGf0oMKw==). You can convert a numeric input to it's string representation before encrypting, but you'll need to know if that input string is an int32 / int64 etc. after you decrypt as the encryption routine is only concerning itself with strings.Mccutchen
I mean I factor out creating a byte array and instead of base64 encoding it, I convert it to an int or a long.Dit
@IanWarburton I see. Well, you could do this, but you'll be severely limiting yourself in the amount of data that you can practically encrypt. The way this encryption is set up is that the output bytes are multiples of 16, so encrypting even 1 byte of input results in 16 bytes of encrypted output. That's an int128 right there already, and it'll only get bigger as your input bytes grow. You'd need to handle an integer that large (or larger) with something like the System.Numerics.BigInteger class, although I'm not sure this approach will be particularly scalable.Mccutchen
Sounds complicated. I've just changed all my numbers to strings. Which isn't all that bad because I'm not doing arithmetic with them.Dit
I did try converting a number to a string, running your code and then converting the byte array to an int and it did fit into it. Or would the int have just rolled over?Dit
@IanWarburton Depending upon how you've converted the byte array to an int, it may well have rolled over. You could try it using the BigInteger class (which will accept a byte array in the constuctor to easily create a BigInteger instance - i.e. BigInteger number = new BigInteger(bytes);) and see if you get the same result/number.Mccutchen
code review: PBKDF2 makes more senses since you are generating more than 160 bits of key material. However ur PBKDF1 isn't even salted contrary to statement above. Your comments about IV are incorrect, a 16 character UTF8 is 16 bytes, it works anyway because IV size is actually based on block size, not key size. Also IV is being ascii decoded instead of utf8 decoded in decrypt. Ur answer talks about abstracting away salts and IVs but really u just removed them -- not modern or secureCarincarina
I have checked it by using a test string for encryption and it is generating me following encrypted string: /n75iM/HSHhnb6WTsI+pfxIY4wvjhESUCFXo0ZfdQTs= Now this might be problematic when appended to a URL especially in Asp.Net MVC based URLs. Also it includes "+" character which also disturbs URL at the receiving end.Retinite
@FaisalMq - It's trivial to do System.Net.WebUtility.UrlEncode(output); on the output in order to pass it over a URL and then do System.Net.WebUtility.UrlDecode(output); on the other end before you decrypt the encrypted string.Mccutchen
This is unauthenticated encryption. An attacker can change the message without you being able to notice. He cannot learn the message but he can change it.Endocranium
@Endocranium Unless it is used where padding oracles apply (for instance in transport protocols), in that case an attacker may even reconstruct the plaintext message.Urbanus
how much will be the length of the encrypted text knowing the max length of password and the text? for example if the text can be max 32 character and password max 16 how much the length of the encrypted string can be?Inherent
I get the following error in line: using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)): Specified initialization vector (IV) does not match the block size for this algorithm.Sakhuja
PasswordDeriveBytes not IDisposableHypethral
@Hypethral - According to this MSDN page the PasswordDeriveBytes class inherits from the DeriveBytes class which provides a Dispose() method, and is therefore Disposable.Mccutchen
if you get ASCII bytes of 16 character ASCII string you get 16 bytes, instead of 32 - as claimed in your first comments, isn't it? (for the IV)Trypanosomiasis
I get the same error as SearchForKnowledge Specified initialization vector (IV) does not match the block size for this algorithm. it would be helpful if the example can be updated to include a working text/passphrase and show the output (encrypted string).Cima
@shev72 - Are you using the newer code that I've recently updated on this answer?Mccutchen
@Craig: yes just copy pasted /tested the code before making the comment, I have used Ghazal's answer below which is sufficient for my needs.Cima
@shev72 - Strange. I've just copy/pasted the code from the answer (not the separate downloadable version) into a brand new Visual Studio 2015 console application and it works fine for me. Note that SearchForKnowledge's error was with the very first version of the code as the line that he's getting the error on does not exist within the new version.Mccutchen
@CraigTP, ok not sure what's happenind, I am on VS 2013, but result should be the same... I actually never saw the old version of the code which dates back a long time.Cima
Just implemented this code, is it normal I Encrypt("12345") 6 times with the same key and they all return different strings ? EDIT : This can't be normal ... Using the key it should alway give the same exact encryption string.Firooc
EDIT 2 : Ok, I just found out it is normal and that I can't compare 2 encrypted key together, I need to decrypt both to check if they match. Great code right here, sadly I can't give more than a single upvote. (Comment can only be edited for 5 minutes .... Ok then ... )Firooc
It's me once again haha I was writing method descriptions and I figured I had no clue how this encryption is called ... Is it homemade ? Is it PBKDF2 ? Or maybe Rfc2898DeriveBytes ? For now I'll call it homemade encryption but I know this uses SHA1 and some other stuff too.Firooc
@Firooc Glad you're enjoying the code. Yes, it IS correct that the same plain text encrypted with the same key multiple times produces a different result as the encryption is salted to produce this result. Also, this ISN'T "homemade" encryption. My code merely acts as a "wrapper" for the real encryption routine, which is Rijndael. The line of code that starts using (var symmetricKey = new RijndaelManaged()) is where the built-in .NET managed version of Rijndael is used.Mccutchen
@Mccutchen Sweet ! I totally missed that part about Rijndael. Althought I never heard of Rijndael, I did heard about AES and I'm even more happy that I used your solution !Firooc
@Mccutchen Good explanation but 1000 iterations is very low. When the PBKDF2 standard was written in 2000, the recommended minimum number of iterations was 1000, but the parameter is intended to be increased over time as CPU speeds increase. As of 2005 a Kerberos standard recommended 4096 iterations, Apple iOS 3 used 2000, iOS 4 used 10000, while in 2011 LastPass used 5000 iterations for JavaScript clients and 100000 iterations for server-side hashing.Wraf
This code does not verify that the message has not been tampered with. Changing bits of the encrypted string will still yield a valid decryption, with parts of the plaintext result being corrupted. This should be combined with a hash to detect tampering - probably encrypt-then-mac, i.e. include the hash of the ciphertext.Clutch
Sorry this may be a silly question, would this work on two different computers. as in a client sends string to server, would the server be able to decrypt it?Ascarid
@XGlib Yes, this will work correctly between two computers so long as the code is the same (i..e you don't change the KeySize or DerivationIterations constants between the two computers). The salt and IV bytes are generated when the string is encrypted and effectively stored within the encrypted string output allowing the "recieving" computer to extract them and use the same salt and IV bytes for the decryption.Mccutchen
Any chance for a .net Core update, since RijndaelManaged() class is not avaliable in Core?Carolacarolan
I'm surprised nobody has mentioned how very slow this algorithm is. It uses 1000 iterations to derive the password, which would make sense if the goal was password hashing (as Ogglas points out, it would even be too low) to protect against brute-force attacks, but does it make sense when your ultimate goal is symmetrical encryption? When the encrypted string can be decrypted using a simple Decrypt function it doesn't make enough difference to an attacker, while making a big difference if/when you need to decrypt a lot of data. That said, the number of iterations can be lowered easily.Cordellcorder
One of the most comprehensive answers I have seen on stack over flow, just downloaded the example project. Good Stuff - Vote upCoaler
I get System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed. on return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); (line 83)Lustful
@Lustful That's strange. That error is usually caused when the final block of bytes isn't flushed to the cryptostream (See: simple-talk.com/blogs/oh-no-my-paddings-invalid), however, my code is explicitly calling the method to ensure that the final block is flushed (i.e. cryptoStream.FlushFinalBlock();). Are you running the provided VS2013 sample solution download or have you copied and pasted the code from the answer into a new project? Are you targeting the correct framework? (This code is designed to run against the full .NET framework v4.5)Mccutchen
@Mccutchen I've copied and pasted on a .net 4.6 asp.net project. I transmit the encrypted string in the URL as querystring param and then decrypt in the called page. I use URL encode&decode, tried also without, and by replacing '+' but I get always the same issue. But tested on the same page it works perfectly. I've finally used a very simple AES and CryptoStream to encrypt/decrypt as I don't need hi level of securityLustful
When running this code I get this error: System.FormatException: Invalid length for a Base-64 char array or string. On line... var cipherTextBytesEithSaltAndIV = Convert.FromBase64String(cipherText);Daryldaryle
Hello, I have a strange issue with this code. It works, and I can encrypt and decrypt strings. But sometimes I get an exception, telling me "Salt must be at least 8 bytes long". But why? I didnt changed anything in the code. It happens after a few days on the same machine. What could be the problem?Feeder
I am not allowed to answer the question, so I just comment here. As far as .NET Standard 2 goes, the block size must be 128. Here's how it changes your code: github.com/nopara73/DotNetEssentials/blob/master/…Synopsize
thank you so much for simple explanation. good and organized code.Misguided
Prior to your 23/Dec/2015 major update, you used tu89geji340t89u2 as you IV. After seeing this constant gets widespread use all over the Internet and even in some commercial games, I kindly ask you: A) Is your answer the primary source for this constant? B) If yes, why have you chosen this particular value? C) If not, where have you copied it from? Thanks!Shaniqua
@Shaniqua To be honest, it was so long ago I can't remember exactly, however, I believe that, yes, my original crypto code was the original source for the constant. However, there's no magical property to it, it's simply a random string.Mccutchen
Is Close for the streams really needed? They should be closed anyways, because they are inside a using? I would like to use this code or is there a "better" method now? (2019)Mazda
i got Exception when the KeySize was 100 . System.IndexOutOfRangeException: Index was outside the bounds of the array.Gottwald
perfect infiniteYeseniayeshiva
In my version translated to VB.NET, if I encrypt this "2q1Gd6BTKVZtSvf5LeEUm0MOSNl8esw2rcNRS91t" with the password "JMuO88Z0FQ5W" and then decrypt the result, it works. But if I try to decrypt it with the password with one more character added, for example "JMuO88Z0FQ5W4" (<-- added a 4 at the end) I get the exception "System.Security.Cryptography.CryptographicException: 'Filling is invalid and cannot be removed.'" (freely translated from Swedish) at a line saying (in your C# code) "var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);".Consubstantiation
With current analyzers, I got the warning "CA5379:Do Not Use Weak Key Derivation Function Algorithm" for new Rfc2898DeriveBytes - should this code be considered insecure? I am not using it for password hashing, rather encryption/decryptionBoldt
This doesn't work with .NET Core, There is an open discussion here : github.com/dotnet/runtime/issues/18706Cerys
Here is what seems to be a solution for .NET 5Lackey
This not longer seems to work in .NET 6, the decrypted data is truncated.Mariettemarigold
@Mariettemarigold Has this been mentioned? It saved me today: #69911584Negligible
As remarked by other users, there was a bug revealed when running this code on .NET 6.0, with truncated value after decrypting. I updated the code with fix provided by #69911584.Oarsman
I've adapted this code to use 128 bit block-size which is the AES standard (an the only way for it to work on dotnet core. Other than that everything worked as a charm!Hydrostatics
Adapted for NET 8 and Unity. Reduced memory consumption. Added possibility to encrypt byte arrays, not only strings. You can also attach custom data that will not be encrypted. gist.github.com/viruseg/d30cdca9df952c798e4da56e1b01428fImpellent
learn.microsoft.com/en-us/dotnet/api/…: The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.Barometrograph
W
142
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;
    }
}
Wally answered 15/12, 2014 at 12:44 Comment(16)
You probably shouldn't hard code the encryption key into the methods.Carotid
This is very useful and simple for the numerous cases where we do not need the complexities of salt. I just made the encryption key a parameter and was able to use the code successfully as is.Cima
@Carotid - where should he put the encryption key instead of hard coding it into the method?Pipette
@Pipette to make the method portable, you can always pass the key as a method parameter. For example: public static string Encrypt(string clearText, string encryptionKey) This way you can have unique keys for each method call.Elbertine
My Code Analyzer warned that variable cs is being disposed twice. We do not need redundant statements cs.Close() in both Encrypt and Decrypt methods, since both will be disposed once control exists the using block.Pitcher
I ran into a FormatException when doing a roundtrip with a too long key. When the encrypted string is over 256 characters it seems it just gets truncated. For some reason the roundtrip works in-memory but not when e.g. encrypted string is copied and pasted. It then also does not end with equal sign.Striation
Why do you replace spaces with +?Symmetrical
This has worked out of the box, THANKS!, voted up!Solent
Thanks Ivan MedvedevThurstan
Thanks Ivan! That was very nice, the only change that I made, was to put each method inside a try catch statement.Visible
How can I set the length of the encrypted string?Palaeozoic
Simple and to the point, thanks a lot.Garrison
Good one indeed. Just added the code return clearText.Replace(".", "").Replace("+", "").Replace("=", "").ToLower(); to cleanup the output and make it look better !Unemployable
Not working. I'm getting this error on decryption: 'Padding is invalid and cannot be removed.'Adcock
Fixed! need to use this constructor: learn.microsoft.com/en-us/dotnet/api/…Adcock
is it good practice to store the ciphertext produced by this helper encrypt method in oracle table varchar2 data type column and later decrpt when required?Marvelofperu
S
48

If you are targeting ASP.NET Core that does not support RijndaelManaged yet, you can use IDataProtectionProvider.

First, configure your application to use data protection:

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

Then you'll be able to inject IDataProtectionProvider instance and use it to encrypt/decrypt data:

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

See this article for more details.

Sassy answered 21/3, 2016 at 17:41 Comment(4)
best one for me, I'm using asp.netcore 3.1, thanks.Slum
A quick note about the article you provided is that it states: "Encryption requires a key, which is created and managed by the data protection system. Keys are created with a default lifetime of 90 days, and stored in a suitable location according to the environment. Keys are temporary, so the data protection API is designed mainly for short term data protection scenarios". Just beware if you implement this method, that your keys expire after 90 days per default.Wallet
This should now be the accepted answer since the author mentions/is asking for the " most modern (best) way" and "with a minimum of fuss involving salts, key" implementation.Romaic
Just wanted to mention that services.AddDataProtection() has to stand alone or has to be the last services.Add...() method if you concat them as it doesn't return an IServiceCollection.Cyclonite
Y
31

Try this class:

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

}

and use it like this:

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

// later
string actual=keys.DecryptString(encr);
Yulandayule answered 16/4, 2012 at 15:22 Comment(8)
This is not a bad answer because diversity is what may keep encryption algorithms strong. I don't think a warning on this particular approach robustness is needed since no encryption mechanism is perfect, after all.Shreeves
This answer is decent. TDES is used a lot to secure ATM transactions as they travel over the phone line. I +1'd it, I'm not sure why someone else had -1'd it.Aminaamine
Diversity is not what keeps encryption algorithms strong. Math does that :) DES (even triple DES) is an old algorithm that is no longer strong enough for most applications.Cobalt
This was exactly what i was looking for my application. Dont need much just a way to make sure a user cant choose the next integer in sequence. So instead of using a link that has 380 plugged in they get some random string so they can't use 381 instead.Ionone
3DES isn't optimal, but it isn't the biggest problem with this answer. Your don't have a MAC, enabling active attacks like padding oracles. Leaving the IV management to the caller is a bad idea as well, since they will almost certainly mess it up, typically by using a fixed IV instead of generating a different one for each message.Maurita
I tried this and the encrypt part worked fine. The decrypt however, threw an exception at "cs.Read(result, 0, result.Length);" (the entire result entries were 0) and the " return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(data))).TrimEnd('\0');" line threw "Array cannot be null. Parameter name: bytes". I'm guessing, is there a size limitation on the encrypted string?Carraway
This solution was based on .NET 2.0 and recently Microsoft has updated the cryptography namespace. I suspect my answer is obsolete by now.Yulandayule
@Darkloki, try the solution in https://mcmap.net/q/27038/-encrypt-and-decrypt-a-string-in-c-closedYulandayule
P
21

If you need to store a password in memory and would like to have it encrypted you should use SecureString:

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

For more general uses I would use a FIPS approved algorithm such as Advanced Encryption Standard, formerly known as Rijndael. See this page for an implementation example:

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

Piled answered 16/4, 2012 at 3:11 Comment(4)
SecureString doesnt seem to be a good fit I think but it is a good thing when you are working within a secure environment like apps for banks...Felicidad
It really depends on what you need to do. I was first introduced to it when we were required to encrypt in-memory credentials in applications for the government.Piled
They also provide AES (and others), besides Rijndael: learn.microsoft.com/en-us/dotnet/api/…Immediate
SecureString is deprecated MS recommends not using it github.com/dotnet/platform-compat/blob/master/docs/DE0001.mdKatar
A
14

You may be looking for the ProtectedData class, which encrypts data using the user's logon credentials.

Ahmedahmedabad answered 16/4, 2012 at 3:4 Comment(6)
I read MSDN documentation but it doesnt state that what would happen if we moved these encrypted data to some another machine with different credentials. So we would still be able decrypt it back?Felicidad
@Braveyard Just decrypt it when exporting and encrypt it again on the new machine.Capsulize
@RichardHein: I thought that too, but doesn't seem a wise advice. When I move the info, I don't like to do that in plain human readable format, otherwise what's the meaning of all these encryption things..Felicidad
@Braveyard Then decrypt on old machine - encrypt again using a shared key for transport, on old machine - decrypt on new machine with shared key - encrypt with new machine key?Capsulize
@RichardHein: If you want to move data across machines, ProtectedData is the wrong tool for the job. (Unless you're in a domain, so that they'd have the same users everywhere)Ahmedahmedabad
@Ahmedahmedabad : As a side note, even if in a domain, this does not neccessarily work. Sure doesn't in my domain, and if you're on Azure it might not even work on the same webrole. In my very recent experience, DataProtectionScope.CurrentUser can only be used reliably on the same (physical) machine.Iorgo
S
-2

The easiest way that I've seen to do encryption is through RSA

Check out the MSDN on it: http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx

It does involve using bytes, but when it comes down to it you kind of do want encryption and decryption to be tough to figure out otherwise it will be easy to hack.

Spies answered 16/4, 2012 at 3:6 Comment(1)
RSA is asymmetric, which is unlikely to be what he wants.Ahmedahmedabad

© 2022 - 2024 — McMap. All rights reserved.