Latest commit 03630e8 Feb 20, 2017 @ofek complete tox
Failed to load latest commit information.
privy change security level 7 Feb 18, 2017
tests version 2.0.0 Feb 15, 2017
.codecov.yml init Feb 13, 2017
.coveragerc init Feb 13, 2017
.gitattributes :neckbeard: Added .gitattributes & .gitignore files Feb 13, 2017
.gitignore init Feb 13, 2017
.travis.yml update docs and give credit Feb 14, 2017
LICENSE.txt init Feb 13, 2017
README.rst change security level 7 Feb 18, 2017
TODO.rst init Feb 13, 2017 update Feb 14, 2017 init Feb 13, 2017 4.0.0 Feb 18, 2017
tox.ini complete tox Feb 21, 2017 init Feb 13, 2017


Privy: Password-protected secrets made easy

Privy is a small and fast utility for password-protecting secrets such as seeds for digital signatures or Bitcoin wallets.


Say for example you are using GnuPG. You are about to sign a message but it first requires your password. Does your password become the input to instantiate your private key? No, it is first hashed by a secure key derivation function. That hash then becomes the input to a symmetric cipher such as AES which then decrypts your stored private key. That is what Privy does.

Fear not! With Privy, this become trivially easy:

>>> import privy
>>> # After creating secret, immediately encrypt it using Privy.
>>> secret = b'secret'
>>> hidden = privy.hide(secret, ask_for_password())
>>> hidden

Now you can safely store or transmit the hidden secret. Whenever your user needs to use their secret again, ask for their password to take a peek.

>>> privy.peek(hidden, password)


Privy is available on Linux/macOS and Windows and supports Python 2.7, 3.3+, PyPy, and PyPy3.3-5.5+.

$ pip install privy

Encryption scheme

Secrets are encrypted using the Fernet protocol. Specifically, it uses AES for encryption and has built-in authentication using HMAC. The private key used for encryption is derived from the password using a key derivation function. The key derivation function used is Argon2, the winner of the Password Hashing Competition. Both Argon2i and Argon2d variants are supported.

Encrypted format

ascii(Argon2 algorithm || security level || base64(salt) || base64(Fernet token))


There are 2 functions: hide and peek.

hide(secret, password, security=2, salt=None, server=True)

Encrypts secret using password. Returns the hidden secret as unicode.

  • Parameters
    • secret (bytes) - The secret to encrypt.
    • password (bytes or unicode) - The password used to access the secret.
    • security (int) - A number 0-20 inclusive. Higher values are more secure at the cost of slower computation and greater use of memory. See security levels.
    • salt (bytes) - The salt used for the password hash. Defaults to os.urandom(32).
    • server (bool) - If True, it is assumed side-channel attack protection is needed and therefore the Argon2i algorithm will be used. Otherwise, the password will be hashed using the Argon2d algorithm.

peek(hidden, password, expires=None)

Decrypts hidden using password. Returns the secret as bytes.

  • Parameters
    • hidden (bytes or unicode) - The hidden secret to decrypt.
    • password (bytes or unicode) - The password used to access the secret.
    • expires (int) - The maximum number of seconds since encryption that is allowed. The default is no expiration.

A ValueError will be raised if the password is wrong, the password was attempted on a different hidden secret, or the number of seconds since encryption is > expires argument.

Security levels

All expected times were taken from tests on an Intel Core i7-2670QM @ 2.2 GHz when decrypting a 256 KiB secret.

This is the command, where SL is the desired security level:

$ python -m timeit -s "import privy, os; pw = 'password'; s = os.urandom(1024 * 256); h = privy.hide(s, pw, SL)" "privy.peek(h, pw)"
Levels Argon2 settings Expected time Notes
0 m=8 KiB, t=1 7 msec Lowest possible
1 m=4 MiB, t=10 54 msec  
2 m=8 MiB, t=10 99 msec Default
3 m=32 MiB, t=10 367 msec  
4 m=48 MiB, t=10 540 msec  
5 m=96 MiB, t=10 1.1 sec Good choice
6 m=256 MiB, t=10 3 sec  
7 m=512 MiB, t=10 6 sec  
8 m=768 MiB, t=10 9 sec  
9 m=1 GiB, t=10 12.2 sec  
10 m=2 GiB, t=20 48 sec For use on users' machines
11 m=3 GiB, t=30 107
12 m=4 GiB, t=40 ?
13 m=5 GiB, t=50 ?
14 m=6 GiB, t=60 ?
15 m=7 GiB, t=70 ?
16 m=8 GiB, t=80 ?
17 m=9 GiB, t=90 ?
18 m=10 GiB, t=100 ?
19 m=11 GiB, t=110 ?
20 m=12 GiB, t=120 ?


Important changes are emphasized.


  • Breaking: For saner conformity, security level 7 now utilizes 512 MiB of RAM instead of 448.
  • Major improvements to documentation.


  • Added security levels 11-20. These are quite resource intensive and are therefore only acceptable for individual use.


  • Breaking: Due to requests, the encrypted format now uses url-safe base64 instead of hex.


  • Initial release