Skip to content

Commit 88821f2

Browse files
authored
feat(package): remove MD5 hashing entirely (#1262)
1 parent ce5fe53 commit 88821f2

File tree

3 files changed

+9
-42
lines changed

3 files changed

+9
-42
lines changed

changelog/1262.removal.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Remove support for MD5 digests during uploads.
2+
3+
This support was entirely vestigial, as MD5 is not a secure hash function
4+
and is not actually required on upload by PyPI.
5+
6+
Indices that cross-reference the uploaded content with a digest should
7+
use the provided SHA-256 and/or BLAKE2 digests instead.

tests/test_package.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ def test_metadata_dictionary_values(gpg_signature, attestation):
294294

295295

296296
TWINE_1_5_0_WHEEL_HEXDIGEST = package_file.Hexdigest(
297-
"1919f967e990bee7413e2a4bc35fd5d1",
298297
"d86b0f33f0c7df49e888b11c43b417da5520cbdbce9f20618b1494b600061e67",
299298
"b657a4148d05bd0098c1d6d8cc4e14e766dbe93c3a5ab6723b969da27a87bac0",
300299
)
@@ -308,18 +307,6 @@ def test_hash_manager():
308307
assert hasher.hexdigest() == TWINE_1_5_0_WHEEL_HEXDIGEST
309308

310309

311-
def test_fips_hash_manager_md5(monkeypatch):
312-
"""Generate hexdigest without MD5 when hashlib is using FIPS mode."""
313-
replaced_md5 = pretend.raiser(ValueError("fipsmode"))
314-
monkeypatch.setattr(package_file.hashlib, "md5", replaced_md5)
315-
316-
filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl"
317-
hasher = package_file.HashManager(filename)
318-
hasher.hash()
319-
hashes = TWINE_1_5_0_WHEEL_HEXDIGEST._replace(md5=None)
320-
assert hasher.hexdigest() == hashes
321-
322-
323310
@pytest.mark.parametrize("exception_class", [TypeError, ValueError])
324311
def test_fips_hash_manager_blake2(exception_class, monkeypatch):
325312
"""Generate hexdigest without BLAKE2 when hashlib is using FIPS mode."""
@@ -333,21 +320,18 @@ def test_fips_hash_manager_blake2(exception_class, monkeypatch):
333320
assert hasher.hexdigest() == hashes
334321

335322

336-
def test_fips_metadata_excludes_md5_and_blake2(monkeypatch):
323+
def test_fips_metadata_excludes_blake2(monkeypatch):
337324
"""Generate a valid metadata dictionary for Nexus when FIPS is enabled.
338325
339326
See also: https://github.com/pypa/twine/issues/775
340327
"""
341328
replaced_blake2b = pretend.raiser(ValueError("fipsmode"))
342-
replaced_md5 = pretend.raiser(ValueError("fipsmode"))
343-
monkeypatch.setattr(package_file.hashlib, "md5", replaced_md5)
344329
monkeypatch.setattr(package_file.hashlib, "blake2b", replaced_blake2b)
345330

346331
filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl"
347332
pf = package_file.PackageFile.from_filename(filename, None)
348333

349334
mddict = pf.metadata_dictionary()
350-
assert "md5_digest" not in mddict
351335
assert "blake2_256_digest" not in mddict
352336

353337

twine/package.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ class PackageMetadata(TypedDict, total=False):
157157
filetype: str
158158
gpg_signature: Tuple[str, bytes]
159159
attestations: str
160-
md5_digest: str
161160
sha256_digest: str
162161
blake2_256_digest: str
163162

@@ -188,7 +187,6 @@ def __init__(
188187
hasher.hash()
189188
hexdigest = hasher.hexdigest()
190189

191-
self.md5_digest = hexdigest.md5
192190
self.sha2_digest = hexdigest.sha2
193191
self.blake2_256_digest = hexdigest.blake2
194192

@@ -274,7 +272,7 @@ def metadata_dictionary(self) -> PackageMetadata:
274272

275273
# Additional meta-data: some of these fileds may not be set. Some
276274
# package repositories do not allow null values, so this only sends
277-
# non-null values. In particular, FIPS disables MD5 and Blake2, making
275+
# non-null values. In particular, FIPS disables Blake2, making
278276
# the digest values null. See https://github.com/pypa/twine/issues/775
279277

280278
if self.comment is not None:
@@ -289,9 +287,6 @@ def metadata_dictionary(self) -> PackageMetadata:
289287
if self.attestations is not None:
290288
data["attestations"] = json.dumps(self.attestations)
291289

292-
if self.md5_digest:
293-
data["md5_digest"] = self.md5_digest
294-
295290
if self.blake2_256_digest:
296291
data["blake2_256_digest"] = self.blake2_256_digest
297292

@@ -352,7 +347,6 @@ def run_gpg(cls, gpg_args: Tuple[str, ...]) -> None:
352347

353348

354349
class Hexdigest(NamedTuple):
355-
md5: Optional[str]
356350
sha2: Optional[str]
357351
blake2: Optional[str]
358352

@@ -367,13 +361,6 @@ def __init__(self, filename: str) -> None:
367361
"""Initialize our manager and hasher objects."""
368362
self.filename = filename
369363

370-
self._md5_hasher = None
371-
try:
372-
self._md5_hasher = hashlib.md5()
373-
except ValueError:
374-
# FIPs mode disables MD5
375-
pass
376-
377364
self._sha2_hasher = hashlib.sha256()
378365

379366
self._blake_hasher = None
@@ -383,15 +370,6 @@ def __init__(self, filename: str) -> None:
383370
# FIPS mode disables blake2
384371
pass
385372

386-
def _md5_update(self, content: bytes) -> None:
387-
if self._md5_hasher is not None:
388-
self._md5_hasher.update(content)
389-
390-
def _md5_hexdigest(self) -> Optional[str]:
391-
if self._md5_hasher is not None:
392-
return self._md5_hasher.hexdigest()
393-
return None
394-
395373
def _sha2_update(self, content: bytes) -> None:
396374
if self._sha2_hasher is not None:
397375
self._sha2_hasher.update(content)
@@ -414,14 +392,12 @@ def hash(self) -> None:
414392
"""Hash the file contents."""
415393
with open(self.filename, "rb") as fp:
416394
for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""):
417-
self._md5_update(content)
418395
self._sha2_update(content)
419396
self._blake_update(content)
420397

421398
def hexdigest(self) -> Hexdigest:
422399
"""Return the hexdigest for the file."""
423400
return Hexdigest(
424-
self._md5_hexdigest(),
425401
self._sha2_hexdigest(),
426402
self._blake_hexdigest(),
427403
)

0 commit comments

Comments
 (0)