skip to content

What does usedforsecurity=False do in hashlib.new("md5", usedforsecurity=False)?

level: seniorimportance: nice to knowfreq 20%

answer

  1. A declaration of intent, not a mitigation
  2. Some builds refuse the algorithm outright
  3. Legacy checksum versus security control
  4. Guaranteed by the module, not by the vendor
  5. Handle ValueError from the constructor

basics

~20 s

It declares that the digest is not being used for a security purpose, so a hardened or FIPS-restricted build permits a blocked algorithm such as MD5 instead of raising ValueError. On an ordinary build it changes nothing observable.

solid answer

~40 s

Some builds link an OpenSSL configured to refuse non-approved digests; on those, `hashlib.md5(...)` raises `ValueError` and legacy code that used MD5 as a plain checksum stops working. Passing `usedforsecurity=False` to `hashlib.new()` or to a named constructor asserts that the digest is a non-security checksum — a cache key, a legacy interop value — and lets the implementation fall back to a non-restricted code path. It is a **declaration of intent, not a mitigation**: it does not make MD5 collision-resistant and must never appear on an integrity or authentication path. The related gotcha is that `hashlib.algorithms_guaranteed` still lists `md5`; that set is what the module promises, and a vendor's hardened build can still refuse it at construction time.

code

python · 8 lines
python
import hashlib

print("md5" in hashlib.algorithms_guaranteed)
print(sorted(hashlib.algorithms_guaranteed)[:5])

h = hashlib.new("md5", usedforsecurity=False)
h.update(b"tile-cache-key")
print(h.name, h.hexdigest()[:16])

go deeper

for a junior

You are unlikely to be asked this. Take away only the shape of it: a keyword that says a digest is not a security control, and the rule that new code should use SHA-256 or BLAKE2 rather than MD5.

for a middle

Be able to say what it does mechanically — it permits a restricted algorithm on a hardened build instead of raising ValueError — and that it changes policy, never the algorithm's strength.

for a senior

Show the operating judgement: catching ValueError from the constructor, telling a genuine legacy checksum apart from an integrity check, validating configured names against hashlib.algorithms_available, and documenting why a legacy digest cannot move.

for a principal

Own the migration story: an inventory of where weak digests persist, digests stored alongside their algorithm name so rehashing is possible, and a standard that new code uses a guaranteed modern digest.

### The problem the flag exists to solve CPython's `hashlib` is usually backed by the platform's OpenSSL. Some deployments — regulated environments, hardened distributions, images built for a compliance regime — configure that library to permit only approved digests. On such a build, constructing MD5 or SHA-1 fails at the constructor with a `ValueError`, before you hash a single byte. That policy is aimed at security uses, but a digest is not always a security control. MD5 shows up all over old code as a cheap non-cryptographic fingerprint: a cache key, an ETag, a shard selector, a value some other system computed years ago and still compares against. Blocking those uses breaks working software without improving anyone's security posture. `usedforsecurity` is the way to tell the two apart. It is a keyword-only parameter accepted by `hashlib.new()` and by the named constructors: ```python h = hashlib.new("md5", usedforsecurity=False) h.update(b"tile-cache-key") h.hexdigest() ``` The default is `True` — meaning "assume this matters" — and passing `False` asserts that this particular digest is not a security control, allowing the implementation to route around the restriction, typically by using CPython's built-in implementation rather than the restricted provider. ### What it does not do The flag changes **policy**, not mathematics. MD5 with `usedforsecurity=False` is exactly as collision-prone as MD5 without it: practical collisions have been constructible for many years, and SHA-1 is in the same category. So the flag is safe on a cache key and indefensible on anything that answers "is this the file I expect" against an adversary, verifies a download, deduplicates content where a collision has security meaning, or feeds a signature. Two more misconceptions are worth naming. It does not silently substitute a stronger algorithm — the digest you get is a real MD5, byte-compatible with every other MD5, which is the whole point for legacy interop. And it is not a speed knob; any performance difference is an incidental consequence of which implementation ends up being used. ### algorithms_guaranteed and the limits of "guaranteed" `hashlib.algorithms_guaranteed` is a set of names the module promises to support on every platform: the SHA-2 family, the SHA-3 family, SHAKE, BLAKE2 — and, historically, `md5` and `sha1`. Its sibling `hashlib.algorithms_available` is the larger set this particular interpreter can construct, including whatever extra digests the linked OpenSSL provides. Configuration-driven code should validate an algorithm name against one of these sets rather than trusting a string from a file. The subtlety a senior is expected to know: membership in `algorithms_guaranteed` is a promise made by the stdlib, and a hardened vendor build can still refuse to construct one of those names. So `"md5" in hashlib.algorithms_guaranteed` being `True` does not guarantee `hashlib.md5()` succeeds on the host you are actually running on. Code that must survive both kinds of build handles the `ValueError`: ```python try: h = hashlib.new(name, usedforsecurity=False) except ValueError: h = hashlib.sha256() # or fail loudly, if the legacy value is required ``` ### The judgement call Reaching for this flag is nearly always a signal about an old code path, and the right response has three parts. First, decide honestly whether that digest is a security control — if the answer is "partly" or "I am not sure", it is, and the fix is a modern algorithm, not a flag. Second, if it genuinely is a checksum, prefer moving it to SHA-256 or BLAKE2 anyway; those are guaranteed everywhere, are not policy-restricted, and BLAKE2 is typically faster than MD5 on modern hardware, so the "legacy is cheaper" argument rarely survives measurement. Third, keep `usedforsecurity=False` only where the digest value itself must stay byte-compatible with a system you do not control — and comment it with *why*, because the next reader will otherwise assume it is a security bypass. Storing the algorithm name alongside every persisted digest is what makes that eventual migration possible: without it, you cannot tell which stored values still need rehashing. ### Choosing the replacement When a legacy digest can move, the replacement is rarely a hard call. SHA-256 is the boring default and is in `hashlib.algorithms_guaranteed`. BLAKE2 is the interesting one: `hashlib.blake2b` and `hashlib.blake2s` are also guaranteed, are typically faster than MD5 on 64-bit hardware, and take a `digest_size` argument, so `hashlib.blake2b(data, digest_size=16)` gives a real 16-byte digest when storage per row genuinely matters — a proper short digest, not a truncated long one. That removes the last honest argument for keeping a weak algorithm around: it was never the cheap option, only the familiar one.

  • Does usedforsecurity=False make MD5 safe for verifying a downloaded artefact?
    No. The flag changes only whether the build permits the construction; the algorithm is unchanged and MD5 collisions are practically constructible. Verifying an artefact against an adversary needs a modern digest such as SHA-256 or BLAKE2, and ideally a signature, since an attacker who can replace the file can usually replace the checksum beside it.
  • hashlib.algorithms_guaranteed contains "md5" — why can hashlib.md5() still fail?
    That set states what the module promises across platforms, but a vendor may ship a hardened build whose crypto provider refuses non-approved digests, so construction raises ValueError on that host. Code that must run on both kinds of build should catch ValueError, or pass usedforsecurity=False when the digest genuinely is not a security control.
  • What is the difference between hashlib.algorithms_guaranteed and hashlib.algorithms_available?
    algorithms_guaranteed is the fixed set of names the module promises on every platform; algorithms_available is the set this specific interpreter can construct, usually larger because it includes whatever the linked crypto library adds. Validate a configured algorithm name against one of them rather than passing an arbitrary string to hashlib.new().

saying these in an interview costs you the question

  • Claims the flag makes MD5 collision-resistant
  • Thinks it silently upgrades to a stronger algorithm
  • Treats it as a performance tuning option
  • Uses it on an integrity or authentication path
  • Assumes algorithms_guaranteed means construction always succeeds
  • Keeps a legacy digest with no note on why it cannot change

context