# # PublicKey/_PBES.py : Password-Based Encryption functions # # =================================================================== # The contents of this file are dedicated to the public domain. To # the extent that dedication to the public domain is not available, # everyone is granted a worldwide, perpetual, royalty-free, # non-exclusive license to exercise all rights associated with the # contents of this file for any purpose whatsoever. # No rights are reserved. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # =================================================================== import sys if sys.version_info[0] == 2 and sys.version_info[1] == 1: from Crypto.Util.py21compat import * from Crypto.Util.py3compat import * from Crypto import Random from Crypto.Util.asn1 import * from Crypto.Util.Padding import pad, unpad from Crypto.Hash import MD5, SHA1 from Crypto.Cipher import DES, ARC2, DES3, AES from Crypto.Protocol.KDF import PBKDF1, PBKDF2 # These are the ASN.1 definitions used by the PBES1/2 logic: # # EncryptedPrivateKeyInfo ::= SEQUENCE { # encryptionAlgorithm EncryptionAlgorithmIdentifier, # encryptedData EncryptedData # } # # EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier # # EncryptedData ::= OCTET STRING # # AlgorithmIdentifier ::= SEQUENCE { # algorithm OBJECT IDENTIFIER, # parameters ANY DEFINED BY algorithm OPTIONAL # } # # PBEParameter ::= SEQUENCE { # salt OCTET STRING (SIZE(8)), # iterationCount INTEGER # } # # PBES2-params ::= SEQUENCE { # keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}}, # encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} # } # # PBKDF2-params ::= SEQUENCE { # salt CHOICE { # specified OCTET STRING, # otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}} # }, # iterationCount INTEGER (1..MAX), # keyLength INTEGER (1..MAX) OPTIONAL, # prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 # } # def decode_der(obj_class, binstr): """Instantiate a DER object class, decode a DER binary string in it, and return the object.""" der = obj_class() der.decode(binstr) return der class PBES1(object): """Deprecated encryption scheme with password-based key derivation (originally defined in PKCS#5 v1.5, but still present in `v2.0`__). .. __: http://www.ietf.org/rfc/rfc2898.txt """ def decrypt(data, passphrase): """Decrypt a piece of data using a passphrase and *PBES1*. The algorithm to use is automatically detected. :Parameters: data : byte string The piece of data to decrypt. passphrase : byte string The passphrase to use for decrypting the data. :Returns: The decrypted data, as a binary string. """ encrypted_private_key_info = decode_der(DerSequence, data) encrypted_algorithm = decode_der( DerSequence, encrypted_private_key_info[0] ) encrypted_data = decode_der( DerOctetString, encrypted_private_key_info[1] ).payload pbe_oid = decode_der(DerObjectId, encrypted_algorithm[0]).value cipher_params = {} if pbe_oid == "1.2.840.113549.1.5.3": # PBE_MD5_DES_CBC hashmod = MD5 ciphermod = DES elif pbe_oid == "1.2.840.113549.1.5.6": # PBE_MD5_RC2_CBC hashmod = MD5 ciphermod = ARC2 cipher_params['effective_keylen'] = 64 elif pbe_oid == "1.2.840.113549.1.5.10": # PBE_SHA1_DES_CBC hashmod = SHA1 ciphermod = DES elif pbe_oid == "1.2.840.113549.1.5.11": # PBE_SHA1_RC2_CBC hashmod = SHA1 ciphermod = ARC2 cipher_params['effective_keylen'] = 64 else: raise ValueError("Unknown OID") pbe_params = decode_der(DerSequence, encrypted_algorithm[1]) salt = decode_der(DerOctetString, pbe_params[0]).payload iterations = pbe_params[1] key_iv = PBKDF1(passphrase, salt, 16, iterations, hashmod) key, iv = key_iv[:8], key_iv[8:] cipher = ciphermod.new(key, ciphermod.MODE_CBC, iv, **cipher_params) pt = cipher.decrypt(encrypted_data) return unpad(pt, cipher.block_size) decrypt = staticmethod(decrypt) class PBES2(object): """Encryption scheme with password-based key derivation (defined in `PKCS#5 v2.0`__). .. __: http://www.ietf.org/rfc/rfc2898.txt.""" def encrypt(data, passphrase, protection, prot_params=None, randfunc=None): """Encrypt a piece of data using a passphrase and *PBES2*. :Parameters: data : byte string The piece of data to encrypt. passphrase : byte string The passphrase to use for encrypting the data. protection : string The identifier of the encryption algorithm to use. The default value is '``PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC``'. prot_params : dictionary Parameters of the protection algorithm. +------------------+-----------------------------------------------+ | Key | Description | +==================+===============================================+ | iteration_count | The KDF algorithm is repeated several times to| | | slow down brute force attacks on passwords. | | | The default value is 1 000. | +------------------+-----------------------------------------------+ | salt_size | Salt is used to thwart dictionary and rainbow | | | attacks on passwords. The default value is 8 | | | bytes. | +------------------+-----------------------------------------------+ 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 ``Crypto.Random``. :Returns: The encrypted data, as a binary string. """ if prot_params is None: prot_params = {} if randfunc is None: randfunc = Random.new().read if protection == 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC': key_size = 24 module = DES3 protection = DES3.MODE_CBC enc_oid = "1.2.840.113549.3.7" elif protection == 'PBKDF2WithHMAC-SHA1AndAES128-CBC': key_size = 16 module = AES protection = AES.MODE_CBC enc_oid = "2.16.840.1.101.3.4.1.2" elif protection == 'PBKDF2WithHMAC-SHA1AndAES192-CBC': key_size = 24 module = AES protection = AES.MODE_CBC enc_oid = "2.16.840.1.101.3.4.1.22" elif protection == 'PBKDF2WithHMAC-SHA1AndAES256-CBC': key_size = 32 module = AES protection = AES.MODE_CBC enc_oid = "2.16.840.1.101.3.4.1.42" else: raise ValueError("Unknown mode") # Get random data iv = randfunc(module.block_size) salt = randfunc(prot_params.get("salt_size", 8)) # Derive key from password count = prot_params.get("iteration_count", 1000) key = PBKDF2(passphrase, salt, key_size, count) key_derivation_func = newDerSequence( DerObjectId("1.2.840.113549.1.5.12"), # PBKDF2 newDerSequence( DerOctetString(salt), DerInteger(count) ) ) # Create cipher and use it cipher = module.new(key, protection, iv) encrypted_data = cipher.encrypt(pad(data, cipher.block_size)) encryption_scheme = newDerSequence( DerObjectId(enc_oid), DerOctetString(iv) ) # Result encrypted_private_key_info = newDerSequence( # encryptionAlgorithm newDerSequence( DerObjectId("1.2.840.113549.1.5.13"), # PBES2 newDerSequence( key_derivation_func, encryption_scheme ), ), DerOctetString(encrypted_data) ) return encrypted_private_key_info.encode() encrypt = staticmethod(encrypt) def decrypt(data, passphrase): """Decrypt a piece of data using a passphrase and *PBES2*. The algorithm to use is automatically detected. :Parameters: data : byte string The piece of data to decrypt. passphrase : byte string The passphrase to use for decrypting the data. :Returns: The decrypted data, as a binary string. """ encrypted_private_key_info = decode_der(DerSequence, data) encryption_algorithm = decode_der( DerSequence, encrypted_private_key_info[0] ) encrypted_data = decode_der( DerOctetString, encrypted_private_key_info[1] ).payload pbe_oid = decode_der(DerObjectId, encryption_algorithm[0]).value if pbe_oid != "1.2.840.113549.1.5.13": raise ValueError("Not a PBES2 object") pbes2_params = decode_der(DerSequence, encryption_algorithm[1]) ### Key Derivation Function selection key_derivation_func = decode_der(DerSequence, pbes2_params[0]) key_derivation_oid = decode_der( DerObjectId, key_derivation_func[0] ).value # For now, we only support PBKDF2 if key_derivation_oid != "1.2.840.113549.1.5.12": raise ValueError("Unknown KDF") pbkdf2_params = decode_der(DerSequence, key_derivation_func[1]) salt = decode_der(DerOctetString, pbkdf2_params[0]).payload iteration_count = pbkdf2_params[1] if len(pbkdf2_params) > 2: pbkdf2_key_length = pbkdf2_params[2] else: pbkdf2_key_length = None if len(pbkdf2_params) > 3: raise ValueError("Unsupported PRF for PBKDF2") ### Cipher selection encryption_scheme = decode_der(DerSequence, pbes2_params[1]) encryption_oid = decode_der( DerObjectId, encryption_scheme[0] ).value if encryption_oid == "1.2.840.113549.3.7": # DES_EDE3_CBC ciphermod = DES3 key_size = 24 elif encryption_oid == "2.16.840.1.101.3.4.1.2": # AES128_CBC ciphermod = AES key_size = 16 elif encryption_oid == "2.16.840.1.101.3.4.1.22": # AES192_CBC ciphermod = AES key_size = 24 elif encryption_oid == "2.16.840.1.101.3.4.1.42": # AES256_CBC ciphermod = AES key_size = 32 else: raise ValueError("Unsupported cipher") if pbkdf2_key_length and pbkdf2_key_length != key_size: raise ValueError("Mismatch between PBKDF2 parameters" " and selected cipher") IV = decode_der(DerOctetString, encryption_scheme[1]).payload # Create cipher key = PBKDF2(passphrase, salt, key_size, iteration_count) cipher = ciphermod.new(key, ciphermod.MODE_CBC, IV) # Decrypt data pt = cipher.decrypt(encrypted_data) return unpad(pt, cipher.block_size) decrypt = staticmethod(decrypt)