Safeguard
Application Security•

CWE-1240 Risky Cryptographic Primitive Implementations: Fix Guide

Custom, outdated or non-compliant crypto fails quietly. CWE-1240 explains how risky primitives get in, how to spot them and what to use instead.

Safeguard Security Team
5 min read

CWE-1240 is the weakness of reaching for a cryptographic primitive and implementing it with something non-standard, unproven or disallowed. That can be a home-grown cipher, a hash that was retired years ago, or a mode of operation nobody has reviewed. It affects application developers, firmware teams and hardware designers alike, since all of them need hashes, ciphers, signatures and random numbers.

How it happens

Cryptographic primitives are built to do one narrow job in a mathematically well understood way. The usual argument for strength is that breaking the primitive takes an infeasible number of attempts. A risky implementation breaks that argument. The math the team relies on no longer describes the code that actually runs.

The usual routes in are familiar:

  • A default configuration picks an old algorithm when stronger ones are available.
  • A team writes its own algorithm because the standard one looks heavy.
  • A standard algorithm gets used in a new way, such as building encryption out of a chain of hashes.
  • Hardware uses a linear feedback shift register where a proper random source is needed.

MITRE's observed examples show the pattern. CVE-2020-4778 describes software using MD5 where related products default to SHA-256. CVE-2019-3907 covers an identity card that stores an MD5 hash of a salt and password. CVE-2021-34687 involves a personal key sent over the network with a substitution cipher. You can read each on its Gold vulnerability page, and the others at CVE-2019-3907 and CVE-2021-34687.

What an attacker gains

MITRE rates the confidentiality impact as high. In the worst case, incorrect use of a primitive leaves supposedly encrypted data as readable plaintext. A weak hash means stored passwords fall to guessing. A predictable random value means keys can be guessed.

How to find it

The MITRE entry lists four detection approaches.

  1. Architecture or design review. Check that every primitive matches the strongest recommendations from trusted parties. A custom or proprietary implementation without public review is a significant concern. MITRE rates this as high effectiveness.
  2. Manual analysis. Confirm that each implementation has no known flaws and is not using known-weak algorithms such as MD4, MD5, SHA1 or DES.
  3. Test vectors. For hardware, run dynamic tests before and after silicon using vectors published by NIST.
  4. Entropy checks. If the output of a primitive lacks entropy, something went wrong. Methods range from Shannon entropy to a crude compression test: truly random bytes should not compress.

For software, a quick pass is to search the code for the weak names above.

grep -rniE "md5|sha1|\bdes\b|rc4" src/ --include="*.py" --include="*.java" --include="*.js"

Each hit needs a human decision. An MD5 checksum for a cache key is a different matter from MD5 protecting passwords.

How to fix it

MITRE's mitigations, in plain steps:

  • Require compliance with the strongest recommendations from trusted parties, and keep that up to date because recommendations change. For US government systems, FIPS 140-3 supersedes FIPS 140-2.
  • Design with the strongest available primitives and algorithms from trusted parties.
  • Do not write custom or private cryptographic algorithms. Attacks on them are likely to be well understood by cryptographers. The source should be available for analysis.
  • Avoid using algorithms in novel ways or with new modes of operation, even when you are sure it is safe.
  • Build for cryptographic agility. Make it possible to swap one primitive for another in the next generation, and use wrappers to keep interfaces uniform. For hardware, design at the level of replaceable blocks.

Here is the password case in Python. The vulnerable version first, then a version using a memory-hard function from the standard library.

# Vulnerable: fast, unsalted, long retired for passwords
import hashlib
stored = hashlib.md5(password.encode()).hexdigest()
# Better: salted scrypt from the standard library
import hashlib, os
salt = os.urandom(16)
stored = salt + hashlib.scrypt(password.encode(), salt=salt, n=2**14, r=8, p=1)

In real systems, use a maintained password hashing library and its recommended parameters rather than rolling this yourself. The point of the example is the primitive, not the glue.

Try it yourself

Weak crypto often arrives through a dependency, not your own code. Run an inventory of what your project pulls in and look for libraries that carry the old algorithms.

Click through it at your own pace, or open it full screen.

You can also run the free in-browser scan at gold.safeguard.sh/scan. No login is needed, the file stays on your machine, and it handles up to 100 dependencies.

Quick check

Is it fine to write your own cipher if you keep it secret?

No. MITRE discourages custom or private algorithms. They are likely exposed to attacks cryptographers already understand, and secrecy is especially weak if the algorithm breaks once attackers learn how it works.

What does cryptographic agility mean here?

Designing so one primitive can be replaced by another in the next generation, with wrappers giving uniform interfaces. It matters most in hardware, where upgrades are slow.

How can you tell that a random source has gone wrong?

Measure the entropy of its output. Truly random bytes should barely compress, so a compressed copy that is much smaller than the original is a warning sign.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.