diff --git a/README.rst b/README.rst
index 6380fff..e22dd09 100644
--- a/README.rst
+++ b/README.rst
@@ -252,7 +252,12 @@ put it directly into ``pip``.
 Version History
 ===============
 
-0.13.x
+next
+
+  * Support for "extras syntax". E.g. ``hashin "requests[security]"``. Doesn't
+    actually get hashes for ``security`` (in this case, that's not even a
+    package) but allows that syntax into your ``requirements.txt`` file.
+    See https://github.com/peterbe/hashin/issues/70
 
   * All code is now formatted with `Black <https://pypi.org/project/black/>`_.
 
diff --git a/hashin.py b/hashin.py
index ec636e4..b74ccf2 100755
--- a/hashin.py
+++ b/hashin.py
@@ -6,6 +6,7 @@ See README :)
 from __future__ import print_function
 import argparse
 import cgi
+import difflib
 import tempfile
 import os
 import re
@@ -14,8 +15,8 @@ import json
 from itertools import chain
 
 import pip_api
+from packaging.requirements import Requirement
 from packaging.version import parse
-import difflib
 
 if sys.version_info >= (3,):
     from urllib.request import urlopen
@@ -146,8 +147,10 @@ def run_single_package(
         package, version = spec, None
         # There are other ways to what the latest version is.
 
+    req = Requirement(package)
+
     data = get_package_hashes(
-        package=package,
+        package=req.name,
         version=version,
         verbose=verbose,
         python_versions=python_versions,
@@ -155,9 +158,15 @@ def run_single_package(
         include_prereleases=include_prereleases,
     )
     package = data["package"]
+    # We need to keep this `req` instance for the sake of turning it into a string
+    # the correct way. But, the name might actually be wrong. Suppose the user
+    # asked for "Django" but on PyPI it's actually called "django", then we want
+    # correct that.
+    # We do that by modifying only the `name` part of the `Requirement` instance.
+    req.name = package
 
     maybe_restriction = "" if not restriction else "; {0}".format(restriction)
-    new_lines = "{0}=={1}{2} \\\n".format(package, data["version"], maybe_restriction)
+    new_lines = "{0}=={1}{2} \\\n".format(req, data["version"], maybe_restriction)
     padding = " " * 4
     for i, release in enumerate(data["hashes"]):
         new_lines += "{0}--hash={1}:{2}".format(padding, algorithm, release["hash"])
@@ -190,8 +199,12 @@ def run_single_package(
 
 def amend_requirements_content(requirements, package, new_lines):
     # if the package wasn't already there, add it to the bottom
-    regex = "(^|\n|\n\r){0}==".format(re.escape(package))
-    if not re.search(regex, requirements, re.IGNORECASE):
+    regex = re.compile(
+        r"(^|\n|\n\r){0}==|(^|\n|\n\r){0}\[.*\]==".format(re.escape(package)),
+        re.IGNORECASE,
+    )
+
+    if not regex.search(requirements):
         # easy peasy
         if requirements:
             requirements = requirements.strip() + "\n"
@@ -201,7 +214,7 @@ def amend_requirements_content(requirements, package, new_lines):
         lines = []
         padding = " " * 4
         for line in requirements.splitlines():
-            if line.lower().startswith("{0}==".format(package.lower())):
+            if regex.search(line):
                 lines.append(line)
             elif lines and line.startswith(padding):
                 lines.append(line)
@@ -271,7 +284,7 @@ def expand_python_version(version):
 # This should match the naming convention laid out in PEP 0427
 # url = 'https://pypi.python.org/packages/3.4/P/Pygments/Pygments-2.1-py3-none-any.whl' # NOQA
 CLASSIFY_WHEEL_RE = re.compile(
-    """
+    r"""
     ^(?P<package>.+)-
     (?P<version>\d[^-]*)-
     (?P<python_version>[^-]+)-
@@ -285,7 +298,7 @@ CLASSIFY_WHEEL_RE = re.compile(
 )
 
 CLASSIFY_EGG_RE = re.compile(
-    """
+    r"""
     ^(?P<package>.+)-
     (?P<version>\d[^-]*)-
     (?P<python_version>[^-]+)
@@ -298,7 +311,7 @@ CLASSIFY_EGG_RE = re.compile(
 )
 
 CLASSIFY_ARCHIVE_RE = re.compile(
-    """
+    r"""
     ^(?P<package>.+)-
     (?P<version>\d[^-]*)
     (-(?P<platform>[^\.]+))?
@@ -310,7 +323,7 @@ CLASSIFY_ARCHIVE_RE = re.compile(
 )
 
 CLASSIFY_EXE_RE = re.compile(
-    """
+    r"""
     ^(?P<package>.+)-
     (?P<version>\d[^-]*)[-\.]
     ((?P<platform>[^-]*)-)?
diff --git a/setup.py b/setup.py
index 9a43ff6..882b14f 100644
--- a/setup.py
+++ b/setup.py
@@ -7,38 +7,44 @@ _here = path.dirname(__file__)
 # http://www.eby-sarna.com/pipermail/peak/2010-May/003357.html:
 try:
     import multiprocessing
+
     multiprocessing = multiprocessing  # take it easy pyflakes
 except ImportError:
     pass
 
 setup(
-    name='hashin',
-    version='0.13.4',
-    description='Edits your requirements.txt by hashing them in',
-    long_description=open(path.join(_here, 'README.rst')).read(),
-    author='Peter Bengtsson',
-    author_email='mail@peterbe.com',
-    license='MIT',
-    py_modules=['hashin'],
-    entry_points={
-        'console_scripts': ['hashin = hashin:main']
-        },
-    url='https://github.com/peterbe/hashin',
+    name="hashin",
+    version="0.13.4",
+    description="Edits your requirements.txt by hashing them in",
+    long_description=open(path.join(_here, "README.rst")).read(),
+    author="Peter Bengtsson",
+    author_email="mail@peterbe.com",
+    license="MIT",
+    py_modules=["hashin"],
+    entry_points={"console_scripts": ["hashin = hashin:main"]},
+    url="https://github.com/peterbe/hashin",
     include_package_data=True,
-    python_requires='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3',
-    install_requires=['packaging', 'pip-api'],
-    tests_require=['pytest', 'mock'],
-    setup_requires=['pytest-runner'],
+    python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3",
+    install_requires=["packaging", "pip-api"],
+    tests_require=["pytest", "mock"],
+    setup_requires=["pytest-runner"],
     classifiers=[
-        'Intended Audience :: Developers',
-        'Intended Audience :: System Administrators',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3',
-        'Topic :: Software Development :: Build Tools',
-        'Topic :: System :: Installation/Setup',
-        'Topic :: System :: Systems Administration'
-        ],
-    keywords=['pip', 'repeatable', 'deploy', 'deployment', 'hash',
-              'install', 'installer']
+        "Intended Audience :: Developers",
+        "Intended Audience :: System Administrators",
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Topic :: Software Development :: Build Tools",
+        "Topic :: System :: Installation/Setup",
+        "Topic :: System :: Systems Administration",
+    ],
+    keywords=[
+        "pip",
+        "repeatable",
+        "deploy",
+        "deployment",
+        "hash",
+        "install",
+        "installer",
+    ],
 )
diff --git a/tox.ini b/tox.ini
index 89e3636..7599fc5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,7 +16,7 @@ commands = pytest tests {posargs}
 [testenv:lint]
 deps =
     -rlint-requirements.txt
-commands=therapist run --use-tracked-files hashin.py tests
+commands=therapist run --use-tracked-files
 
 [testenv:restlint]
 deps =

fails on extras syntax
example:

```
$ hashin 'python-language-server[yapf]'
Traceback (most recent call last):
  File "/home/user/go/src/github.com/user/flaskexp/ve/bin/hashin", line 11, in <module>
    sys.exit(main())
  File "/home/user/go/src/github.com/user/flaskexp/ve/lib/python3.6/site-packages/hashin.py", line 499, in main
    include_prereleases=args.include_prereleases,
  File "/home/user/go/src/github.com/user/flaskexp/ve/lib/python3.6/site-packages/hashin.py", line 129, in run
    run_single_package(spec, *args, **kwargs)
  File "/home/user/go/src/github.com/user/flaskexp/ve/lib/python3.6/site-packages/hashin.py", line 157, in run_single_package
    include_prereleases=include_prereleases,
  File "/home/user/go/src/github.com/user/flaskexp/ve/lib/python3.6/site-packages/hashin.py", line 429, in get_package_hashes
    data = get_package_data(package, verbose)
  File "/home/user/go/src/github.com/user/flaskexp/ve/lib/python3.6/site-packages/hashin.py", line 358, in get_package_data
    content = json.loads(_download(url))
  File "/home/user/go/src/github.com/user/flaskexp/ve/lib/python3.6/site-packages/hashin.py", line 100, in _download
    r = urlopen(url)
  File "/usr/lib/python3.6/urllib/request.py", line 223, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.6/urllib/request.py", line 532, in open
    response = meth(req, response)
  File "/usr/lib/python3.6/urllib/request.py", line 642, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python3.6/urllib/request.py", line 564, in error
    result = self._call_chain(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 756, in http_error_302
    return self.parent.open(new, timeout=req.timeout)
  File "/usr/lib/python3.6/urllib/request.py", line 532, in open
    response = meth(req, response)
  File "/usr/lib/python3.6/urllib/request.py", line 642, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python3.6/urllib/request.py", line 564, in error
    result = self._call_chain(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 756, in http_error_302
    return self.parent.open(new, timeout=req.timeout)
  File "/usr/lib/python3.6/urllib/request.py", line 532, in open
    response = meth(req, response)
  File "/usr/lib/python3.6/urllib/request.py", line 642, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python3.6/urllib/request.py", line 570, in error
    return self._call_chain(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 650, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 404: Not Found
```

diff --git a/tests/test_cli.py b/tests/test_cli.py
index f24f1d5..a949b7e 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1186,3 +1186,185 @@ selenium==2.53.1 \
         self.assertRaises(
             hashin.PackageError, hashin.get_package_hashes, package="uggamugga"
         )
+
+    @cleanup_tmpdir("hashin*")
+    @mock.patch("hashin.urlopen")
+    def test_with_extras_syntax(self, murlopen):
+        """When you want to add the hashes of a package by using the
+        "extras notation". E.g `requests[security]`.
+        In this case, it should basically ignore the `[security]` part when
+        doing the magic but get that back when putting the final result
+        into the requirements file.
+        """
+
+        def mocked_get(url, **options):
+            if url == "https://pypi.org/pypi/hashin/json":
+                return _Response(
+                    {
+                        "info": {"version": "0.10", "name": "hashin"},
+                        "releases": {
+                            "0.10": [
+                                {
+                                    "url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
+                                    "digests": {"sha256": "ccccc"},
+                                }
+                            ]
+                        },
+                    }
+                )
+            raise NotImplementedError(url)
+
+        murlopen.side_effect = mocked_get
+
+        with tmpfile() as filename:
+            with open(filename, "w") as f:
+                f.write("")
+
+            retcode = hashin.run("hashin[stuff]", filename, "sha256")
+
+            self.assertEqual(retcode, 0)
+            with open(filename) as f:
+                output = f.read()
+            self.assertTrue("hashin[stuff]==0.10" in output)
+
+    @cleanup_tmpdir("hashin*")
+    @mock.patch("hashin.urlopen")
+    def test_extras_syntax_edit(self, murlopen):
+        def mocked_get(url, **options):
+            if url == "https://pypi.org/pypi/hashin/json":
+                return _Response(
+                    {
+                        "info": {"version": "0.10", "name": "hashin"},
+                        "releases": {
+                            "0.10": [
+                                {
+                                    "url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
+                                    "digests": {"sha256": "ccccc"},
+                                }
+                            ]
+                        },
+                    }
+                )
+            raise NotImplementedError(url)
+
+        murlopen.side_effect = mocked_get
+
+        with tmpfile() as filename:
+            with open(filename, "w") as f:
+                f.write("hashin==0.10\n")
+                f.write("    --hash=sha256:ccccc\n")
+
+            retcode = hashin.run("hashin[stuff]", filename, "sha256")
+
+            self.assertEqual(retcode, 0)
+            with open(filename) as f:
+                output = f.read()
+            self.assertTrue("hashin[stuff]==0.10" in output)
+            self.assertTrue("hashin==0.10" not in output)
+
+    @cleanup_tmpdir("hashin*")
+    @mock.patch("hashin.urlopen")
+    def test_add_extra_extras_syntax_edit(self, murlopen):
+        def mocked_get(url, **options):
+            if url == "https://pypi.org/pypi/hashin/json":
+                return _Response(
+                    {
+                        "info": {"version": "0.10", "name": "hashin"},
+                        "releases": {
+                            "0.10": [
+                                {
+                                    "url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
+                                    "digests": {"sha256": "ccccc"},
+                                }
+                            ]
+                        },
+                    }
+                )
+            raise NotImplementedError(url)
+
+        murlopen.side_effect = mocked_get
+
+        with tmpfile() as filename:
+            with open(filename, "w") as f:
+                f.write("hashin[stuff]==0.10\n")
+                f.write("    --hash=sha256:ccccc\n")
+
+            retcode = hashin.run("hashin[extra,stuff]", filename, "sha256")
+
+            self.assertEqual(retcode, 0)
+            with open(filename) as f:
+                output = f.read()
+            self.assertTrue("hashin[extra,stuff]==0.10" in output)
+            self.assertTrue("hashin==0.10" not in output)
+            self.assertTrue("hashin[stuff]==0.10" not in output)
+            self.assertTrue("hashin[extra]==0.10" not in output)
+
+    @cleanup_tmpdir("hashin*")
+    @mock.patch("hashin.urlopen")
+    def test_change_extra_extras_syntax_edit(self, murlopen):
+        def mocked_get(url, **options):
+            if url == "https://pypi.org/pypi/hashin/json":
+                return _Response(
+                    {
+                        "info": {"version": "0.10", "name": "hashin"},
+                        "releases": {
+                            "0.10": [
+                                {
+                                    "url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
+                                    "digests": {"sha256": "ccccc"},
+                                }
+                            ]
+                        },
+                    }
+                )
+            raise NotImplementedError(url)
+
+        murlopen.side_effect = mocked_get
+
+        with tmpfile() as filename:
+            with open(filename, "w") as f:
+                f.write("hashin[stuff]==0.10\n")
+                f.write("    --hash=sha256:ccccc\n")
+
+            retcode = hashin.run("hashin[different]", filename, "sha256")
+
+            self.assertEqual(retcode, 0)
+            with open(filename) as f:
+                output = f.read()
+            self.assertTrue("hashin[different]==0.10" in output)
+            self.assertTrue("hashin[stuff]==0.10" not in output)
+
+    @cleanup_tmpdir("hashin*")
+    @mock.patch("hashin.urlopen")
+    def test_remove_extra_extras_syntax_edit(self, murlopen):
+        def mocked_get(url, **options):
+            if url == "https://pypi.org/pypi/hashin/json":
+                return _Response(
+                    {
+                        "info": {"version": "0.10", "name": "hashin"},
+                        "releases": {
+                            "0.10": [
+                                {
+                                    "url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
+                                    "digests": {"sha256": "ccccc"},
+                                }
+                            ]
+                        },
+                    }
+                )
+            raise NotImplementedError(url)
+
+        murlopen.side_effect = mocked_get
+
+        with tmpfile() as filename:
+            with open(filename, "w") as f:
+                f.write("hashin[stuff]==0.10\n")
+                f.write("    --hash=sha256:ccccc\n")
+
+            retcode = hashin.run("hashin", filename, "sha256")
+
+            self.assertEqual(retcode, 0)
+            with open(filename) as f:
+                output = f.read()
+            self.assertTrue("hashin==0.10" in output)
+            self.assertTrue("hashin[stuff]==0.10" not in output)

pip install -e .
parse_log_pytest
requirements.txt
packaging pip-api pytest mock
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
-e git+https://github.com/peterbe/hashin.git@ffe6115cceef588f6e559710d9ce2fdb3e07f4eb#egg=hashin
importlib-metadata==4.8.3
iniconfig==1.1.1
mock==5.2.0
packaging==21.3
pip-api==0.0.26
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: hashin
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - mock==5.2.0
      - packaging==21.3
      - pip-api==0.0.26
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/hashin

tests/test_cli.py::Tests::test_add_extra_extras_syntax_edit tests/test_cli.py::Tests::test_change_extra_extras_syntax_edit tests/test_cli.py::Tests::test_extras_syntax_edit tests/test_cli.py::Tests::test_remove_extra_extras_syntax_edit tests/test_cli.py::Tests::test_with_extras_syntax
tests/test_cli.py::Tests::test_amend_requirements_content_new tests/test_cli.py::Tests::test_amend_requirements_content_new_similar_name tests/test_cli.py::Tests::test_amend_requirements_content_replacement tests/test_cli.py::Tests::test_amend_requirements_content_replacement_2 tests/test_cli.py::Tests::test_amend_requirements_content_replacement_amonst_others tests/test_cli.py::Tests::test_amend_requirements_content_replacement_amonst_others_2 tests/test_cli.py::Tests::test_amend_requirements_content_replacement_single_to_multi tests/test_cli.py::Tests::test_expand_python_version tests/test_cli.py::Tests::test_filter_releases tests/test_cli.py::Tests::test_get_hashes_error tests/test_cli.py::Tests::test_get_latest_version_non_pre_release tests/test_cli.py::Tests::test_get_latest_version_non_pre_release_leading_zeros tests/test_cli.py::Tests::test_get_latest_version_only_pre_release tests/test_cli.py::Tests::test_get_latest_version_simple tests/test_cli.py::Tests::test_get_package_hashes tests/test_cli.py::Tests::test_get_package_hashes_unknown_algorithm tests/test_cli.py::Tests::test_get_package_hashes_without_version tests/test_cli.py::Tests::test_main_packageerrors_stderr tests/test_cli.py::Tests::test_main_version tests/test_cli.py::Tests::test_non_200_ok_download tests/test_cli.py::Tests::test_release_url_metadata_python tests/test_cli.py::Tests::test_run tests/test_cli.py::Tests::test_run_case_insensitive tests/test_cli.py::Tests::test_run_contained_names tests/test_cli.py::Tests::test_run_dry tests/test_cli.py::Tests::test_run_pep_0496 tests/test_cli.py::Tests::test_run_without_specific_version
README.rst tox.ini hashin.py setup.py
README.rst tox.ini hashin.py setup.py