Skip to content

CWE-331: Insufficient Entropy - C#

Overview

Insufficient entropy in C# occurs when using System.Random or Guid.NewGuid() instead of System.Security.Cryptography.RandomNumberGenerator (or RNGCryptoServiceProvider in older .NET) for security-sensitive operations. Random is designed for non-cryptographic purposes and produces predictable sequences.

Primary Defence: Use RandomNumberGenerator.GetBytes() (.NET 6+) or RNGCryptoServiceProvider for all security-sensitive random value generation.

Common Vulnerable Patterns

Using Random with time-based seed for tokens

using System;

// VULNERABLE - Predictable token generation
public class InsecureTokenGenerator
{
    // VULNERABLE - Time-based seed
    public static string GenerateSessionToken()
    {
        var random = new Random((int)DateTime.Now.Ticks);
        return random.Next().ToString("X");
    }
}

Why this is vulnerable:

  • System.Random is deterministic and predictable.
  • Time-based seeds are guessable.

Using Random for encryption keys

using System;

// VULNERABLE - Using Random for encryption key
public static byte[] GenerateKey()
{
    var random = new Random();
    byte[] key = new byte[32];
    random.NextBytes(key);  // Predictable!
    return key;
}

Why this is vulnerable:

  • Keys generated from System.Random are predictable.
  • Observed outputs can reveal future values.

Using Random for API keys

using System;
using System.Text;

// VULNERABLE - API key generation
public static string GenerateApiKey()
{
    var random = new Random();
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 32; i++)
    {
        sb.Append(chars[random.Next(chars.Length)]);
    }
    return sb.ToString();
}

Why this is vulnerable:

  • System.Random is not a CSPRNG.
  • API keys need high entropy and unpredictability.

Using Guid.NewGuid() for security tokens

using System;

// VULNERABLE - Guid.NewGuid() may use weak RNG
public static string GenerateResetToken()
{
    return Guid.NewGuid().ToString();  // Not guaranteed to be cryptographically secure
}

Why this is vulnerable:

  • GUIDs are identifiers, not security tokens.
  • Entropy and RNG source are not guaranteed for secrets.

Insufficient entropy in PIN generation

using System;

// VULNERABLE - Insufficient entropy (only 4 digits)
public static int GeneratePin()
{
    var random = new Random();
    return random.Next(10000);  // 0000-9999
}

Why this is vulnerable:

  • 4 digits is only 10,000 possibilities.
  • System.Random makes the sequence guessable.

Secure Patterns

Using RandomNumberGenerator (.NET 6+)

using System;
using System.Security.Cryptography;
using System.Text;

public class SecureTokenGenerator
{
    // Generate cryptographically secure token (hex-encoded)
    public static string GenerateSecureToken(int bytes = 16)
    {
        byte[] token = RandomNumberGenerator.GetBytes(bytes);
        return Convert.ToHexString(token).ToLower();
    }

    // Generate base64-encoded token
    public static string GenerateBase64Token(int bytes = 32)
    {
        byte[] token = RandomNumberGenerator.GetBytes(bytes);
        return Convert.ToBase64String(token)
            .Replace('+', '-')
            .Replace('/', '_')
            .TrimEnd('=');  // URL-safe base64
    }

    // Generate session ID (256 bits)
    public static string GenerateSessionId()
    {
        return GenerateSecureToken(32);  // 64 hex chars
    }

    // Generate CSRF token (256 bits)
    public static string GenerateCsrfToken()
    {
        return GenerateBase64Token(32);
    }

    // Generate API key (384 bits)
    public static string GenerateApiKey()
    {
        return GenerateBase64Token(48);
    }

    // Generate password reset token (256 bits)
    public static string GeneratePasswordResetToken()
    {
        return GenerateSecureToken(32);
    }

    // Generate numeric PIN with sufficient entropy (min 6 digits)
    public static string GenerateSecurePin(int length = 6)
    {
        if (length < 6)
        {
            throw new ArgumentException("PIN must be at least 6 digits");
        }

        StringBuilder pin = new StringBuilder();
        byte[] randomBytes = RandomNumberGenerator.GetBytes(length);

        for (int i = 0; i < length; i++)
        {
            pin.Append(randomBytes[i] % 10);
        }

        return pin.ToString();
    }

    // Generate cryptographic key for AES-256
    public static byte[] GenerateEncryptionKey(int bits = 256)
    {
        return RandomNumberGenerator.GetBytes(bits / 8);
    }

    // Generate IV for AES encryption
    public static byte[] GenerateIV()
    {
        return RandomNumberGenerator.GetBytes(16);  // 128 bits
    }

    // Generate random integer in range [0, max)
    public static int GetSecureRandomInt(int max)
    {
        return RandomNumberGenerator.GetInt32(max);
    }
}

Why this works:

  • Uses OS CSPRNG via RandomNumberGenerator.
  • Thread-safe and not seed-predictable.
  • 128+ bits of entropy makes guessing infeasible.

Legacy .NET (Framework/Core < 6) with RNGCryptoServiceProvider

using System;
using System.Security.Cryptography;

public class LegacySecureTokenGenerator
{
    private static readonly RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

    public static string GenerateSecureToken(int bytes = 16)
    {
        byte[] token = new byte[bytes];
        rng.GetBytes(token);
        return BitConverter.ToString(token).Replace("-", "").ToLower();
    }

    public static byte[] GenerateRandomBytes(int length)
    {
        byte[] bytes = new byte[length];
        rng.GetBytes(bytes);
        return bytes;
    }
}

Why this works:

  • Legacy CSPRNG that still uses OS entropy sources.
  • Safe for older frameworks when RandomNumberGenerator is unavailable.

Complete encryption example with secure randomness

using System;
using System.Security.Cryptography;
using System.Text;

public class SecureEncryption
{
    // Generate AES-256 key using secure random
    public static byte[] GenerateKey()
    {
        return RandomNumberGenerator.GetBytes(32);  // 256 bits
    }

    // Encrypt with AES-GCM (authenticated encryption)
    // Returns: (nonce, tag, ciphertext)
    public static (byte[] nonce, byte[] tag, byte[] ciphertext) Encrypt(
        byte[] plaintext, 
        byte[] key)
    {
        // Generate secure nonce (96 bits for GCM)
        byte[] nonce = RandomNumberGenerator.GetBytes(12);

        // Create tag buffer
        byte[] tag = new byte[16];  // 128-bit authentication tag

        // Create cipher
        using var aes = new AesGcm(key);

        // Encrypt
        byte[] ciphertext = new byte[plaintext.Length];
        aes.Encrypt(nonce, plaintext, ciphertext, tag);

        return (nonce, tag, ciphertext);
    }

    // Decrypt AES-GCM ciphertext
    public static byte[] Decrypt(
        byte[] nonce, 
        byte[] tag, 
        byte[] ciphertext, 
        byte[] key)
    {
        using var aes = new AesGcm(key);
        byte[] plaintext = new byte[ciphertext.Length];
        aes.Decrypt(nonce, ciphertext, tag, plaintext);
        return plaintext;
    }
}
// Usage example
var key = SecureEncryption.GenerateKey();
var plaintext = Encoding.UTF8.GetBytes("sensitive data");

var (nonce, tag, ciphertext) = SecureEncryption.Encrypt(plaintext, key);
var decrypted = SecureEncryption.Decrypt(nonce, tag, ciphertext, key);

Console.WriteLine(Encoding.UTF8.GetString(decrypted));  // "sensitive data"

Why this works:

  • Keys and nonces come from a CSPRNG.
  • GCM provides confidentiality and integrity.
  • Nonce size and randomness avoid reuse risk.

Framework-Specific Guidance

ASP.NET Core - Session Management

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Security.Cryptography;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDistributedMemoryCache();
        services.AddSession(options =>
        {
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            options.Cookie.SameSite = SameSiteMode.Strict;
            options.IdleTimeout = TimeSpan.FromMinutes(30);
            // ASP.NET Core uses RandomNumberGenerator for session IDs automatically
        });

        services.AddAntiforgery(options =>
        {
            // Anti-forgery tokens use RandomNumberGenerator internally
            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        });
    }
}

Why this works:

  • ASP.NET Core uses CSPRNG for session IDs and antiforgery tokens.
  • Session/antiforgery config enforces secure defaults.

ASP.NET Core - Custom Token Service

using Microsoft.AspNetCore.DataProtection;
using System.Security.Cryptography;

public interface ITokenService
{
    string GenerateVerificationToken();
    string GenerateApiKey();
}

public class TokenService : ITokenService
{
    private readonly IDataProtectionProvider _dataProtection;

    public TokenService(IDataProtectionProvider dataProtection)
    {
        _dataProtection = dataProtection;
    }

    public string GenerateVerificationToken()
    {
        // Generate secure token
        byte[] token = RandomNumberGenerator.GetBytes(32);
        return Convert.ToBase64String(token);
    }

    public string GenerateApiKey()
    {
        // Generate and protect API key
        byte[] key = RandomNumberGenerator.GetBytes(48);
        string apiKey = Convert.ToBase64String(key);

        // Optionally encrypt with Data Protection API
        var protector = _dataProtection.CreateProtector("ApiKeys");
        return protector.Protect(apiKey);
    }
}

Why this works:

  • Tokens come from RandomNumberGenerator, not Random.
  • Data Protection can encrypt stored API keys.

Entity Framework Core - API Key Entity

using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;

public class ApiKey
{
    [Key]
    public int Id { get; set; }

    public string UserId { get; set; }

    // Store hashed key, not plaintext!
    public string KeyHash { get; set; }

    public DateTime CreatedAt { get; set; }

    public DateTime? ExpiresAt { get; set; }

    public static (string plainKey, string hashedKey) GenerateApiKey()
    {
        // Generate secure random key
        byte[] keyBytes = RandomNumberGenerator.GetBytes(48);
        string plainKey = Convert.ToBase64String(keyBytes);

        // Hash for storage (use SHA256 or better, bcrypt/Argon2)
        using var sha256 = SHA256.Create();
        byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plainKey));
        string hashedKey = Convert.ToBase64String(hashBytes);

        return (plainKey, hashedKey);
    }
}

Why this works:

  • Keys are generated with CSPRNG entropy.
  • Hashing avoids storing plaintext keys.

JWT Security

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

public class JwtTokenProvider
{
    // SECURE - Generate signing key with RandomNumberGenerator
    private static readonly byte[] _signingKey = RandomNumberGenerator.GetBytes(64);
    private static readonly SymmetricSecurityKey _securityKey = new SymmetricSecurityKey(_signingKey);

    public static string GenerateJwtToken(string userId)
    {
        // Generate unique JTI (JWT ID)
        string jti = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, userId),
                new Claim(JwtRegisteredClaimNames.Jti, jti)
            }),
            Expires = DateTime.UtcNow.AddHours(1),
            SigningCredentials = new SigningCredentials(
                _securityKey, 
                SecurityAlgorithms.HmacSha256Signature)
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

Why this works:

  • Signing keys and JTI use CSPRNG entropy.
  • Short expirations limit token exposure.

Additional Resources