summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Encryption.py
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-09-20 16:23:25 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-09-20 16:23:25 -0400
commit48c584194e4e5ec4b3561b2d6448ba4728ab0739 (patch)
treea4e2900d06d260ebde50cdf861769ef096c638af /src/lib/Bcfg2/Encryption.py
parentcf0583059bbcecbb655924afdbf16d51122703b2 (diff)
downloadbcfg2-48c584194e4e5ec4b3561b2d6448ba4728ab0739.tar.gz
bcfg2-48c584194e4e5ec4b3561b2d6448ba4728ab0739.tar.bz2
bcfg2-48c584194e4e5ec4b3561b2d6448ba4728ab0739.zip
Encryption: improved docs, made algorithm configurable
Diffstat (limited to 'src/lib/Bcfg2/Encryption.py')
-rwxr-xr-xsrc/lib/Bcfg2/Encryption.py149
1 files changed, 122 insertions, 27 deletions
diff --git a/src/lib/Bcfg2/Encryption.py b/src/lib/Bcfg2/Encryption.py
index 5e70a1d10..5eb7ffe8e 100755
--- a/src/lib/Bcfg2/Encryption.py
+++ b/src/lib/Bcfg2/Encryption.py
@@ -1,18 +1,37 @@
-#!/usr/bin/python -Ott
+""" Bcfg2.Encryption provides a number of convenience methods for
+handling encryption in Bcfg2. See :ref:`server-encryption` for more
+details. """
import os
-import base64
from M2Crypto import Rand
from M2Crypto.EVP import Cipher, EVPError
-from Bcfg2.Compat import StringIO, md5
+from Bcfg2.Compat import StringIO, md5, b64encode, b64decode
+#: Constant representing the encryption operation for
+#: :class:`M2Crypto.EVP.Cipher`, which uses a simple integer. This
+#: makes our code more readable.
ENCRYPT = 1
+
+#: Constant representing the decryption operation for
+#: :class:`M2Crypto.EVP.Cipher`, which uses a simple integer. This
+#: makes our code more readable.
DECRYPT = 0
+
+#: Default cipher algorithm. To get a full list of valid algorithms,
+#: you can run::
+#:
+#: openssl list-cipher-algorithms | grep -v ' => ' | \
+#: tr 'A-Z-' 'a-z_' | sort -u
ALGORITHM = "aes_256_cbc"
+
+#: Default initialization vector. For best security, you should use a
+#: unique IV for each message. :func:`ssl_encrypt` does this in an
+#: automated fashion.
IV = '\0' * 16
Rand.rand_seed(os.urandom(1024))
+
def _cipher_filter(cipher, instr):
inbuf = StringIO(instr)
outbuf = StringIO()
@@ -27,54 +46,127 @@ def _cipher_filter(cipher, instr):
outbuf.close()
return rv
+
def str_encrypt(plaintext, key, iv=IV, algorithm=ALGORITHM, salt=None):
- """ encrypt a string """
+ """ Encrypt a string with a key. For a higher-level encryption
+ interface, see :func:`ssl_encrypt`.
+
+ :param plaintext: The plaintext data to encrypt
+ :type plaintext: string
+ :param key: The key to encrypt the data with
+ :type key: string
+ :param iv: The initialization vector
+ :type iv: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :param salt: The salt to use
+ :type salt: string
+ :returns: string - The decrypted data
+ """
cipher = Cipher(alg=algorithm, key=key, iv=iv, op=ENCRYPT, salt=salt)
return _cipher_filter(cipher, plaintext)
-
+
+
def str_decrypt(crypted, key, iv=IV, algorithm=ALGORITHM):
- """ decrypt a string """
+ """ Decrypt a string with a key. For a higher-level decryption
+ interface, see :func:`ssl_decrypt`.
+
+ :param crypted: The raw binary encrypted data
+ :type crypted: string
+ :param key: The encryption key to decrypt with
+ :type key: string
+ :param iv: The initialization vector
+ :type iv: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :returns: string - The decrypted data
+ """
cipher = Cipher(alg=algorithm, key=key, iv=iv, op=DECRYPT)
return _cipher_filter(cipher, crypted)
-
+
+
def ssl_decrypt(data, passwd, algorithm=ALGORITHM):
- """ decrypt openssl-encrypted data """
+ """ Decrypt openssl-encrypted data. This can decrypt data
+ encrypted by :func:`ssl_encrypt`, or ``openssl enc``. It performs
+ a base64 decode first if the data is base64 encoded, and
+ automatically determines the salt and initialization vector (both
+ of which are embedded in the encrypted data).
+
+ :param data: The encrypted data (either base64-encoded or raw
+ binary) to decrypt
+ :type data: string
+ :param passwd: The password to use to decrypt the data
+ :type passwd: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :returns: string - The decrypted data
+ """
# base64-decode the data if necessary
try:
- data = base64.b64decode(data)
+ data = b64decode(data)
except TypeError:
# already decoded
pass
-
+
salt = data[8:16]
hashes = [md5(passwd + salt).digest()]
- for i in range(1,3):
- hashes.append(md5(hashes[i-1] + passwd + salt).digest())
+ for i in range(1, 3):
+ hashes.append(md5(hashes[i - 1] + passwd + salt).digest())
key = hashes[0] + hashes[1]
iv = hashes[2]
-
- return str_decrypt(data[16:], key=key, iv=iv)
+
+ return str_decrypt(data[16:], key=key, iv=iv, algorithm=algorithm)
+
def ssl_encrypt(plaintext, passwd, algorithm=ALGORITHM, salt=None):
- """ encrypt data in a format that is openssl compatible """
+ """ Encrypt data in a format that is openssl compatible.
+
+ :param plaintext: The plaintext data to encrypt
+ :type plaintext: string
+ :param passwd: The password to use to encrypt the data
+ :type passwd: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :param salt: The salt to use. If none is provided, one will be
+ randomly generated.
+ :type salt: bytes
+ :returns: string - The base64-encoded, salted, encrypted string.
+ The string includes a trailing newline to make it fully
+ compatible with openssl command-line tools.
+ """
if salt is None:
salt = Rand.rand_bytes(8)
-
+
hashes = [md5(passwd + salt).digest()]
- for i in range(1,3):
- hashes.append(md5(hashes[i-1] + passwd + salt).digest())
+ for i in range(1, 3):
+ hashes.append(md5(hashes[i - 1] + passwd + salt).digest())
key = hashes[0] + hashes[1]
iv = hashes[2]
-
- crypted = str_encrypt(plaintext, key=key, salt=salt, iv=iv)
- return base64.b64encode("Salted__" + salt + crypted) + "\n"
+
+ crypted = str_encrypt(plaintext, key=key, salt=salt, iv=iv,
+ algorithm=algorithm)
+ return b64encode("Salted__" + salt + crypted) + "\n"
+
+
+def get_algorithm(setup):
+ """ Get the cipher algorithm from the config file. This is used
+ in case someone uses the OpenSSL algorithm name (e.g.,
+ "AES-256-CBC") instead of the M2Crypto name (e.g., "aes_256_cbc"),
+ and to handle errors in a sensible way and deduplicate this code.
+
+ :param setup: The Bcfg2 option set to extract passphrases from
+ :type setup: Bcfg2.Options.OptionParser
+ :returns: dict - a dict of ``<passphrase name>``: ``<passphrase>``
+ """
+ return setup.cfp.get("encryption", "algorithm",
+ default=ALGORITHM).lower().replace("-", "_")
def get_passphrases(setup):
""" Get all candidate encryption passphrases from the config file.
:param setup: The Bcfg2 option set to extract passphrases from
:type setup: Bcfg2.Options.OptionParser
- returns: dict - a dict of <passphrase name>: <passphrase>
+ :returns: dict - a dict of ``<passphrase name>``: ``<passphrase>``
"""
section = "encryption"
if setup.cfp.has_section(section):
@@ -83,11 +175,13 @@ def get_passphrases(setup):
else:
return dict()
-def bruteforce_decrypt(crypted, passphrases=None, setup=None):
+
+def bruteforce_decrypt(crypted, passphrases=None, setup=None,
+ algorithm=ALGORITHM):
""" Convenience method to decrypt the given encrypted string by
trying the given passphrases or all passphrases (as returned by
- :func:`Bcfg2.Encryption.passphrases`) sequentially until one is
- found that works.
+ :func:`get_passphrases`) sequentially until one is found that
+ works.
Either ``passphrases`` or ``setup`` must be provided.
@@ -97,6 +191,8 @@ def bruteforce_decrypt(crypted, passphrases=None, setup=None):
:type passphrases: list
:param setup: A Bcfg2 option set to extract passphrases from
:type setup: Bcfg2.Options.OptionParser
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
:returns: string - The decrypted data
:raises: :class:`M2Crypto.EVP.EVPError`, if the data cannot be decrypted
"""
@@ -104,8 +200,7 @@ def bruteforce_decrypt(crypted, passphrases=None, setup=None):
passphrases = get_passphrases(setup).values()
for passwd in passphrases:
try:
- return ssl_decrypt(crypted, passwd)
+ return ssl_decrypt(crypted, passwd, algorithm=algorithm)
except EVPError:
pass
raise EVPError("Failed to decrypt")
-