Don't use the ECB encryption mode

Rule ND3132 Security

Matches methods that call the one-shot ECB APIs of SymmetricAlgorithm (Aes, TripleDES, DES, RC2): EncryptEcb(), DecryptEcb(), TryEncryptEcb(), TryDecryptEcb() and GetCiphertextLengthEcb().

In Electronic Codebook (ECB) mode every block is encrypted independently with the same key, hence two identical plaintext blocks produce two identical ciphertext blocks. An attacker can spot repeated structures, swap or replay blocks, and in many cases recover the content without the key. This is CWE-327 Use of a Broken or Risky Cryptographic Algorithm (OWASP Top 10 A02 Cryptographic Failures).

Assigning CipherMode.ECB to the Mode property cannot be detected because enum values are not visible in the compiled code.

Flagged Code
static byte[] Encrypt(byte[] plaintext, byte[] key) {
   using var aes = Aes.Create();
   aes.Key = key;
   return aes.EncryptEcb(plaintext, PaddingMode.PKCS7);   // flagged: ECB
}
Fixed Code
static byte[] Encrypt(byte[] plaintext, byte[] key, byte[] nonce, out byte[] tag) {
   using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
   var ciphertext = new byte[plaintext.Length];
   tag = new byte[AesGcm.TagByteSizes.MaxSize];
   aes.Encrypt(nonce, plaintext, ciphertext, tag);       // authenticated encryption
   return ciphertext;
}

How to fix

Use an authenticated encryption mode: AesGcm or AesCcm with a unique nonce per message. When authenticated encryption is not available, use EncryptCbc() with a random IV per message and protect the ciphertext with an HMAC (encrypt-then-MAC).

Never reuse a nonce or an IV with the same key.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Don't use the ECB encryption mode</Name>
// <Id>ND3132:DontUseEcbEncryptionMode</Id>
warnif count > 0

// Third-party members are part of the code model only when the application uses them.
// Since .NET 6 the one-shot APIs carry the mode in their name: EncryptEcb(), DecryptEcb(),
// TryEncryptEcb(), TryDecryptEcb(), GetCiphertextLengthEcb().
// Setting SymmetricAlgorithm.Mode = CipherMode.ECB is not detectable: enum values are not visible in IL.
let ecbMembers = ThirdParty.Methods.Where(m =>
   m.ParentType.FullName.StartsWith("System.Security.Cryptography.") &&
   m.SimpleNameEqualsAny("EncryptEcb", "DecryptEcb", "TryEncryptEcb", "TryDecryptEcb", "GetCiphertextLengthEcb"))
   .ToHashSetEx()
where ecbMembers.Any()

from m in Application.Methods.UsingAny(ecbMembers)
let ecbMembersUsed = m.MethodsCalled.Intersect(ecbMembers).ToArray()
select new {
  m,
  ecbMembersUsed,
  Debt = 20.ToMinutes().ToDebt(),
  Severity = Severity.High
}

//<Expl>
// The method *{0}* calls *{1}*, which encrypts or decrypts with the ECB mode.
// ECB encrypts identical plaintext blocks into identical ciphertext blocks,
// so patterns of the plaintext remain visible in the ciphertext.
//</Expl>

// <Desc>
// Matches methods that call the one-shot ECB APIs of *SymmetricAlgorithm*
// (*Aes*, *TripleDES*, *DES*, *RC2*): *EncryptEcb()*, *DecryptEcb()*,
// *TryEncryptEcb()*, *TryDecryptEcb()* and *GetCiphertextLengthEcb()*.
//
// In Electronic Codebook (ECB) mode every block is encrypted independently
// with the same key, hence two identical plaintext blocks produce two
// identical ciphertext blocks. An attacker can spot repeated structures,
// swap or replay blocks, and in many cases recover the content without
// the key. This is CWE-327 *Use of a Broken or Risky Cryptographic
// Algorithm* (OWASP Top 10 A02 Cryptographic Failures).
//
// Assigning *CipherMode.ECB* to the *Mode* property cannot be detected
// because enum values are not visible in the compiled code.
//
// **Flagged Code**
// ```csharp
// static byte[] Encrypt(byte[] plaintext, byte[] key) {
//    using var aes = Aes.Create();
//    aes.Key = key;
//    return aes.EncryptEcb(plaintext, PaddingMode.PKCS7);   // flagged: ECB
// }
// ```
//
// **Fixed Code**
// ```csharp
// static byte[] Encrypt(byte[] plaintext, byte[] key, byte[] nonce, out byte[] tag) {
//    using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
//    var ciphertext = new byte[plaintext.Length];
//    tag = new byte[AesGcm.TagByteSizes.MaxSize];
//    aes.Encrypt(nonce, plaintext, ciphertext, tag);       // authenticated encryption
//    return ciphertext;
// }
// ```
// </Desc>

// <HowToFix>
// Use an authenticated encryption mode: *AesGcm* or *AesCcm* with a unique
// nonce per message. When authenticated encryption is not available, use
// *EncryptCbc()* with a random IV per message and protect the ciphertext
// with an HMAC (encrypt-then-MAC).
//
// Never reuse a nonce or an IV with the same key.
// </HowToFix>

Security: all 39 items