| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| __all__ = ['encode', 'decode'] |
|
|
| import re |
| from binascii import a2b_base64, b2a_base64, hexlify, unhexlify |
|
|
| from Crypto.Hash import MD5 |
| from Crypto.Util.Padding import pad, unpad |
| from Crypto.Cipher import DES, DES3, AES |
| from Crypto.Protocol.KDF import PBKDF1 |
| from Crypto.Random import get_random_bytes |
| from Crypto.Util.py3compat import tobytes, tostr |
|
|
|
|
| def encode(data, marker, passphrase=None, randfunc=None): |
| """Encode a piece of binary data into PEM format. |
| |
| Args: |
| data (byte string): |
| The piece of binary data to encode. |
| marker (string): |
| The marker for the PEM block (e.g. "PUBLIC KEY"). |
| Note that there is no official master list for all allowed markers. |
| Still, you can refer to the OpenSSL_ source code. |
| passphrase (byte string): |
| If given, the PEM block will be encrypted. The key is derived from |
| the passphrase. |
| randfunc (callable): |
| Random number generation function; it accepts an integer N and returns |
| a byte string of random data, N bytes long. If not given, a new one is |
| instantiated. |
| |
| Returns: |
| The PEM block, as a string. |
| |
| .. _OpenSSL: https://github.com/openssl/openssl/blob/master/include/openssl/pem.h |
| """ |
|
|
| if randfunc is None: |
| randfunc = get_random_bytes |
|
|
| out = "-----BEGIN %s-----\n" % marker |
| if passphrase: |
| |
| salt = randfunc(8) |
| key = PBKDF1(passphrase, salt, 16, 1, MD5) |
| key += PBKDF1(key + passphrase, salt, 8, 1, MD5) |
| objenc = DES3.new(key, DES3.MODE_CBC, salt) |
| out += "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,%s\n\n" %\ |
| tostr(hexlify(salt).upper()) |
| |
| data = objenc.encrypt(pad(data, objenc.block_size)) |
| elif passphrase is not None: |
| raise ValueError("Empty password") |
|
|
| |
| |
| chunks = [tostr(b2a_base64(data[i:i + 48])) |
| for i in range(0, len(data), 48)] |
| out += "".join(chunks) |
| out += "-----END %s-----" % marker |
| return out |
|
|
|
|
| def _EVP_BytesToKey(data, salt, key_len): |
| d = [ b'' ] |
| m = (key_len + 15 ) // 16 |
| for _ in range(m): |
| nd = MD5.new(d[-1] + data + salt).digest() |
| d.append(nd) |
| return b"".join(d)[:key_len] |
|
|
|
|
| def decode(pem_data, passphrase=None): |
| """Decode a PEM block into binary. |
| |
| Args: |
| pem_data (string): |
| The PEM block. |
| passphrase (byte string): |
| If given and the PEM block is encrypted, |
| the key will be derived from the passphrase. |
| |
| Returns: |
| A tuple with the binary data, the marker string, and a boolean to |
| indicate if decryption was performed. |
| |
| Raises: |
| ValueError: if decoding fails, if the PEM file is encrypted and no passphrase has |
| been provided or if the passphrase is incorrect. |
| """ |
|
|
| |
| r = re.compile(r"\s*-----BEGIN (.*)-----\s+") |
| m = r.match(pem_data) |
| if not m: |
| raise ValueError("Not a valid PEM pre boundary") |
| marker = m.group(1) |
|
|
| |
| r = re.compile(r"-----END (.*)-----\s*$") |
| m = r.search(pem_data) |
| if not m or m.group(1) != marker: |
| raise ValueError("Not a valid PEM post boundary") |
|
|
| |
| lines = pem_data.replace(" ", '').split() |
|
|
| |
| if lines[1].startswith('Proc-Type:4,ENCRYPTED'): |
| if not passphrase: |
| raise ValueError("PEM is encrypted, but no passphrase available") |
| DEK = lines[2].split(':') |
| if len(DEK) != 2 or DEK[0] != 'DEK-Info': |
| raise ValueError("PEM encryption format not supported.") |
| algo, salt = DEK[1].split(',') |
| salt = unhexlify(tobytes(salt)) |
|
|
| padding = True |
|
|
| if algo == "DES-CBC": |
| key = _EVP_BytesToKey(passphrase, salt, 8) |
| objdec = DES.new(key, DES.MODE_CBC, salt) |
| elif algo == "DES-EDE3-CBC": |
| key = _EVP_BytesToKey(passphrase, salt, 24) |
| objdec = DES3.new(key, DES3.MODE_CBC, salt) |
| elif algo == "AES-128-CBC": |
| key = _EVP_BytesToKey(passphrase, salt[:8], 16) |
| objdec = AES.new(key, AES.MODE_CBC, salt) |
| elif algo == "AES-192-CBC": |
| key = _EVP_BytesToKey(passphrase, salt[:8], 24) |
| objdec = AES.new(key, AES.MODE_CBC, salt) |
| elif algo == "AES-256-CBC": |
| key = _EVP_BytesToKey(passphrase, salt[:8], 32) |
| objdec = AES.new(key, AES.MODE_CBC, salt) |
| elif algo.lower() == "id-aes256-gcm": |
| key = _EVP_BytesToKey(passphrase, salt[:8], 32) |
| objdec = AES.new(key, AES.MODE_GCM, nonce=salt) |
| padding = False |
| else: |
| raise ValueError("Unsupport PEM encryption algorithm (%s)." % algo) |
| lines = lines[2:] |
| else: |
| objdec = None |
|
|
| |
| data = a2b_base64(''.join(lines[1:-1])) |
| enc_flag = False |
| if objdec: |
| if padding: |
| data = unpad(objdec.decrypt(data), objdec.block_size) |
| else: |
| |
| data = objdec.decrypt(data) |
| enc_flag = True |
|
|
| return (data, marker, enc_flag) |
|
|