Skip to content

jsdalton/secrets.py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Warning: Use this library at your own risk! Consider Sodium and its accompanying Python library PyNaCl for an implementation that affords true security guarantees and has been thoroughly vetted by cryptographic experts.

Secrets.py

Secrets.py is a small Python library that makes it easy to encrypt and decrypt both messages and files. It's really just a friendly interface to some of the cryptographic tools in the PyCrypto library.

Usage

Encrypt and Decrypt

To simply encrypt or decrypt a string, all that's required is a key.

>>> import secrets
>>> msg = "One if by land; two if by sea"
>>> key = "swordfish"
>>> encrypted_msg = secrets.encrypt(key, msg)
>>> encrypted_msg
'ZnSzzAj6q9Ug2bDWB3RBFjFCEANLgYUC9Ji/uVpx9n+GP9OVBRZIW+50M24J'
>>> secrets.decrypt(key, encrypted_msg)
'One if by land; two if by sea'

By default, encrypted messages are base64 encoded. Messages can be binary encoded by passing base64_encode=False to encrypt() (and base64_decode=False to decrypt() the message).

Verified Encrypt and Decrypt

Messages can also be encrypted in a "verifiable" format via verify_encrypt(), which allows you to verify() a key:

>>> msg = "One if by land; two if by sea"
>>> key = "swordfish"
>>> encrypted_msg = secrets.verified_encrypt(key, msg)
>>> secrets.verify("Bad password!", encrypted_msg)
False
>>> secrets.verify(key, encrypted_msg)
True

verified_decrypt() will return None if the key is wrong or the message is corrupt:

>>> print secrets.verified_decrypt("Bad password!", encrypted_msg)
None
>>> print secrets.verified_decrypt(key, "blahblahblah")
None

Whereas the correct key results in the original message:

>>> secrets.verified_decrypt(key, encrypted_msg)
'One if by land; two if by sea'

Without using verified decryption, it's difficult (but not impossible) to determine programatically if a decrypted message is "correct" or not. Compare:

>>> encrypted_msg = secrets.encrypt(key, msg)
>>> secrets.decrypt(key, encrypted_msg)
'One if by land; two if by sea'
>>> secrets.decrypt("Bad password!", encrypted_msg)
'\xbf\xfd \x88W3\x11\xf4\xc3_J\xd0p\xbd\xf9d\x8bBn\x12\xea7\xf7\xdb\x87\x8d\x02zh'

Encrypt and Decrypt Files

You can also encrypt and decrypt files via a similar API:

>>> import secrets
>>> key = "swordfish"
>>> with open('config.ini', 'r') as f:
...   f.read()
... 
'[Values]\nfoo = bar\nbar = baz\n\n[More Values]\nthis = that\nsalt = pepper\n'
>>> secrets.encrypt_file(key, 'config.ini')
>>> with open('config.ini.encrypted', 'r') as f:
...   f.read()
... 
'r\xbc\x1a\xf9\x82\xa1\xb4r&\xce\xf6r\xc3\x1fTJ\x9a\x1bi\xb3\x89oh.?\xcc\xfc\x86<\x0b\xc8|\xfb\xd5\x91\xe0\x91\xe1\ni\xe1\xed\xa7\xfba\x90]\xbc\xb6R\x9b&d]3hC#\xe2\xc4\xa2\xe7+\x7ff\x82D\xca\xce\xc7\xd8\xa6\x9f\x91\x9b\x95\xe8u\x0b\xd1\xd1\x80\xfe\xed3\xc7'
>>> secrets.decrypt_file(key, 'config.ini.encrypted', output_filename='config.ini.decrypted')
>>> with open('config.ini.decrypted', 'r') as f:
...   f.read()
... 
'[Values]\nfoo = bar\nbar = baz\n\n[More Values]\nthis = that\nsalt = pepper\n'

Files are encrypted in binary format.

Verified Encrypt and Decrypt Files

As you might expect, there is a also a "verified" version of encrypt and decrypt for files:

>>> key = "swordfish"
>>> secrets.verified_encrypt_file(key, 'config.ini')
>>> with open('config.ini.encrypted', 'r') as f:
...   f.read()
... 
'\xd0`\xea`\x04X>2P\x82\x1eo\xc1\xc0\x19\t\x8a\xc6\x8d\xd4\x89j\xfeT\\\xbcDiT{\xa83\x8b<b\xfbO\x0f1T\xc6\xbc\xe3\xca\x03|[\x93\xcbY\x08\xb0l&\x87\xadJ>\x87\xdd\xb9\x0e(\x1c\x1b\xffs\x02\x19\xd8R\xefT\xff\x10\xbd\x1b\x89M\xc8O\xc5X\xa3\xd2rf\x1d\x80\xdb\xd1?\xfd\xfaX\xde\xe6\x87\x9f\xa0\xd0\xc1\xaa\x88\r\xf8\x11\xbe\xe1\x83m\xc6\xbbz\xae\xccgX'

verify_file() confirms that the key and file contents are correct:

>>> secrets.verify_file("Bad password!", 'config.ini.encrypted')
False
>>> secrets.verify_file(key, 'config.ini')
False
>>> secrets.verify_file(key, 'config.ini.encrypted')
True

If either the key or file contents are incorrect, verified_decrypt_file() raises a ValueError:

>>> secrets.verified_decrypt_file("Bad password!", 'config.ini.encrypted')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "secrets/__init__.py", line 67, in verified_decrypt_file
  File "secrets/cryptors.py", line 143, in decrypt_file
ValueError: Bad key or contents of input_filename.
>>> secrets.verified_decrypt_file(key, 'config.ini')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "secrets/__init__.py", line 67, in verified_decrypt_file
  File "secrets/cryptors.py", line 143, in decrypt_file
ValueError: Bad key or contents of input_filename.

Whereas the correct key will silently succeed:

>>> secrets.verified_decrypt_file(key, 'config.ini.encrypted', output_filename='config.ini.decrypted')
>>> with open('config.ini.decrypted', 'r') as f:
...   f.read()
... 
'[Values]\nfoo = bar\nbar = baz\n\n[More Values]\nthis = that\nsalt = pepper\n'

As with decrypt(), decrypt_file() will result in binary garbage if the key is wrong or the encrypted message is corrupt.

Granular Control

If you need finer control than the main API provides, use the Cryptor and FileCryptor classes. These classes offer additional input/output options as well as a choice of the cipher and digest modes used, while still offering a more straightforward API than PyCrypto.

Security Notes

Secrets is just a simplified wrapper around PyCrypto, which handles the majority of the actual cryptography. A few notes:

  • You probably should not be using this library. It has not been vetted by cryptographic experts. It is unlikely that it is worse than using PyCrypto directly, but there are more secure options such as Keyczar and Sodium/PyNaCl. Use it at your own risk.
  • Definitely don't use this to store decryptable passwords. Passwords should be stored with a one-way hash like bcrypt.
  • By default, Secrets uses the AES cipher in CFB mode. The Cryptor class can optionally be initialized with other cipher classes from PyCrypto. It has been tested with DES and Blowfish. Padding is set automatically with the PyCrypto Random module.
  • Secrets derives the key used for encryption from the provided key using PBKDF2. PBKDF2 requires a random salt, which is generated automatically and prepended to the encrypted file/string for use in decryption. The key length is the maximum allowed for the chosen cipher class.
  • The verified_* functions all work by prepending an HMAC digest to the encrypted message. HMAC provides a high degree of security, since the correct digest can only be derived from the key and the correctly decrypted message. (The purpose of these functions is, for example, to protect a correctly decrypted file from being overwritten with garbage if an incorrect key is inadvertently provided.) The HMAC digest is computed with specified digest mode (hashlib.sha256 by default).
  • Version 0.2 fixes a potential security flaw where the key could be exposed by testing the time it takes to verify the hash.
  • Please contact me directly or open an issue if you observe any security flaws minor or severe in this library.

About

Simplified encryption and decryption of strings and files.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages