Encryption and decryption on streams using AES CBC + HMAC SHA

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
7
down vote

favorite












The goal of the code below is to:



  • Support authenticated encryption using AES in the CBC mode and using HMAC SHA.

  • Support encryption and decryption of data of size larger than memory (potentially).

Please criticize :)



public class AesCbcThenHmacSha : IDisposable

private readonly Func<HMAC> hmacShaTransformFactory;
private readonly AesCryptoServiceProvider aesProvider;
private bool disposed;

public AesCbcThenHmacSha(byte aesKey, byte aesIv, byte macKey)

if (aesKey == null) throw new ArgumentNullException(nameof(aesKey));
if (aesIv == null) throw new ArgumentNullException(nameof(aesIv));
if (macKey == null) throw new ArgumentNullException(nameof(macKey));

// key size
// --------
// the key size property is set based on the provided
// key. the validation of length is also performed
// by the AesCryptoServiceProvider.

// block size
// ----------
// there is only one valid block size for AES and
// it does not need to be specified explicitly. it
// is 128 by default (and cannot be changed to any
// different value).

// initialization vector
// ---------------------
// the iv has to match the block size, so validation
// of the length is also performed beneath.

this.aesProvider = new AesCryptoServiceProvider

Key = aesKey,
IV = aesIv,
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC
;

this.hmacShaTransformFactory = CreateHmacShaTransform(macKey);


private static Func<HMAC> CreateHmacShaTransform(byte key)

var bits = key.Length * 8;

switch (bits)

case 256: return () => new HMACSHA256(key);
case 384: return () => new HMACSHA384(key);
case 512: return () => new HMACSHA512(key);

default:
throw new ArgumentException($"the bits-bit HMAC SHA is not supported");



public void Encrypt(Stream inputStream, Stream outputStream, out byte hmacSha)

if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));

var hmacShaTransform = this.hmacShaTransformFactory.Invoke();
var aesTransform = this.aesProvider.CreateEncryptor();

var hmacShaStream = new CryptoStream(outputStream, hmacShaTransform, CryptoStreamMode.Write);
var aesStream = new CryptoStream(hmacShaStream, aesTransform, CryptoStreamMode.Write);

inputStream.CopyTo(aesStream);
aesStream.FlushFinalBlock();

hmacSha = hmacShaTransform.Hash;
hmacShaTransform.Dispose();


public void Decrypt(Stream inputStream, Stream outputStream, byte expectedHmacSha)

if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));
if (expectedHmacSha == null) throw new ArgumentNullException(nameof(expectedHmacSha));

using (var hmacShaTransform = this.hmacShaTransformFactory.Invoke())
using (var hmacShaStream = new CryptoStream(Stream.Null, hmacShaTransform, CryptoStreamMode.Write))

inputStream.CopyTo(hmacShaStream);
hmacShaStream.FlushFinalBlock();

var hmacSha = hmacShaTransform.Hash;
if (!hmacSha.SequenceEqual(expectedHmacSha))

throw "message authentication code does not match the expected value".Security();



inputStream.Position = 0;

var aesCryptoTransform = this.aesProvider.CreateDecryptor();
var aesStream = new CryptoStream(outputStream, aesCryptoTransform, CryptoStreamMode.Write);
inputStream.CopyTo(aesStream);
aesStream.FlushFinalBlock();


public void Dispose()

if (!this.disposed)

this.aesProvider.Dispose();
this.disposed = true;









share|improve this question



























    up vote
    7
    down vote

    favorite












    The goal of the code below is to:



    • Support authenticated encryption using AES in the CBC mode and using HMAC SHA.

    • Support encryption and decryption of data of size larger than memory (potentially).

    Please criticize :)



    public class AesCbcThenHmacSha : IDisposable

    private readonly Func<HMAC> hmacShaTransformFactory;
    private readonly AesCryptoServiceProvider aesProvider;
    private bool disposed;

    public AesCbcThenHmacSha(byte aesKey, byte aesIv, byte macKey)

    if (aesKey == null) throw new ArgumentNullException(nameof(aesKey));
    if (aesIv == null) throw new ArgumentNullException(nameof(aesIv));
    if (macKey == null) throw new ArgumentNullException(nameof(macKey));

    // key size
    // --------
    // the key size property is set based on the provided
    // key. the validation of length is also performed
    // by the AesCryptoServiceProvider.

    // block size
    // ----------
    // there is only one valid block size for AES and
    // it does not need to be specified explicitly. it
    // is 128 by default (and cannot be changed to any
    // different value).

    // initialization vector
    // ---------------------
    // the iv has to match the block size, so validation
    // of the length is also performed beneath.

    this.aesProvider = new AesCryptoServiceProvider

    Key = aesKey,
    IV = aesIv,
    Padding = PaddingMode.PKCS7,
    Mode = CipherMode.CBC
    ;

    this.hmacShaTransformFactory = CreateHmacShaTransform(macKey);


    private static Func<HMAC> CreateHmacShaTransform(byte key)

    var bits = key.Length * 8;

    switch (bits)

    case 256: return () => new HMACSHA256(key);
    case 384: return () => new HMACSHA384(key);
    case 512: return () => new HMACSHA512(key);

    default:
    throw new ArgumentException($"the bits-bit HMAC SHA is not supported");



    public void Encrypt(Stream inputStream, Stream outputStream, out byte hmacSha)

    if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
    if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));

    var hmacShaTransform = this.hmacShaTransformFactory.Invoke();
    var aesTransform = this.aesProvider.CreateEncryptor();

    var hmacShaStream = new CryptoStream(outputStream, hmacShaTransform, CryptoStreamMode.Write);
    var aesStream = new CryptoStream(hmacShaStream, aesTransform, CryptoStreamMode.Write);

    inputStream.CopyTo(aesStream);
    aesStream.FlushFinalBlock();

    hmacSha = hmacShaTransform.Hash;
    hmacShaTransform.Dispose();


    public void Decrypt(Stream inputStream, Stream outputStream, byte expectedHmacSha)

    if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
    if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));
    if (expectedHmacSha == null) throw new ArgumentNullException(nameof(expectedHmacSha));

    using (var hmacShaTransform = this.hmacShaTransformFactory.Invoke())
    using (var hmacShaStream = new CryptoStream(Stream.Null, hmacShaTransform, CryptoStreamMode.Write))

    inputStream.CopyTo(hmacShaStream);
    hmacShaStream.FlushFinalBlock();

    var hmacSha = hmacShaTransform.Hash;
    if (!hmacSha.SequenceEqual(expectedHmacSha))

    throw "message authentication code does not match the expected value".Security();



    inputStream.Position = 0;

    var aesCryptoTransform = this.aesProvider.CreateDecryptor();
    var aesStream = new CryptoStream(outputStream, aesCryptoTransform, CryptoStreamMode.Write);
    inputStream.CopyTo(aesStream);
    aesStream.FlushFinalBlock();


    public void Dispose()

    if (!this.disposed)

    this.aesProvider.Dispose();
    this.disposed = true;









    share|improve this question























      up vote
      7
      down vote

      favorite









      up vote
      7
      down vote

      favorite











      The goal of the code below is to:



      • Support authenticated encryption using AES in the CBC mode and using HMAC SHA.

      • Support encryption and decryption of data of size larger than memory (potentially).

      Please criticize :)



      public class AesCbcThenHmacSha : IDisposable

      private readonly Func<HMAC> hmacShaTransformFactory;
      private readonly AesCryptoServiceProvider aesProvider;
      private bool disposed;

      public AesCbcThenHmacSha(byte aesKey, byte aesIv, byte macKey)

      if (aesKey == null) throw new ArgumentNullException(nameof(aesKey));
      if (aesIv == null) throw new ArgumentNullException(nameof(aesIv));
      if (macKey == null) throw new ArgumentNullException(nameof(macKey));

      // key size
      // --------
      // the key size property is set based on the provided
      // key. the validation of length is also performed
      // by the AesCryptoServiceProvider.

      // block size
      // ----------
      // there is only one valid block size for AES and
      // it does not need to be specified explicitly. it
      // is 128 by default (and cannot be changed to any
      // different value).

      // initialization vector
      // ---------------------
      // the iv has to match the block size, so validation
      // of the length is also performed beneath.

      this.aesProvider = new AesCryptoServiceProvider

      Key = aesKey,
      IV = aesIv,
      Padding = PaddingMode.PKCS7,
      Mode = CipherMode.CBC
      ;

      this.hmacShaTransformFactory = CreateHmacShaTransform(macKey);


      private static Func<HMAC> CreateHmacShaTransform(byte key)

      var bits = key.Length * 8;

      switch (bits)

      case 256: return () => new HMACSHA256(key);
      case 384: return () => new HMACSHA384(key);
      case 512: return () => new HMACSHA512(key);

      default:
      throw new ArgumentException($"the bits-bit HMAC SHA is not supported");



      public void Encrypt(Stream inputStream, Stream outputStream, out byte hmacSha)

      if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
      if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));

      var hmacShaTransform = this.hmacShaTransformFactory.Invoke();
      var aesTransform = this.aesProvider.CreateEncryptor();

      var hmacShaStream = new CryptoStream(outputStream, hmacShaTransform, CryptoStreamMode.Write);
      var aesStream = new CryptoStream(hmacShaStream, aesTransform, CryptoStreamMode.Write);

      inputStream.CopyTo(aesStream);
      aesStream.FlushFinalBlock();

      hmacSha = hmacShaTransform.Hash;
      hmacShaTransform.Dispose();


      public void Decrypt(Stream inputStream, Stream outputStream, byte expectedHmacSha)

      if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
      if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));
      if (expectedHmacSha == null) throw new ArgumentNullException(nameof(expectedHmacSha));

      using (var hmacShaTransform = this.hmacShaTransformFactory.Invoke())
      using (var hmacShaStream = new CryptoStream(Stream.Null, hmacShaTransform, CryptoStreamMode.Write))

      inputStream.CopyTo(hmacShaStream);
      hmacShaStream.FlushFinalBlock();

      var hmacSha = hmacShaTransform.Hash;
      if (!hmacSha.SequenceEqual(expectedHmacSha))

      throw "message authentication code does not match the expected value".Security();



      inputStream.Position = 0;

      var aesCryptoTransform = this.aesProvider.CreateDecryptor();
      var aesStream = new CryptoStream(outputStream, aesCryptoTransform, CryptoStreamMode.Write);
      inputStream.CopyTo(aesStream);
      aesStream.FlushFinalBlock();


      public void Dispose()

      if (!this.disposed)

      this.aesProvider.Dispose();
      this.disposed = true;









      share|improve this question













      The goal of the code below is to:



      • Support authenticated encryption using AES in the CBC mode and using HMAC SHA.

      • Support encryption and decryption of data of size larger than memory (potentially).

      Please criticize :)



      public class AesCbcThenHmacSha : IDisposable

      private readonly Func<HMAC> hmacShaTransformFactory;
      private readonly AesCryptoServiceProvider aesProvider;
      private bool disposed;

      public AesCbcThenHmacSha(byte aesKey, byte aesIv, byte macKey)

      if (aesKey == null) throw new ArgumentNullException(nameof(aesKey));
      if (aesIv == null) throw new ArgumentNullException(nameof(aesIv));
      if (macKey == null) throw new ArgumentNullException(nameof(macKey));

      // key size
      // --------
      // the key size property is set based on the provided
      // key. the validation of length is also performed
      // by the AesCryptoServiceProvider.

      // block size
      // ----------
      // there is only one valid block size for AES and
      // it does not need to be specified explicitly. it
      // is 128 by default (and cannot be changed to any
      // different value).

      // initialization vector
      // ---------------------
      // the iv has to match the block size, so validation
      // of the length is also performed beneath.

      this.aesProvider = new AesCryptoServiceProvider

      Key = aesKey,
      IV = aesIv,
      Padding = PaddingMode.PKCS7,
      Mode = CipherMode.CBC
      ;

      this.hmacShaTransformFactory = CreateHmacShaTransform(macKey);


      private static Func<HMAC> CreateHmacShaTransform(byte key)

      var bits = key.Length * 8;

      switch (bits)

      case 256: return () => new HMACSHA256(key);
      case 384: return () => new HMACSHA384(key);
      case 512: return () => new HMACSHA512(key);

      default:
      throw new ArgumentException($"the bits-bit HMAC SHA is not supported");



      public void Encrypt(Stream inputStream, Stream outputStream, out byte hmacSha)

      if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
      if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));

      var hmacShaTransform = this.hmacShaTransformFactory.Invoke();
      var aesTransform = this.aesProvider.CreateEncryptor();

      var hmacShaStream = new CryptoStream(outputStream, hmacShaTransform, CryptoStreamMode.Write);
      var aesStream = new CryptoStream(hmacShaStream, aesTransform, CryptoStreamMode.Write);

      inputStream.CopyTo(aesStream);
      aesStream.FlushFinalBlock();

      hmacSha = hmacShaTransform.Hash;
      hmacShaTransform.Dispose();


      public void Decrypt(Stream inputStream, Stream outputStream, byte expectedHmacSha)

      if (inputStream == null) throw new ArgumentNullException(nameof(inputStream));
      if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));
      if (expectedHmacSha == null) throw new ArgumentNullException(nameof(expectedHmacSha));

      using (var hmacShaTransform = this.hmacShaTransformFactory.Invoke())
      using (var hmacShaStream = new CryptoStream(Stream.Null, hmacShaTransform, CryptoStreamMode.Write))

      inputStream.CopyTo(hmacShaStream);
      hmacShaStream.FlushFinalBlock();

      var hmacSha = hmacShaTransform.Hash;
      if (!hmacSha.SequenceEqual(expectedHmacSha))

      throw "message authentication code does not match the expected value".Security();



      inputStream.Position = 0;

      var aesCryptoTransform = this.aesProvider.CreateDecryptor();
      var aesStream = new CryptoStream(outputStream, aesCryptoTransform, CryptoStreamMode.Write);
      inputStream.CopyTo(aesStream);
      aesStream.FlushFinalBlock();


      public void Dispose()

      if (!this.disposed)

      this.aesProvider.Dispose();
      this.disposed = true;











      share|improve this question












      share|improve this question




      share|improve this question








      edited Apr 14 at 9:00
























      asked Apr 13 at 22:18









      Patryk Golebiowski

      1362




      1362




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          2
          down vote













          My main concern with your design is that it encourages IV reuse - which, as far as I understand, pretty much defeats the point of having an IV in the first place. With IV reuse, the same plaintext always results in the same ciphertext - and plaintexts with the same prefix produce ciphertexts where the first block(s) are the same. That is leaking information that could be useful for an attacker.



          I would either move the IV parameter from the constructor to Encrypt, or let Encrypt generate an IV (in a cryptographically secure way) and prepend it to the ciphertext (Decrypt should then discard the first decrypted block).




          Some more notes:



          • Why is the MAC 'returned', instead of appended to the output stream?

          • Do you have a specific reason for combining AES-CBC with a hash-based MAC, instead of using an authenticated mode such as CCM or GCM? Cryptography is not my area of expertise, so I can't tell you which of these is 'best', but I'm just pointing out their existence in case you didn't know about them.

          • There are several things that are not being disposed, mostly in Encrypt but also in Decrypt. Disposal is also inconsistent, with a few using statements and a few 'loose' Dispose calls (without any try-finally constructs).

          • Related to the previous point, CryptoStream.Dispose takes ownership of the stream that's given to it, and prior to .NET 4.7.2 there's no way to override that behavior, so that may explain why you're not disposing those. If that's the case, I'd recommend documenting that - otherwise you won't be able to tell whether you simply forgot or you had a specific reason for not disposing those streams.

          • Personally I wouldn't make Security an extension method, as I don't consider strings to be 'inherently convertible' to exceptions.

          • It's good to see documentation, but why not use XML comments (<summary>...</summary>)? That allows your IDE to show relevant information whenever you're using this code, without you (or someone else) having to look at the implementation. You may also want to document which key sizes are valid and which padding mode is used, as well as which exception types a caller may expect.





          share|improve this answer





















            Your Answer




            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: false,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );








             

            draft saved


            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f192013%2fencryption-and-decryption-on-streams-using-aes-cbc-hmac-sha%23new-answer', 'question_page');

            );

            Post as a guest






























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            2
            down vote













            My main concern with your design is that it encourages IV reuse - which, as far as I understand, pretty much defeats the point of having an IV in the first place. With IV reuse, the same plaintext always results in the same ciphertext - and plaintexts with the same prefix produce ciphertexts where the first block(s) are the same. That is leaking information that could be useful for an attacker.



            I would either move the IV parameter from the constructor to Encrypt, or let Encrypt generate an IV (in a cryptographically secure way) and prepend it to the ciphertext (Decrypt should then discard the first decrypted block).




            Some more notes:



            • Why is the MAC 'returned', instead of appended to the output stream?

            • Do you have a specific reason for combining AES-CBC with a hash-based MAC, instead of using an authenticated mode such as CCM or GCM? Cryptography is not my area of expertise, so I can't tell you which of these is 'best', but I'm just pointing out their existence in case you didn't know about them.

            • There are several things that are not being disposed, mostly in Encrypt but also in Decrypt. Disposal is also inconsistent, with a few using statements and a few 'loose' Dispose calls (without any try-finally constructs).

            • Related to the previous point, CryptoStream.Dispose takes ownership of the stream that's given to it, and prior to .NET 4.7.2 there's no way to override that behavior, so that may explain why you're not disposing those. If that's the case, I'd recommend documenting that - otherwise you won't be able to tell whether you simply forgot or you had a specific reason for not disposing those streams.

            • Personally I wouldn't make Security an extension method, as I don't consider strings to be 'inherently convertible' to exceptions.

            • It's good to see documentation, but why not use XML comments (<summary>...</summary>)? That allows your IDE to show relevant information whenever you're using this code, without you (or someone else) having to look at the implementation. You may also want to document which key sizes are valid and which padding mode is used, as well as which exception types a caller may expect.





            share|improve this answer

























              up vote
              2
              down vote













              My main concern with your design is that it encourages IV reuse - which, as far as I understand, pretty much defeats the point of having an IV in the first place. With IV reuse, the same plaintext always results in the same ciphertext - and plaintexts with the same prefix produce ciphertexts where the first block(s) are the same. That is leaking information that could be useful for an attacker.



              I would either move the IV parameter from the constructor to Encrypt, or let Encrypt generate an IV (in a cryptographically secure way) and prepend it to the ciphertext (Decrypt should then discard the first decrypted block).




              Some more notes:



              • Why is the MAC 'returned', instead of appended to the output stream?

              • Do you have a specific reason for combining AES-CBC with a hash-based MAC, instead of using an authenticated mode such as CCM or GCM? Cryptography is not my area of expertise, so I can't tell you which of these is 'best', but I'm just pointing out their existence in case you didn't know about them.

              • There are several things that are not being disposed, mostly in Encrypt but also in Decrypt. Disposal is also inconsistent, with a few using statements and a few 'loose' Dispose calls (without any try-finally constructs).

              • Related to the previous point, CryptoStream.Dispose takes ownership of the stream that's given to it, and prior to .NET 4.7.2 there's no way to override that behavior, so that may explain why you're not disposing those. If that's the case, I'd recommend documenting that - otherwise you won't be able to tell whether you simply forgot or you had a specific reason for not disposing those streams.

              • Personally I wouldn't make Security an extension method, as I don't consider strings to be 'inherently convertible' to exceptions.

              • It's good to see documentation, but why not use XML comments (<summary>...</summary>)? That allows your IDE to show relevant information whenever you're using this code, without you (or someone else) having to look at the implementation. You may also want to document which key sizes are valid and which padding mode is used, as well as which exception types a caller may expect.





              share|improve this answer























                up vote
                2
                down vote










                up vote
                2
                down vote









                My main concern with your design is that it encourages IV reuse - which, as far as I understand, pretty much defeats the point of having an IV in the first place. With IV reuse, the same plaintext always results in the same ciphertext - and plaintexts with the same prefix produce ciphertexts where the first block(s) are the same. That is leaking information that could be useful for an attacker.



                I would either move the IV parameter from the constructor to Encrypt, or let Encrypt generate an IV (in a cryptographically secure way) and prepend it to the ciphertext (Decrypt should then discard the first decrypted block).




                Some more notes:



                • Why is the MAC 'returned', instead of appended to the output stream?

                • Do you have a specific reason for combining AES-CBC with a hash-based MAC, instead of using an authenticated mode such as CCM or GCM? Cryptography is not my area of expertise, so I can't tell you which of these is 'best', but I'm just pointing out their existence in case you didn't know about them.

                • There are several things that are not being disposed, mostly in Encrypt but also in Decrypt. Disposal is also inconsistent, with a few using statements and a few 'loose' Dispose calls (without any try-finally constructs).

                • Related to the previous point, CryptoStream.Dispose takes ownership of the stream that's given to it, and prior to .NET 4.7.2 there's no way to override that behavior, so that may explain why you're not disposing those. If that's the case, I'd recommend documenting that - otherwise you won't be able to tell whether you simply forgot or you had a specific reason for not disposing those streams.

                • Personally I wouldn't make Security an extension method, as I don't consider strings to be 'inherently convertible' to exceptions.

                • It's good to see documentation, but why not use XML comments (<summary>...</summary>)? That allows your IDE to show relevant information whenever you're using this code, without you (or someone else) having to look at the implementation. You may also want to document which key sizes are valid and which padding mode is used, as well as which exception types a caller may expect.





                share|improve this answer













                My main concern with your design is that it encourages IV reuse - which, as far as I understand, pretty much defeats the point of having an IV in the first place. With IV reuse, the same plaintext always results in the same ciphertext - and plaintexts with the same prefix produce ciphertexts where the first block(s) are the same. That is leaking information that could be useful for an attacker.



                I would either move the IV parameter from the constructor to Encrypt, or let Encrypt generate an IV (in a cryptographically secure way) and prepend it to the ciphertext (Decrypt should then discard the first decrypted block).




                Some more notes:



                • Why is the MAC 'returned', instead of appended to the output stream?

                • Do you have a specific reason for combining AES-CBC with a hash-based MAC, instead of using an authenticated mode such as CCM or GCM? Cryptography is not my area of expertise, so I can't tell you which of these is 'best', but I'm just pointing out their existence in case you didn't know about them.

                • There are several things that are not being disposed, mostly in Encrypt but also in Decrypt. Disposal is also inconsistent, with a few using statements and a few 'loose' Dispose calls (without any try-finally constructs).

                • Related to the previous point, CryptoStream.Dispose takes ownership of the stream that's given to it, and prior to .NET 4.7.2 there's no way to override that behavior, so that may explain why you're not disposing those. If that's the case, I'd recommend documenting that - otherwise you won't be able to tell whether you simply forgot or you had a specific reason for not disposing those streams.

                • Personally I wouldn't make Security an extension method, as I don't consider strings to be 'inherently convertible' to exceptions.

                • It's good to see documentation, but why not use XML comments (<summary>...</summary>)? That allows your IDE to show relevant information whenever you're using this code, without you (or someone else) having to look at the implementation. You may also want to document which key sizes are valid and which padding mode is used, as well as which exception types a caller may expect.






                share|improve this answer













                share|improve this answer



                share|improve this answer











                answered Apr 20 at 10:57









                Pieter Witvoet

                3,611621




                3,611621






















                     

                    draft saved


                    draft discarded


























                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f192013%2fencryption-and-decryption-on-streams-using-aes-cbc-hmac-sha%23new-answer', 'question_page');

                    );

                    Post as a guest













































































                    Popular posts from this blog

                    Greedy Best First Search implementation in Rust

                    Function to Return a JSON Like Objects Using VBA Collections and Arrays

                    C++11 CLH Lock Implementation