Weak Crypto in AI-Generated Python

AI assistants reach for MD5, AES-ECB, and the random module because their training data did. BrassCoders flags every instance through Bandit's crypto rules, and the research measures how often it matters.

Copper Sun Brass Team · · 5 min read
securityai-code-reviewbenchmarks

BrassCoders flags weak cryptography deterministically through Bandit’s crypto rules, because AI assistants produce it at a measured rate. Pearce et al. found roughly 40% of GitHub Copilot programs vulnerable across the CWE Top 25, and the FormAI study labeled 51.24% of 112,000 generated C programs vulnerable by formal verification. Weak crypto is a pattern the model inherited from a decade of tutorials that reach for MD5 and AES-ECB because they are short and they run.

The dangerous part isn’t that the model knows a weak primitive. It’s that the weak primitive satisfies the prompt on the input the developer tested, and the security gap only opens against an attacker the prompt never described.

The Rate AI Ships Weak Crypto

BrassCoders treats weak crypto as a high-confidence AI failure mode because three independent studies measured it. Pearce et al. (Asleep at the Keyboard?, IEEE S&P 2022) generated 1,689 programs with GitHub Copilot across 89 scenarios drawn from MITRE’s CWE Top 25 and found approximately 40% vulnerable, with the CWE-327 cryptography scenarios among the categories the model handled worst.

The field data agrees. Fu et al. (Security Weaknesses of Copilot-Generated Code in GitHub Projects, ACM TOSEM 2025) studied Copilot snippets merged into real public repositories and found the single most frequent weakness was CWE-330, use of insufficiently random values. That is the randomness failure that undermines session tokens, nonces, and keys. The same study reports that feeding static-analysis warnings back through the assistant fixed up to 55.5% of the issues.

The largest measurement comes from formal verification. The FormAI dataset (Tihanyi et al. 2023) generated 112,000 C programs and labeled them with the ESBMC bounded model checker rather than a heuristic linter; 51.24% carried at least one vulnerability, crypto misuse among the labeled classes. Because the labeling is a formal proof, that rate is a floor.

What Bandit Flags

BrassCoders bundles Bandit’s cryptography rules and reports every match the same way on every run: MD5 and SHA1 (B303), insecure ciphers and ECB mode (B304 and B305), weak hashlib algorithms (B324), and the standard random module used in a security context (B311). Each is a structural pattern in the source, not a judgment about the surrounding code.

A password hasher that reaches for MD5 produces a finding shaped like this:

issues:
  - id: bandit-B303-001
    severity: high
    file_path: auth/password.py
    line_number: 22
    title: Use of Weak MD5 Hash for Security
    description: MD5 is a fast, collision-prone hash unsuitable for password
      storage. An attacker who obtains the hash can brute-force it cheaply.
    remediation: Replace with a slow, salted password hash — bcrypt, scrypt,
      or Argon2 via passlib or the hashlib.scrypt primitive.

The AI assistant reading the findings file gets the severity, the exact line, and the fix direction before it reads a line of source. It confirms the finding and writes the patch. The discovery step is already done.

The MD5 in the Corpus Is Actually Fine

BrassCoders flags the MD5 call in file_dedupe.py from its published corpus, and that finding is a false positive: the file hashes file contents to detect duplicates, a non-security use where MD5’s speed is a feature and its collision-weakness doesn’t matter. The generated code:

with open(path, "rb") as f:
    digest = hashlib.md5(f.read()).hexdigest()
if digest in seen:
    duplicates.append((path, seen[digest]))

This is exactly the case where a context-inferring scanner would go wrong in the other direction. A rule that demoted MD5 findings whenever the variable was named digest or the file was named *_dedupe.py would also demote a real password hash that happened to use those names. BrassCoders refuses that trade. It reports the md5 call, and the AI assistant reading the findings decides whether this instance is security-sensitive.

That division is the point. BrassCoders is the pattern reporter; the AI consumer of the YAML is the context-aware triage layer. The false positive is a feature, not a failure, because the alternative is a scanner that stays silent on the real bug to keep its false-positive count down.

Why Review Alone Misses Weak Crypto

BrassCoders puts a deterministic check under human review because review alone is unreliable on crypto, and the strongest evidence is a controlled study. Perry et al. (Do Users Write More Insecure Code with AI Assistants?, ACM CCS 2023) ran participants through security-sensitive tasks including one that asked them to encrypt and sign a message. Those with an AI assistant wrote significantly less secure code, and were more likely to believe their code was secure than the control group.

The confidence gap is what makes this hard to catch by eye. A developer trusts weak crypto more when an assistant produced it, so it clears review with less scrutiny. A deterministic scanner underneath the review doesn’t share that bias. It flags the md5 call whether a human, a model, or a coin flip put it there.

The Fixes

BrassCoders emits a remediation note with each crypto finding, and the fixes are well-defined by use case. Password hashing moves off MD5 and SHA1 onto a slow, salted algorithm:

from passlib.hash import argon2

hashed = argon2.hash(password)
argon2.verify(candidate, hashed)

Symmetric encryption moves off ECB, which leaks structure because identical plaintext blocks produce identical ciphertext blocks, onto an authenticated mode:

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

aesgcm = AESGCM(key)
ct = aesgcm.encrypt(nonce, plaintext, associated_data)

Token and key generation moves off the random module, whose Mersenne Twister is predictable from observed outputs, onto secrets:

import secrets

token = secrets.token_hex(32)

Each fix is the kind of well-defined replacement the model implements from the remediation note in one pass. The scanner narrows the solution space; the assistant writes the specific patch against the surrounding code.

pip install brasscoders
brasscoders --offline scan /path/to/your/project

The scanner flags the pattern on every run. The AI assistant triages the context. You keep the crypto that’s fine and fix the crypto that isn’t.

Frequently Asked Questions

How often does AI-generated code use weak cryptography?

Three studies converge on a high rate. Pearce et al. (IEEE S&P 2022) found roughly 40% of 1,689 GitHub Copilot programs vulnerable across CWE Top 25, with the CWE-327 cryptography scenarios among the worst-handled. Fu et al. (ACM TOSEM 2025) found CWE-330, insufficiently random values, the single most frequent weakness in real Copilot snippets. FormAI's formal-verification labeling put the overall vulnerability rate at 51.24% across 112,000 generated C programs.

What weak-crypto patterns do AI assistants generate most?

MD5 and SHA1 for password hashing, AES in ECB mode, hardcoded initialization vectors, and the standard random module used to generate tokens and keys where a cryptographically secure generator is required. Fu et al. found this last pattern, CWE-330, the most common weakness of all in shipped Copilot code.

Does BrassCoders flag MD5 even when it's safe?

Yes, and that is by design. BrassCoders reports the structural pattern; it does not infer whether the specific use is security-sensitive. MD5 used to detect duplicate files is safe, and BrassCoders still flags it. The AI assistant reading the findings file triages the context and marks it a false positive. BrassCoders reports the pattern; Claude decides whether it matters.

How do I fix weak crypto that BrassCoders flags?

Password hashing moves from MD5 or SHA1 to bcrypt, scrypt, or Argon2. AES moves from ECB to an authenticated mode like GCM. Token and key generation moves from the random module to Python's secrets module. BrassCoders emits a remediation note with each finding stating the direction.