pwnedpasswords


Namepwnedpasswords JSON
Version 3.0.0 PyPI version JSON
download
home_pagehttps://github.com/lionheart/pwnedpasswords
SummaryA Python wrapper for Troy Hunt's Pwned Passwords API.
upload_time2025-08-24 13:42:36
maintainerNone
docs_urlNone
authorDan Loewenherz
requires_pythonNone
licenseApache 2.0
keywords passwords security
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI
coveralls test coverage No coveralls.
            <!-- <p align="center">
  <img width="344" height="225" src="meta/repo-banner-small.png" />
</p> -->

![](meta/repo-banner.png)
[![](meta/repo-banner-bottom.png)][lionheart-url]

[![CI Status][ci-badge]][travis-repo-url]
[![Version][version-badge]][pypi-url]
[![Python Versions][versions-badge]][pypi-url]

`pwnedpasswords` is a small Python wrapper and command line utility that lets you check if a passphrase has been pwned using the [Pwned Passwords v2 API](https://haveibeenpwned.com/API/v2#PwnedPasswords). All provided password data is [k-anonymized][k-anonymous-url] before sending to the API, so plaintext passwords never leave your computer.

From https://haveibeenpwned.com/API/v2#PwnedPasswords:

> Pwned Passwords are more than half a billion passwords which have previously been exposed in data breaches. The service is detailed in the [launch blog post](https://www.troyhunt.com/introducing-306-million-freely-downloadable-pwned-passwords/) then [further expanded on with the release of version 2](https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2). The entire data set is [both downloadable and searchable online via the Pwned Passwords page](https://haveibeenpwned.com/Passwords).

### Installation

pwnedpasswords is available for download through [PyPi][pypi-url]. You can install it right away using pip.

```bash
pip install pwnedpasswords
```

### Usage

```python
import pwnedpasswords

pwnedpasswords.check("testing 123")
# Returns 1
```

#### Security Note

No plaintext passwords ever leave your machine using pwnedpasswords.

How does that work? Well, the Pwned Passwords v2 API has a pretty cool [k-anonymity][k-anonymous-url] implementation.

From https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/:

> Formally, a data set can be said to hold the property of k-anonymity, if for every record in a released table, there are k − 1 other records identical to it.

This allows us to only provide the first 5 characters of the SHA-1 hash of the password in question. The API then responds with a list of SHA-1 hash suffixes with that prefix. On average, that list contains 478 results.

People smarter than I am have used [math](https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/) to prove that 5-character prefixes are sufficient to maintain k-anonymity for this database.

In short: your plaintext passwords are protected if you use this library. You won't leak enough data to identity which passwords you're searching for.

#### Notes

pwnedpasswords automatically checks if your provided input looks like a SHA-1 hash. If it does, it won't do any further processing. If it looks like plain text, it'll automatically hash it before sending it to the Pwned Passwords API.

If you'd like to provide an already-hashed password as input to pwnedpasswords, you don't need to do anything--pwnedpasswords will detect that it looks like a SHA-1 hash and won't hash it again before sending it to the `range` endpoint.

```python
pwnedpasswords.check("b8dfb080bc33fb564249e34252bf143d88fc018f")
```

Likewise, if a password looks like a SHA-1 hash (i.e., matches the regex `[0-9a-fA-F]{40}`) but is actually a user-provided password, set `plain_text` to `True`, so that the library knows to hash it before sending it to the API.

```python
pwnedpasswords.check("1231231231231231231231231231231231231231", plain_text=True)
```

## Details

### `check`

This is the preferred method. By default, the `check` method uses the `https://api.pwnedpasswords.com/range/` endpoint, which is [k-anonymous][k-anonymous-url].

```python
pwnedpasswords.check("mypassword")
# 34729
```

If you'd like to force pwnedpasswords to use the search endpoint instead (https://api.pwnedpasswords.com/pwnedpassword/), set the `anonymous` parameter to `False`.

```python
pwnedpasswords.check("password", anonymous=False)
# 3303003
```

You might want to do this if you'd prefer faster response times, and aren't that worried about leaking passwords you're searching for over the network.

If you'd like direct access to the search and range endpoints, you can also call them directly.

### `range`

```python
pwnedpasswords.range("098765")
# outputs a dictionary mapping SHA-1 hash suffixes to frequency counts
```

## Command Line Utility

pwnedpasswords comes bundled with a handy command line utility.

```bash
$ pwnedpasswords 123456password
240
```

Output is simply the number of entries found in the Pwned Passwords database.

If you'd like to prevent input from appearing in your history, specify the `--stdin` argument to provide input via stdin (h/t to [@tveastman](https://github.com/tveastman) for requesting this).

```bash
$ pwnedpasswords --stdin
mypassword
34729
```

For help, just provide `-h` as a command-line argument.

```bash
$ pwnedpasswords -h
usage: pwnedpasswords [-h] [--verbose] [--plain-text] (--stdin | password)

Checks Pwned Passwords API to see if provided plaintext data was found in a
data breach.

positional arguments:
  password      The password or hashed password to search for.

optional arguments:
  -h, --help    show this help message and exit
  --verbose     Display verbose output.
  --plain-text  Specify that the provided input is plain text, even if it
                looks like a SHA-1 hash.
  --stdin       Read provided input from stdin.
```

#### Note

The CLI returns an exit code equal to the base-10 log of the result count, plus 1. If there are no matches in the API, the exit status will be `0`. While returning the base-10 log might seem odd, note that most systems require exit status codes to be in the range 0-127, and I wanted the status code to provide *some* indication for severity. log(N) seemed to be a good tradeoff. The exit status is log(N)+1 since there are plenty of matches in the database with 1 match.

If you'd like to take a look under the hood to make sure things are working as they should, set the `--verbose` flag.

```bash
$ pwnedpasswords 123456password --verbose
INFO:pwnedpasswords.pwnedpasswords:https://api.pwnedpasswords.com/range/5052C
INFO:pwnedpasswords.pwnedpasswords:Entry found
240
```

## Thanks

Special thanks to [Troy Hunt](https://www.troyhunt.com) for collecting this data and providing this service.

## Authors

Dan Loewenherz / ([@dlo](https://github.com/dlo))
Matt Hoffman / ([@mjhoffman65](https://github.com/mjhoffman65))
Hoan Ton-That / ([@hoan](https://github.com/hoan))
Thomas Kafetzis / ([@KafetzisThomas](https://github.com/KafetzisThomas))

## See also

[django-pwnedpasswords-validator](https://github.com/lionheart/django-pwnedpasswords-validator), a validator that checks user passwords against the Pwned Passwords API using this library.

## License

Apache License, Version 2.0. See [LICENSE](LICENSE) for details.

[ci-badge]: https://img.shields.io/travis/lionheart/pwnedpasswords.svg?style=flat
[version-badge]: https://img.shields.io/pypi/v/pwnedpasswords.svg?style=flat
[versions-badge]: https://img.shields.io/pypi/pyversions/pwnedpasswords.svg?style=flat

[travis-repo-url]: https://travis-ci.org/lionheart/pwnedpasswords
[k-anonymous-url]: https://en.wikipedia.org/wiki/K-anonymity
[semver-url]: http://www.semver.org
[pypi-url]: https://pypi.python.org/pypi/pwnedpasswords
[lionheart-url]: https://lionheartsw.com/


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/lionheart/pwnedpasswords",
    "name": "pwnedpasswords",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "passwords security",
    "author": "Dan Loewenherz",
    "author_email": "dan@lionheartsw.com",
    "download_url": "https://github.com/lionheart/pwnedpasswords/tarball/3.0.0",
    "platform": null,
    "description": "<!-- <p align=\"center\">\n  <img width=\"344\" height=\"225\" src=\"meta/repo-banner-small.png\" />\n</p> -->\n\n![](meta/repo-banner.png)\n[![](meta/repo-banner-bottom.png)][lionheart-url]\n\n[![CI Status][ci-badge]][travis-repo-url]\n[![Version][version-badge]][pypi-url]\n[![Python Versions][versions-badge]][pypi-url]\n\n`pwnedpasswords` is a small Python wrapper and command line utility that lets you check if a passphrase has been pwned using the [Pwned Passwords v2 API](https://haveibeenpwned.com/API/v2#PwnedPasswords). All provided password data is [k-anonymized][k-anonymous-url] before sending to the API, so plaintext passwords never leave your computer.\n\nFrom https://haveibeenpwned.com/API/v2#PwnedPasswords:\n\n> Pwned Passwords are more than half a billion passwords which have previously been exposed in data breaches. The service is detailed in the [launch blog post](https://www.troyhunt.com/introducing-306-million-freely-downloadable-pwned-passwords/) then [further expanded on with the release of version 2](https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2). The entire data set is [both downloadable and searchable online via the Pwned Passwords page](https://haveibeenpwned.com/Passwords).\n\n### Installation\n\npwnedpasswords is available for download through [PyPi][pypi-url]. You can install it right away using pip.\n\n```bash\npip install pwnedpasswords\n```\n\n### Usage\n\n```python\nimport pwnedpasswords\n\npwnedpasswords.check(\"testing 123\")\n# Returns 1\n```\n\n#### Security Note\n\nNo plaintext passwords ever leave your machine using pwnedpasswords.\n\nHow does that work? Well, the Pwned Passwords v2 API has a pretty cool [k-anonymity][k-anonymous-url] implementation.\n\nFrom https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/:\n\n> Formally, a data set can be said to hold the property of k-anonymity, if for every record in a released table, there are k \u2212 1 other records identical to it.\n\nThis allows us to only provide the first 5 characters of the SHA-1 hash of the password in question. The API then responds with a list of SHA-1 hash suffixes with that prefix. On average, that list contains 478 results.\n\nPeople smarter than I am have used [math](https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/) to prove that 5-character prefixes are sufficient to maintain k-anonymity for this database.\n\nIn short: your plaintext passwords are protected if you use this library. You won't leak enough data to identity which passwords you're searching for.\n\n#### Notes\n\npwnedpasswords automatically checks if your provided input looks like a SHA-1 hash. If it does, it won't do any further processing. If it looks like plain text, it'll automatically hash it before sending it to the Pwned Passwords API.\n\nIf you'd like to provide an already-hashed password as input to pwnedpasswords, you don't need to do anything--pwnedpasswords will detect that it looks like a SHA-1 hash and won't hash it again before sending it to the `range` endpoint.\n\n```python\npwnedpasswords.check(\"b8dfb080bc33fb564249e34252bf143d88fc018f\")\n```\n\nLikewise, if a password looks like a SHA-1 hash (i.e., matches the regex `[0-9a-fA-F]{40}`) but is actually a user-provided password, set `plain_text` to `True`, so that the library knows to hash it before sending it to the API.\n\n```python\npwnedpasswords.check(\"1231231231231231231231231231231231231231\", plain_text=True)\n```\n\n## Details\n\n### `check`\n\nThis is the preferred method. By default, the `check` method uses the `https://api.pwnedpasswords.com/range/` endpoint, which is [k-anonymous][k-anonymous-url].\n\n```python\npwnedpasswords.check(\"mypassword\")\n# 34729\n```\n\nIf you'd like to force pwnedpasswords to use the search endpoint instead (https://api.pwnedpasswords.com/pwnedpassword/), set the `anonymous` parameter to `False`.\n\n```python\npwnedpasswords.check(\"password\", anonymous=False)\n# 3303003\n```\n\nYou might want to do this if you'd prefer faster response times, and aren't that worried about leaking passwords you're searching for over the network.\n\nIf you'd like direct access to the search and range endpoints, you can also call them directly.\n\n### `range`\n\n```python\npwnedpasswords.range(\"098765\")\n# outputs a dictionary mapping SHA-1 hash suffixes to frequency counts\n```\n\n## Command Line Utility\n\npwnedpasswords comes bundled with a handy command line utility.\n\n```bash\n$ pwnedpasswords 123456password\n240\n```\n\nOutput is simply the number of entries found in the Pwned Passwords database.\n\nIf you'd like to prevent input from appearing in your history, specify the `--stdin` argument to provide input via stdin (h/t to [@tveastman](https://github.com/tveastman) for requesting this).\n\n```bash\n$ pwnedpasswords --stdin\nmypassword\n34729\n```\n\nFor help, just provide `-h` as a command-line argument.\n\n```bash\n$ pwnedpasswords -h\nusage: pwnedpasswords [-h] [--verbose] [--plain-text] (--stdin | password)\n\nChecks Pwned Passwords API to see if provided plaintext data was found in a\ndata breach.\n\npositional arguments:\n  password      The password or hashed password to search for.\n\noptional arguments:\n  -h, --help    show this help message and exit\n  --verbose     Display verbose output.\n  --plain-text  Specify that the provided input is plain text, even if it\n                looks like a SHA-1 hash.\n  --stdin       Read provided input from stdin.\n```\n\n#### Note\n\nThe CLI returns an exit code equal to the base-10 log of the result count, plus 1. If there are no matches in the API, the exit status will be `0`. While returning the base-10 log might seem odd, note that most systems require exit status codes to be in the range 0-127, and I wanted the status code to provide *some* indication for severity. log(N) seemed to be a good tradeoff. The exit status is log(N)+1 since there are plenty of matches in the database with 1 match.\n\nIf you'd like to take a look under the hood to make sure things are working as they should, set the `--verbose` flag.\n\n```bash\n$ pwnedpasswords 123456password --verbose\nINFO:pwnedpasswords.pwnedpasswords:https://api.pwnedpasswords.com/range/5052C\nINFO:pwnedpasswords.pwnedpasswords:Entry found\n240\n```\n\n## Thanks\n\nSpecial thanks to [Troy Hunt](https://www.troyhunt.com) for collecting this data and providing this service.\n\n## Authors\n\nDan Loewenherz / ([@dlo](https://github.com/dlo))\nMatt Hoffman / ([@mjhoffman65](https://github.com/mjhoffman65))\nHoan Ton-That / ([@hoan](https://github.com/hoan))\nThomas Kafetzis / ([@KafetzisThomas](https://github.com/KafetzisThomas))\n\n## See also\n\n[django-pwnedpasswords-validator](https://github.com/lionheart/django-pwnedpasswords-validator), a validator that checks user passwords against the Pwned Passwords API using this library.\n\n## License\n\nApache License, Version 2.0. See [LICENSE](LICENSE) for details.\n\n[ci-badge]: https://img.shields.io/travis/lionheart/pwnedpasswords.svg?style=flat\n[version-badge]: https://img.shields.io/pypi/v/pwnedpasswords.svg?style=flat\n[versions-badge]: https://img.shields.io/pypi/pyversions/pwnedpasswords.svg?style=flat\n\n[travis-repo-url]: https://travis-ci.org/lionheart/pwnedpasswords\n[k-anonymous-url]: https://en.wikipedia.org/wiki/K-anonymity\n[semver-url]: http://www.semver.org\n[pypi-url]: https://pypi.python.org/pypi/pwnedpasswords\n[lionheart-url]: https://lionheartsw.com/\n\n",
    "bugtrack_url": null,
    "license": "Apache 2.0",
    "summary": "A Python wrapper for Troy Hunt's Pwned Passwords API.",
    "version": "3.0.0",
    "project_urls": {
        "Download": "https://github.com/lionheart/pwnedpasswords/tarball/3.0.0",
        "Homepage": "https://github.com/lionheart/pwnedpasswords"
    },
    "split_keywords": [
        "passwords",
        "security"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d567f45939847b4f771f97af2953a6c05d281255ce2fb9c88275604200a57114",
                "md5": "2869991a1083a97426166af99191f7af",
                "sha256": "4fe89ae75ba91dc4698ca721432b80032a5b648507e2bfefb57b13570ef82d76"
            },
            "downloads": -1,
            "filename": "pwnedpasswords-3.0.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2869991a1083a97426166af99191f7af",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 13623,
            "upload_time": "2025-08-24T13:42:36",
            "upload_time_iso_8601": "2025-08-24T13:42:36.480600Z",
            "url": "https://files.pythonhosted.org/packages/d5/67/f45939847b4f771f97af2953a6c05d281255ce2fb9c88275604200a57114/pwnedpasswords-3.0.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-24 13:42:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "lionheart",
    "github_project": "pwnedpasswords",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": false,
    "circle": true,
    "lcname": "pwnedpasswords"
}
        
Elapsed time: 0.95638s