1

I just know I will get referenced to a billion SO questions, BUT, everytime I try to use one for use with .net Maui and therefore using .Net 6 and to be .Net 7 I get a warning about OBSOLETE (and deprecated) functions. I can't tell you how many solutions I've tried. And of course I simply don't have the brains or time to dig deep.

So I'd be very grateful if someone could simply post some code that does the following:

(password could be ANY length, but I'll accept restrictions, but min 4 chars (like a pin). Yes I know it might not be very secure, but it's all I need for my use case.)

 public static string Encrypt(string plainText, string password)
 {

 }

 public static string Decrypt(string cipherText, string password)
 {
 }

I have seen that there are all kinds of extras like salts and pbkdf2 for hashing the password, but I'd really like all that gubbins to be hidden from me, so I don't have to bother. But please feel free to add explanations of why I should bother...

Many thanks and please be gentle. :)

gfmoore
  • 976
  • 18
  • 31
  • 1
    Your question title is not the place for tags. If this question is genuinely specifically limited to .NET 6+, you should tag .NET 6 rather than writing it in your question title. That seems unlikely though. If there are examples that don't work for you because of obsolete warnings, then you should mention those specific examples, lest someone suggest them again. – ProgrammingLlama Jun 24 '22 at 08:34
  • Yes, but first when I tried to tag [.Net 6] it didn't work and secondly you have now just opened up the question too widely. This question has to be focussed on .net 6 and above because people won't see the .net 6 tag!! I'd be grateful if you would make an exception. – gfmoore Jun 24 '22 at 08:42
  • 2
    There, I've added the .NET 6 tag. Please do show examples of what hasn't worked for you though. Generally PBKDF2 would be the correct way to do this. You don't necessarily need to set a salt, though it does protect against rainbow table attacks where the iterations is a commonly used number, etc. – ProgrammingLlama Jun 24 '22 at 08:44
  • Does this answer your question? [Is this the correct implementation of PBKDF2 in c#](https://stackoverflow.com/questions/47491327/is-this-the-correct-implementation-of-pbkdf2-in-c-sharp) – Charlieface Jun 24 '22 at 09:00
  • 1
    Yes, use PBKDF2 or maybe a secure version of Argon2, I'll bet you can find implementations even if the functionality isn't included in your specific runtime. Are there any other questions? If not I don't think there is much to answer here. – Maarten Bodewes Jun 24 '22 at 09:34
  • @Charlieface, thanks for the suggestion, but the RNGCryptoServiceProvider is now obsolete. As I said many of the solutions I've tried are now obsolete or deprecated. Also this is just a hash for the password and I need this and the upto date encryption/decryption. :) – gfmoore Jun 24 '22 at 10:44
  • Is [this answer](https://stackoverflow.com/a/10176980/380384) still valid? It was posted like a million years ago. – John Alexiou Jun 24 '22 at 13:28
  • https://learn.microsoft.com/en-gb/dotnet/fundamentals/syslib-diagnostics/syslib0021 no, obsolete :( – gfmoore Jun 24 '22 at 20:49

2 Answers2

1

So I seem to have figured out a solution using

namespace Census.Classes;
public static class EncryptionHelper
{
  public static string Encrypt(string plainText, byte[] encryptionKeyBytes)
  {
    byte[] iv = new byte[16];
    byte[] array;

    using (Aes aes = Aes.Create())
    {
      aes.Key = encryptionKeyBytes;
      aes.IV = iv;

      ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

      using (MemoryStream memoryStream = new())
      {
        using (CryptoStream cryptoStream = new((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
        {
          using (StreamWriter streamWriter = new((Stream)cryptoStream))
          {
            streamWriter.Write(plainText);
          }

          array = memoryStream.ToArray();
        }
      }
    }

    return Convert.ToBase64String(array);
  }

  public static string Decrypt(string cipherText, byte[] encryptionKeyBytes)
  {
    byte[] iv = new byte[16];
    byte[] buffer = Convert.FromBase64String(cipherText);

    using (Aes aes = Aes.Create())
    {
      aes.Key = encryptionKeyBytes;
      aes.IV = iv;
      ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

      using (MemoryStream memoryStream = new(buffer))
      {
        using (CryptoStream cryptoStream = new((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
        {
          using (StreamReader streamReader = new((Stream)cryptoStream))
          {
            return streamReader.ReadToEnd();
          }
        }
      }
    }
  }

  private static readonly byte[] Salt = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80 };
  public static byte[] CreateKey(string password, int keyBytes = 32)
  {
    const int Iterations = 300;
    var keyGenerator = new Rfc2898DeriveBytes(password, Salt, Iterations);
    return keyGenerator.GetBytes(keyBytes);
  }
}

I use it as follows:

    //get an encryption key from the password
    byte[] encryptionKeyBytes = EncryptionHelper.CreateKey(Password);
    mystring = EncryptionHelper.Encrypt(plaintext, encryptionKeyBytes);
    

I do it like this because I call it very many times.

I'd be grateful if someone more knowledgeable could cast their eye over it and see if there are any improvements that could be made, but at least it works and doesn't throw up any errors about obsolete or deprecated code - yay! :)

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
gfmoore
  • 976
  • 18
  • 31
1

If you want to store a secret on a particular computer only, and for a particular username then use the ProtectedData class provided in System.Security.Cryptography.

public static string Encrypt(string plainText, string password = null)
{
    var data = Encoding.Default.GetBytes(plainText);
    var pwd = !string.IsNullOrEmpty(password) ? Encoding.Default.GetBytes(password) : Array.Empty<byte>();
    var cipher = ProtectedData.Protect(data, pwd, DataProtectionScope.CurrentUser);
    return Convert.ToBase64String(cipher);
}

public static string Decrypt(string cipherText, string password = null)
{
    var cipher = Convert.FromBase64String(cipherText);
    var pwd = !string.IsNullOrEmpty(password) ? Encoding.Default.GetBytes(password) : Array.Empty<byte>();
    var data = ProtectedData.Unprotect(cipher, pwd, DataProtectionScope.CurrentUser);
    return Encoding.Default.GetString(data);
}

it used the windows cryptographic store to keep the secret. Optionally you can make it available to all users of the machine (provided they have the password) by changing the scope to DataProtectionScope.LocalMachine.

Note that the password is optional, as it uses your windows credentials for key generation.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • I am using .net Maui for developing apps on Android etc, but this is a useful bit of knowledge, thanks. – gfmoore Jun 24 '22 at 20:53