|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from Crypto.Util.py3compat import * |
|
|
|
|
|
from Crypto.Util.asn1 import ( |
|
|
DerNull, |
|
|
DerSequence, |
|
|
DerObjectId, |
|
|
DerOctetString, |
|
|
) |
|
|
|
|
|
from Crypto.IO._PBES import PBES1, PBES2, PbesError |
|
|
|
|
|
|
|
|
__all__ = ['wrap', 'unwrap'] |
|
|
|
|
|
|
|
|
def wrap(private_key, key_oid, passphrase=None, protection=None, |
|
|
prot_params=None, key_params=DerNull(), randfunc=None): |
|
|
"""Wrap a private key into a PKCS#8 blob (clear or encrypted). |
|
|
|
|
|
Args: |
|
|
|
|
|
private_key (byte string): |
|
|
The private key encoded in binary form. The actual encoding is |
|
|
algorithm specific. In most cases, it is DER. |
|
|
|
|
|
key_oid (string): |
|
|
The object identifier (OID) of the private key to wrap. |
|
|
It is a dotted string, like ``1.2.840.113549.1.1.1`` (for RSA keys). |
|
|
|
|
|
passphrase (bytes string or string): |
|
|
The secret passphrase from which the wrapping key is derived. |
|
|
Set it only if encryption is required. |
|
|
|
|
|
protection (string): |
|
|
The identifier of the algorithm to use for securely wrapping the key. |
|
|
The default value is ``PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC``. |
|
|
|
|
|
prot_params (dictionary): |
|
|
Parameters for the protection algorithm. |
|
|
|
|
|
+------------------+-----------------------------------------------+ |
|
|
| Key | Description | |
|
|
+==================+===============================================+ |
|
|
| iteration_count | The KDF algorithm is repeated several times to| |
|
|
| | slow down brute force attacks on passwords | |
|
|
| | (called *N* or CPU/memory cost in scrypt). | |
|
|
| | The default value for PBKDF2 is 1000. | |
|
|
| | The default value for scrypt is 16384. | |
|
|
+------------------+-----------------------------------------------+ |
|
|
| salt_size | Salt is used to thwart dictionary and rainbow | |
|
|
| | attacks on passwords. The default value is 8 | |
|
|
| | bytes. | |
|
|
+------------------+-----------------------------------------------+ |
|
|
| block_size | *(scrypt only)* Memory-cost (r). The default | |
|
|
| | value is 8. | |
|
|
+------------------+-----------------------------------------------+ |
|
|
| parallelization | *(scrypt only)* CPU-cost (p). The default | |
|
|
| | value is 1. | |
|
|
+------------------+-----------------------------------------------+ |
|
|
|
|
|
key_params (DER object or None): |
|
|
The ``parameters`` field to use in the ``AlgorithmIdentifier`` |
|
|
SEQUENCE. If ``None``, no ``parameters`` field will be added. |
|
|
By default, the ASN.1 type ``NULL`` is used. |
|
|
|
|
|
randfunc (callable): |
|
|
Random number generation function; it should accept a single integer |
|
|
N and return a string of random data, N bytes long. |
|
|
If not specified, a new RNG will be instantiated |
|
|
from :mod:`Crypto.Random`. |
|
|
|
|
|
Return: |
|
|
The PKCS#8-wrapped private key (possibly encrypted), as a byte string. |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if key_params is None: |
|
|
algorithm = DerSequence([DerObjectId(key_oid)]) |
|
|
else: |
|
|
algorithm = DerSequence([DerObjectId(key_oid), key_params]) |
|
|
|
|
|
pk_info = DerSequence([ |
|
|
0, |
|
|
algorithm, |
|
|
DerOctetString(private_key) |
|
|
]) |
|
|
pk_info_der = pk_info.encode() |
|
|
|
|
|
if passphrase is None: |
|
|
return pk_info_der |
|
|
|
|
|
if not passphrase: |
|
|
raise ValueError("Empty passphrase") |
|
|
|
|
|
|
|
|
passphrase = tobytes(passphrase) |
|
|
if protection is None: |
|
|
protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC' |
|
|
return PBES2.encrypt(pk_info_der, passphrase, |
|
|
protection, prot_params, randfunc) |
|
|
|
|
|
|
|
|
def unwrap(p8_private_key, passphrase=None): |
|
|
"""Unwrap a private key from a PKCS#8 blob (clear or encrypted). |
|
|
|
|
|
Args: |
|
|
p8_private_key (byte string): |
|
|
The private key wrapped into a PKCS#8 blob, DER encoded. |
|
|
passphrase (byte string or string): |
|
|
The passphrase to use to decrypt the blob (if it is encrypted). |
|
|
|
|
|
Return: |
|
|
A tuple containing |
|
|
|
|
|
#. the algorithm identifier of the wrapped key (OID, dotted string) |
|
|
#. the private key (byte string, DER encoded) |
|
|
#. the associated parameters (byte string, DER encoded) or ``None`` |
|
|
|
|
|
Raises: |
|
|
ValueError : if decoding fails |
|
|
""" |
|
|
|
|
|
if passphrase: |
|
|
passphrase = tobytes(passphrase) |
|
|
|
|
|
found = False |
|
|
try: |
|
|
p8_private_key = PBES1.decrypt(p8_private_key, passphrase) |
|
|
found = True |
|
|
except PbesError as e: |
|
|
error_str = "PBES1[%s]" % str(e) |
|
|
except ValueError: |
|
|
error_str = "PBES1[Invalid]" |
|
|
|
|
|
if not found: |
|
|
try: |
|
|
p8_private_key = PBES2.decrypt(p8_private_key, passphrase) |
|
|
found = True |
|
|
except PbesError as e: |
|
|
error_str += ",PBES2[%s]" % str(e) |
|
|
except ValueError: |
|
|
error_str += ",PBES2[Invalid]" |
|
|
|
|
|
if not found: |
|
|
raise ValueError("Error decoding PKCS#8 (%s)" % error_str) |
|
|
|
|
|
pk_info = DerSequence().decode(p8_private_key, nr_elements=(2, 3, 4, 5)) |
|
|
if len(pk_info) == 2 and not passphrase: |
|
|
raise ValueError("Not a valid clear PKCS#8 structure " |
|
|
"(maybe it is encrypted?)") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if pk_info[0] == 0: |
|
|
if len(pk_info) not in (3, 4): |
|
|
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE") |
|
|
elif pk_info[0] == 1: |
|
|
if len(pk_info) not in (3, 4, 5): |
|
|
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE") |
|
|
else: |
|
|
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE") |
|
|
|
|
|
algo = DerSequence().decode(pk_info[1], nr_elements=(1, 2)) |
|
|
algo_oid = DerObjectId().decode(algo[0]).value |
|
|
if len(algo) == 1: |
|
|
algo_params = None |
|
|
else: |
|
|
try: |
|
|
DerNull().decode(algo[1]) |
|
|
algo_params = None |
|
|
except: |
|
|
algo_params = algo[1] |
|
|
|
|
|
|
|
|
private_key = DerOctetString().decode(pk_info[2]).payload |
|
|
|
|
|
|
|
|
|
|
|
return (algo_oid, private_key, algo_params) |
|
|
|