S3329 "Use a dynamically-generated, random IV." FP

This rule is flagging correct and necessary usage of a non-random IV. This is an example made from the one in the MSFT docs.

using System;
using System.IO;
using System.Security.Cryptography;

namespace RijndaelManaged_Example
{
    class RijndaelExample
    {
        public static void Main()
        {
            try
            {

                string original = "Here is some data to encrypt!";
                byte[] encrypted;
                byte[] key;
                byte[] iv;
                // Create a new instance of the Rijndael
                // class.  This generates a new key and initialization
                // vector (IV).
                using (Rijndael encryptingRijndael = Rijndael.Create())
                {
                    key = encryptingRijndael.Key;
                    iv = encryptingRijndael.IV;
                    encrypted = EncryptStringToBytes(original, key, iv);
                }

                using (Rijndael decryptingRijndael = Rijndael.Create())
                {
                    string sharedKeyNotIv = DecryptStringFromBytes(encrypted, key, decryptingRijndael.IV);
                    string sharedKeyAndIv = DecryptStringFromBytes(encrypted, key, iv);

                    Console.WriteLine("Original:    \t\t\t\t\t{0}", original);
                    Console.WriteLine("Round Trip (sharedKeyNotIv):\t{0}", sharedKeyNotIv);
                    Console.WriteLine("Round Trip (sharedKeyAndIv):\t{0}", sharedKeyAndIv);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error: {0}", e.Message);
            }
        }

        static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;
            // Create an Rijndael object
            // with the specified key and IV.
            using (Rijndael rijAlg = Rijndael.Create())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;

                // Create an encryptor to perform the stream transform.
                ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {

                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            return encrypted;
        }

        static string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an Rijndael object
            // with the specified key and IV.
            using (Rijndael rijAlg = Rijndael.Create())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;

                ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }

            return plaintext;
        }
    }
}

output

Original:    					Here is some data to encrypt!
Round Trip (sharedKeyNotIv):	���_S|�
[�bn��a to encrypt!
Round Trip (sharedKeyAndIv):	Here is some data to encrypt!

The IV needs to be reused in order to decrypt the whole value correctly.

Hello Andrew,

may I ask what version you are using? Thanks!

version of what? The above is coming from SonarCloud if that helps

Thanks, that helps indeed. SonarCloud automatically uses the latest version (in comparison to SonarQube that could be any version). Unfortunately no IV issue is raised for me if I scan your example code. Is your project public by any chance, so that I could have a look there if something is different?

The example was to point out that the IV cannot be dynamic and different each time. Its not the code thats getting flagged. That code getting flagged looks like this…

byte[] keyBytes = new byte[16];
byte[] ivBytes = new byte[16];
//assign the bytes
rijndaelCipher.Key = keyBytes;
rijndaelCipher.IV = ivBytes;
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();

//or it can be one of the other overloads
byte[] keyBytes = new byte[16];
byte[] ivBytes = new byte[16];
//assign the bytes
ICryptoTransform transform = rijndaelCipher.CreateEncryptor(keyBytes, ivBytes);

The rule description also had an example

Of course if you want to decrypt something that is using CBC you will have to use the same IV as for the encryption. The rule does not forbid that. As stated in its description, the rule is intended for the encryption process:

In encryption, when Cipher Block Chaining (CBC) is used, the Initialization Vector (IV) must be random and unpredictable. Otherwise, the encrypted value is vulnerable to crypto-analysis attacks such as the “Chosen-Plaintext Attack”.
An IV value should be associated to one, and only one encryption cycle, because the IV’s purpose is to ensure that the same plaintext encrypted twice will yield two different ciphertexts.

It is possible that it raises a false-positive for you but if I am not able to reproduce it there is nothing I can do about it. In the code you posted I do not see where a value is assigned to ivBytes and this is the relevant part.

I intentionally omitted the assignment of values to the byte[] variables, but they are not randomly generated for each encrypt/decrypt operation set.

Here’s what it looks like in sonarcloud (it will not go away either. I’ve resolved as FP like 5 times already and every commit brings it up again.

It does sound like a true-positive then. But it should not happen that you have to review it again after every commit. I will try to find someone that can take over from here since this is out of my expertise.