diff --git a/pex/package.py b/pex/package.py
index cc90537c5..4e301fa57 100644
--- a/pex/package.py
+++ b/pex/package.py
@@ -9,8 +9,8 @@
 from pex.base import maybe_requirement
 from pex.link import Link
 from pex.pep425tags import get_supported
-from pex.third_party.pkg_resources import EGG_NAME, parse_version, safe_name, safe_version
-from pex.util import Memoizer
+from pex.third_party.pkg_resources import EGG_NAME, parse_version, safe_version
+from pex.util import Memoizer, normalized_name
 
 
 class Package(Link):
@@ -80,8 +80,8 @@ def satisfies(self, requirement, allow_prereleases=None):
     :returns: True if the package matches the requirement, otherwise False
     """
     requirement = maybe_requirement(requirement)
-    link_name = safe_name(self.name).lower()
-    if link_name != requirement.key:
+    link_name = normalized_name(self.name)
+    if link_name != normalized_name(requirement.key):
       return False
 
     # NB: If we upgrade to setuptools>=34 the SpecifierSet used here (requirement.specifier) will
@@ -136,7 +136,7 @@ def __init__(self, url, **kw):
 
   @property
   def name(self):
-    return safe_name(self._name)
+    return normalized_name(self._name)
 
   @property
   def raw_version(self):
@@ -202,7 +202,7 @@ def __hash__(self):
 
   @property
   def name(self):
-    return safe_name(self._name)
+    return normalized_name(self._name)
 
   @property
   def raw_version(self):
diff --git a/pex/resolvable.py b/pex/resolvable.py
index 2eeaaa5ab..c2ff64166 100644
--- a/pex/resolvable.py
+++ b/pex/resolvable.py
@@ -14,6 +14,7 @@
 from pex.package import Package
 from pex.resolver_options import ResolverOptionsBuilder, ResolverOptionsInterface
 from pex.third_party.pkg_resources import Requirement, safe_extra
+from pex.util import normalized_name
 
 # Extract extras as specified per "declaring extras":
 # https://pythonhosted.org/setuptools/setuptools.html
@@ -228,7 +229,7 @@ def packages(self):
 
   @property
   def name(self):
-    return self.requirement.key
+    return normalized_name(self.requirement.key)
 
   @property
   def exact(self):
diff --git a/pex/resolver.py b/pex/resolver.py
index 55a14039b..1af34a78f 100644
--- a/pex/resolver.py
+++ b/pex/resolver.py
@@ -20,9 +20,9 @@
 from pex.platforms import Platform
 from pex.resolvable import ResolvableRequirement, resolvables_from_iterable
 from pex.resolver_options import ResolverOptionsBuilder
-from pex.third_party.pkg_resources import Distribution, Requirement, safe_name
+from pex.third_party.pkg_resources import Distribution, Requirement
 from pex.tracer import TRACER
-from pex.util import DistributionHelper
+from pex.util import DistributionHelper, normalized_name
 
 
 @contextmanager
@@ -78,7 +78,7 @@ def merge(self, other):
 class _ResolvableSet(object):
   @classmethod
   def normalize(cls, name):
-    return safe_name(name).lower()
+    return normalized_name(name)
 
   def __init__(self, tuples=None):
     # A list of _ResolvedPackages
diff --git a/pex/resolver_options.py b/pex/resolver_options.py
index 9d1196d10..ee30803ba 100644
--- a/pex/resolver_options.py
+++ b/pex/resolver_options.py
@@ -10,8 +10,8 @@
 from pex.iterator import Iterator
 from pex.package import EggPackage, SourcePackage, WheelPackage
 from pex.sorter import Sorter
-from pex.third_party.pkg_resources import safe_name
 from pex.translator import ChainedTranslator, EggTranslator, SourceTranslator, WheelTranslator
+from pex.util import normalized_name
 
 
 class ResolverOptionsInterface(object):
@@ -95,11 +95,11 @@ def allow_all_external(self):
     return self
 
   def allow_external(self, key):
-    self._allow_external.add(safe_name(key).lower())
+    self._allow_external.add(normalized_name(key))
     return self
 
   def allow_unverified(self, key):
-    self._allow_unverified.add(safe_name(key).lower())
+    self._allow_unverified.add(normalized_name(key))
     return self
 
   def use_wheel(self):
diff --git a/pex/util.py b/pex/util.py
index c84e48c39..025a9fcdb 100644
--- a/pex/util.py
+++ b/pex/util.py
@@ -5,6 +5,7 @@
 
 import contextlib
 import os
+import re
 import tempfile
 import uuid
 from hashlib import sha1
@@ -250,3 +251,14 @@ def iter_pth_paths(filename):
         if extras_dir_case_insensitive not in known_paths and os.path.exists(extras_dir):
           yield extras_dir
           known_paths.add(extras_dir_case_insensitive)
+
+
+def normalized_name(name):
+  """Return a PEP-503 normalized version of the given name.
+
+  See: https://www.python.org/dev/peps/pep-0503/#normalized-names
+
+  :param str name: The distribution name to normalize.
+  :rtype: str
+  """
+  return re.sub(r"[-_.]+", "-", name).lower()
diff --git a/tests/test_resolvable.py b/tests/test_resolvable.py
index 51c4ca159..63574ef59 100644
--- a/tests/test_resolvable.py
+++ b/tests/test_resolvable.py
@@ -17,6 +17,7 @@
 )
 from pex.resolver_options import ResolverOptionsBuilder
 from pex.testing import make_source_dir
+from pex.util import normalized_name
 
 try:
   from unittest import mock
@@ -89,11 +90,11 @@ def test_resolvable_directory():
 
   with make_source_dir(name='my_project') as td:
     rdir = ResolvableDirectory.from_string(td, builder, interpreter)
-    assert rdir.name == pkg_resources.safe_name('my_project')
+    assert rdir.name == normalized_name('my_project')
     assert rdir.extras() == []
 
     rdir = ResolvableDirectory.from_string(td + '[extra1,extra2]', builder, interpreter)
-    assert rdir.name == pkg_resources.safe_name('my_project')
+    assert rdir.name == normalized_name('my_project')
     assert rdir.extras() == ['extra1', 'extra2']