title: Encode and decode a string, with password and salt, in dotnet core
body: <p>I've been working on a simple helper for dotnet core that should encode and decode a string, based on a user provided password (key) and a salt. </p>
  
  <p>In contradiction to the full .NET Framework, dotnet core currently does not have an implementation for the RijndaelManaged class. Pretty much every decent encrypt/decrypt sample for C# is based on this class, rendering it useless for dotnet core. There is a lot of debate on it on the <a href="https://github.com/dotnet/corefx/issues/12064" rel="nofollow noreferrer">CoreFX repo on GitHub</a>.</p>
  
  <p>However, with the combination of both the (old) <a href="https://msdn.microsoft.com/en-us/library/system.security.cryptography.aesmanaged(v=vs.110).aspx" rel="nofollow noreferrer"> MSDN article on AesManaged</a> and an <a href="https://www.troyhunt.com/owasp-top-10-for-net-developers-part-7/" rel="nofollow noreferrer">article by Troy Hunt</a> -  not to mention some refactoring - I was able to brew the following <strong>semi-working example</strong>: </p>
  
  <pre class="lang-csharp prettyprint-override"><code>internal class CryptographyHelpers
  {
      internal static string Decrypt(string password, string salt, string encrypted_value)
      {
          string decrypted;
  
          using (var aes = Aes.Create())
          {
              var keys = GetAesKeyAndIV(password, salt, aes);
              aes.Key = keys.Item1;
              aes.IV = keys.Item2;
  
              // create a decryptor to perform the stream transform.
              var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
  
              // create the streams used for encryption.
              var encrypted_bytes = ToByteArray(encrypted_value);
              using (var memory_stream = new MemoryStream(encrypted_bytes))
              {
                  using (var crypto_stream = new CryptoStream(memory_stream, decryptor, CryptoStreamMode.Read))
                  {
                      using (var reader = new StreamReader(crypto_stream))
                      {
                          decrypted = reader.ReadToEnd();
                      }
                  }
              }
          }
  
          return decrypted;
      }
  
      internal static string Encrypt(string password, string salt, string plain_text)
      {
          string encrypted;
  
          using (var aes = Aes.Create())
          {
              var keys = GetAesKeyAndIV(password, salt, aes);
              aes.Key = keys.Item1;
              aes.IV = keys.Item2;
  
              var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
  
              using (var memory_stream = new MemoryStream())
              {
                  using (var crypto_stream = new CryptoStream(memory_stream, encryptor, CryptoStreamMode.Write))
                  {
                      using (var writer = new StreamWriter(crypto_stream))
                      {
                          writer.Write(plain_text);
                      }
  
                      var encrypted_bytes = memory_stream.ToArray();
                      encrypted = ToString(encrypted_bytes);
                  }
              }
          }
  
          return encrypted;
      }
  
      private static byte[] ToByteArray(string input)
      {
          return Encoding.Unicode.GetBytes(input);
      }
  
      private static string ToString(byte[] input)
      {
          return Encoding.Unicode.GetString(input);
      }
  
      private static Tuple&lt;byte[], byte[]&gt; GetAesKeyAndIV(string password, string salt, SymmetricAlgorithm symmetricAlgorithm)
      {
          const int bits = 8;
          var key = new byte[16];
          var iv = new byte[16];
  
          var derive_bytes = new Rfc2898DeriveBytes(password, ToByteArray(salt));
          key = derive_bytes.GetBytes(symmetricAlgorithm.KeySize / bits);
          iv = derive_bytes.GetBytes(symmetricAlgorithm.BlockSize / bits);
  
          return new Tuple&lt;byte[], byte[]&gt;(key, iv);
      }
  
  }
  </code></pre>
  
  <p>I said semi-working example, which means it works... <strong>sometimes</strong>. And there lies the problem. When I run my test arbitrarily, it succeeds most of the time. Randomly, about one out of four times, it fails with the following message:</p>
  
  <blockquote>
    <p>Message: Expected string to be "lorem ipsum dom dolor sit amet", but " �R��k6��o��do�Lr sit amet" differs near ">�R" (index 0).</p>
  </blockquote>
  
  <p>It looks like a unicode problem, but changing the Encoding.Unicode in the helpers to Encoding.UTF8, leads to the error:</p>
  
  <blockquote>
    <p>System.Security.Cryptography.CryptographicException : The input data is not a complete block.</p>
  </blockquote>
  
  <p>Changing the encoding to Encoding.ASCII leads to the following error:</p>
  
  <blockquote>
    <p>System.Security.Cryptography.CryptographicException : Specified padding mode is not valid for this algorithm.</p>
  </blockquote>
  
  <p>The test I'm using is:</p>
  
  <pre class="lang-csharp prettyprint-override"><code>[Fact]
  public void Decrypt_Should_Decode_An_Encrypted_String()
  {
      // arrange
      var key = Guid.NewGuid().ToString();
      var salt = Guid.NewGuid().ToString();
      var original_value = "lorem ipsum dom dolor sit amet";
      var encrypted_value = CryptographyHelpers.Encrypt(key, salt, original_value);
  
      // act
      var target = CryptographyHelpers.Decrypt(key, salt, encrypted_value);
  
      // assert
      target.Should().NotBeNullOrEmpty();
      target.Should().Be(original_value);
  }
  </code></pre>
  
  <p>So, my primary question is: <strong>why does my imlementation (test) sometimes fail?</strong></p>
  
  <p>I am definitely not an expert on cryptography, but on a high level am aware of some basic concepts. Also, a similar question <a href="https://stackoverflow.com/a/38399658/1039247">"How to use Rijndael encryption with a .Net Core class library?"</a> was posted, but the only answer stays on a conceptual level, lacking concrete implementation.</p>
  
  <p>Any tips and arguments as to if this is a 'good enough' implementation would also be very much appreciated. </p>
  
  <p>Thanks!</p>