def decrypt_c(rw, c): """ Cette méthode va dechiffrer c :param rw: la clé qui va permettre de dériver les autres clé :param c: le texte chiffré séparé par une , avec son mac :return: le texte c déchiffré """ values = c.split(",") data = values[0] mac = values[1] # KDF on calcule une fois avec 0 et une fois avec 1. Pour le sel la documentation # conseille quelque chose d'une longueur de 16bytes qui n'a pas besoin d'être secret # c'est pourquoi j'ai choisi de prendre le SSID car le client doit aussi connaitre le sel key_aes = scrypt('\x00'.encode() + rw, SSID, 32, N=2 ** 14, r=8, p=1) key_hmac = scrypt('\x01'.encode() + rw, SSID, 32, N=2 ** 14, r=8, p=1) h = HMAC.new(key_hmac, digestmod=SHA256) h.update(a2b_hex(data)) try: h.hexverify(mac) cipher = AES.new(key_aes, AES.MODE_CTR, nonce=bytearray(1)) plaintext = cipher.decrypt(a2b_hex(data)) return plaintext.decode() except ValueError as e: raise e
def test3(self): ref = scrypt(b("password"), b("salt"), 12, 16, 1, 1) # Same output, but this time split over 2 keys key1, key2 = scrypt(b("password"), b("salt"), 6, 16, 1, 1, 2) self.assertEqual((ref[:6], ref[6:]), (key1, key2)) # Same output, but this time split over 3 keys key1, key2, key3 = scrypt(b("password"), b("salt"), 4, 16, 1, 1, 3) self.assertEqual((ref[:4], ref[4:8], ref[8:]), (key1, key2, key3))
def benchmark(): iters = 2**16 start_time = timeit.default_timer() scrypt(password='******', salt=secrets.token_bytes(32), key_len=32, N=iters, r=r, p=p) elapsed = timeit.default_timer() - start_time predicted = elapsed * N / iters print("Key derivation will take about {} seconds ({} per second).".format( round(predicted, 2), round(1/predicted, 3) ))
def hashed_password_verify(hashed_password: str, password: str): if ':' not in hashed_password: # pragma: no cover return False salt_hex, h_hex = hashed_password.split(':', 2) salt = bytes.fromhex(salt_hex) h = bytes.fromhex(h_hex) return secrets.compare_digest(scrypt(password, salt, 16, N=2**14, r=8, p=1), h) # type: ignore
def _hash(self, password): return scrypt(password, salt=self.hash_salt, key_len=32, N=2**12, r=8, p=1)
def Encrypt_AES(): Plaintext = InputText.get("1.0", "end").rstrip() Plaintext_Byte = Plaintext.encode() Plaintext_Byte_Pad = pad(Plaintext_Byte, AES.block_size) # The encoded input data is padded to fit the AES block size (16-Byte). OutputProgress["value"] = 25 Root.update_idletasks() time.sleep(0.05) # ------------------------------------------------------ # KDF: if SelectedAlgorithm.get() == "AES-128": KeyLength = 16 elif SelectedAlgorithm.get() == "AES-192": KeyLength = 24 elif SelectedAlgorithm.get() == "AES-256": KeyLength = 32 OutputProgress["value"] = 50 Root.update_idletasks() time.sleep(0.05) Password = PasswordEntry.get().rstrip() Password_Encode = Password.encode() Salt = get_random_bytes(16) Key = scrypt(Password, Salt, KeyLength, N=2**20, r=8, p=1) # - ( 2¹⁴, 8, 1 ) for interactive logins (≤100ms) # - ( 2²⁰, 8, 1 ) for file encryption (≤5s) # ------------------------------------------------------ # IV: An "initialization vector" is a 16-Byte randomly generated string that randomizes the digest for ciphertext generation, similar to a seed. # 16-Byte Key (128-bit) # 24-Byte Key (192-bit) # 32-Byte Key (256-bit) OutputProgress["value"] = 75 Root.update_idletasks() time.sleep(0.05) Cipher = AES.new(Key, AES.MODE_CBC) Ciphertext = Cipher.encrypt(Plaintext_Byte_Pad) SaltEntry.delete(0, "end") SaltEntry.insert(0, base64.encodebytes(Salt)) VectorEntry.delete(0, "end") VectorEntry.insert(0, base64.encodebytes(Cipher.iv)) OutputText.delete(1.0, "end") OutputText.insert(1.0, base64.encodebytes(Ciphertext)) OutputText.bind("<Key>", lambda a: "break") OutputProgress["value"] = 100 Root.update_idletasks() time.sleep(0.05) OutputProgress["value"] = 0 Root.update_idletasks()
def encryptFile(self, file_in, file_out=''): try: # because server should never have access to plaintext file data with open(self.clientAddress + '/' + file_in, 'rb') as f: data = f.read() # derive file key from user password salt = get_random_bytes(16) fileKey = scrypt(self.filePassword.encode('utf-8'), salt, 16, N=2**20, r=8, p=1) # encrypt the data with the file key cipher_aes = AES.new(fileKey, AES.MODE_GCM) ciphertext, tag = cipher_aes.encrypt_and_digest(data) # if we want to write to another file if file_out != '': with open(self.clientAddress + '/' + file_out, 'wb') as f: [ f.write(x) for x in (salt, cipher_aes.nonce, tag, ciphertext) ] return salt + cipher_aes.nonce + tag + ciphertext except: print("Encryption Failed") return None
def encryptPayload(passwd: bytes, salt: bytes, iv: bytes, payload: bytes): key = scrypt(password=passwd, salt=salt, N=(2**14), key_len=32, r=8, p=1) #print(key, iv, salt) cipher = ChaCha20.new(key=key, nonce=iv) rv = cipher.encrypt(payload) print(rv) return rv
def encrypt(inpath, outpath, password): filename = os.path.basename(inpath) salt = secrets.token_bytes(32) print("Deriving key.") key = scrypt(password=password, salt=salt, key_len=32, N=N, r=r, p=p) print("Reading file.") with open(inpath, 'rb') as inf: in_data = pad(inf.read(), 16) print("Encrypting.") e = AES.new(key, AES.MODE_ECB) out_data = e.encrypt(in_data) #EtM h = SHA3_512.new(key + out_data).digest() padded_filename = pad(filename.encode('utf-8')[:255], 256) e_filename = e.encrypt(padded_filename) print("Writing file.") with open(outpath, 'wb') as outf: outf.write(salt) outf.write(h) outf.write(e_filename) outf.write(out_data)
def generate_scrypt_key(password, salt=None): ##return bytes of keys, returns list in case of keys > 1 ##returns hex encoded salt and key byte array if not salt: salt = os.urandom(16) keys = scrypt(password, salt, KEY_LENGTH, N, R, P, 1) return keys, salt.hex()
def kdf(self, salt): """ Derives the actual aeskey from a given salt and the saved key. """ # pylint: disable=C0415 from Crypto.Protocol.KDF import scrypt return scrypt(self.key, salt, key_len=32, N=2**17, r=8, p=1)
def encapsulado(id): #Importamos la libreria de kyber from pqcrypto.kem.kyber512 import generate_keypair, encrypt, decrypt #recibimos la public key public_key = sock.recv(800) #Se realiza el encapsulado y se obtenemos el CT y el PT ciphertext, plaintext_original = encrypt(public_key) #Enviamos el CT sock.sendall(ciphertext) #Concatenamos el CT con el ID del cliente ct_hash = ciphertext + id #Realizamos el hash al CT con el ID hash = SHA256.new() hash.update(ct_hash) ct_hash = hash.digest() #Enviamos el hash sock.sendall(ct_hash) #data = sock.recv(1024) #print(len(data)) #print(compare_digest(plaintext_original, data)) #time.sleep(1) #salt = b'1234567891123456' #con una funcion KDF se obtiene una llave a traves del PT y el hash key = scrypt(plaintext_original, ct_hash, 16, N=2**14, r=8, p=1) return key, ct_hash[0:16]
def _password_expansion(salt_ghid, password, hardness=None): ''' Expands the author's ghid and password into a master key for use in generating specific keys. Hardness allows you to modify the scrypt inflation parameter. It defaults to something resembling a reasonable general-purpose value for 2016. ''' # Use 2**14 for t<=100ms, 2**20 for t<=5s. if hardness is None: hardness = _DEFAULT_SCRYPT_HARDNESS else: hardness = int(hardness) # Scrypt the password. Salt against the author GHID. combined = scrypt( password = password, salt = bytes(salt_ghid), key_len = 48, N = hardness, r = 8, p = 1 ) key = combined[0:32] seed = combined[32:48] master_secret = Secret( cipher = 1, version = 'latest', key = key, seed = seed ) return master_secret
def decryptPayload(passwd: bytes, salt: bytes, iv: bytes, payload: bytes): key = scrypt(password=passwd, salt=salt, N=(2**14), key_len=32, r=8, p=1) cipher = ChaCha20.new(key=key, nonce=iv) rv = cipher.decrypt(payload) print(payload) # print(rv) return rv.decode(encoding='UTF-8')
def _password_expansion(salt_ghid, password, hardness=None): ''' Expands the author's ghid and password into a master key for use in generating specific keys. Hardness allows you to modify the scrypt inflation parameter. It defaults to something resembling a reasonable general-purpose value for 2016. ''' # Use 2**14 for t<=100ms, 2**20 for t<=5s. if hardness is None: hardness = _DEFAULT_SCRYPT_HARDNESS else: hardness = int(hardness) # Scrypt the password. Salt against the author GHID. combined = scrypt(password=password, salt=bytes(salt_ghid), key_len=48, N=hardness, r=8, p=1) key = combined[0:32] seed = combined[32:48] master_secret = Secret(cipher=1, version='latest', key=key, seed=seed) return master_secret
def decryptFile(self, path, data=None): path = self.clientAddress + '/' + path # parse a given file if data is empty if data == None: file_in = open(path, 'rb') salt, nonce, tag, ciphertext = \ [file_in.read(x) for x in (16, 16, 16, -1)] file_in.close() # read in data if given else: salt, nonce, tag, ciphertext = \ [data[x:x+16] for x in (0, 16, 32, -1)] ciphertext = data[48:] # Generate file key from given filePassword fileKey = scrypt(self.filePassword.encode('utf-8'), salt, 16, N=2**20, r=8, p=1) # Decrypt the data with the file key cipher_aes = AES.new(fileKey, AES.MODE_GCM, nonce) data = cipher_aes.decrypt_and_verify(ciphertext, tag) with open(path, 'wb') as f: f.write(data)
def Scrypt(password: Union[bytes, str], salt: Union[bytes, str], key_len: int, n: int, r: int, p: int) -> bytes: """ Compute the scrypt of the specified password, using the specified parameters. Args: password (str or bytes): Password salt (str or bytes) : Salt key_len (int) : Length of the derived key n (int) : CPU/Memory cost parameter r (int) : Block size parameter p (int) : Parallelization parameter Returns: bytes: Computed scrypt """ # Type for password and salt should be Union[bytes, str] in pycryptodome but it's only str, # so we ignore the mypy warning return scrypt( AlgoUtils.Encode(password), # type: ignore AlgoUtils.Encode(salt), # type: ignore key_len=key_len, N=n, r=r, p=p)
def decrypt_AES(input_filename, password): # The decrypted file # output_filename = os.path.splitext(filename)[0] output_filename = input_filename.replace('.encrypted', '') # Open files file_in = open(input_filename, 'rb') file_out = open(output_filename, 'wb') # Read salt and generate key salt = file_in.read(32) # The salt we generated was 32 bits long key = scrypt(password, salt, key_len=32, N=2**17, r=8, p=1) # Generate a key using the password and salt again # Read nonce and create cipher nonce = file_in.read(16) # The nonce is 16 bytes long cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) # Identify how many bytes of encrypted there is # We know that the salt (32) + the nonce (16) + the data (?) + the tag (16) is in the file # So some basic algebra can tell us how much data we need to read to decrypt file_in_size = os.path.getsize(input_filename) encrypted_data_size = file_in_size - 32 - 16 - 16 # Total - salt - nonce - tag = encrypted data # Read, decrypt and write the data for _ in range( int(encrypted_data_size / BUFFER_SIZE) ): # Identify how many loops of full buffer reads we need to do data = file_in.read( BUFFER_SIZE) # Read in some data from the encrypted file decrypted_data = cipher.decrypt(data) # Decrypt the data file_out.write( decrypted_data) # Write the decrypted data to the output file data = file_in.read( int(encrypted_data_size % BUFFER_SIZE )) # Read whatever we have calculated to be left of encrypted data decrypted_data = cipher.decrypt(data) # Decrypt the data file_out.write( decrypted_data) # Write the decrypted data to the output file # Verify encrypted file was not tampered with tag = file_in.read(16) try: cipher.verify(tag) except ValueError as e: # If we get a ValueError, there was an error when decrypting so we delete the file we created file_in.close() file_out.close() os.remove(output_filename) raise e # If everything is okay, we close the files file_in.close() file_out.close()
def encrypt_bytes(self, plain_text): salt = get_random_bytes(8); key = scrypt(self.password, salt=salt, key_len=self.key_len//8, N=16384, r=8, p=1); nonce = get_random_bytes(16); cipher = AES.new(key=key, mode=AES.MODE_GCM, nonce=nonce, mac_len=16); cipher.update(b'WozMit'); ciphertext, tag = cipher.encrypt_and_digest(plain_text); return (self.key_len//64).to_bytes(1, byteorder='big') + salt + nonce + tag + ciphertext;
def encryption_key(self, salt=None): if not salt: self.salt = os.urandom(self.salt_size) else: self.salt = salt key = scrypt(self.password, self.salt, self.key_size, N=2**15, r=8, p=1) return key
def generate_scrypt_key(password, salt, key_length): ##return bytes of keys, returns list in case of keys > 1 ##returns hex encoded salt and key byte array logger.debug( f"Generating scrypt key with {password} and salt {salt}, with keylength {N}, R {R} and p {P}" ) keys = scrypt(password, salt, key_length, N, R, P, 1) return keys, salt
def _utc_kdf_scrypt(pwd: bytes, kdf_params: Dict) -> bytes: """ Derive key using Scrypt. Accept KDF params from Ether UTC file. """ # Convert salt from HEX to binary data salt = binascii.unhexlify(kdf_params["salt"]) dklen = kdf_params["dklen"] N, r, p = kdf_params["n"], kdf_params["r"], kdf_params["p"] # Get derived key return scrypt(pwd, salt, key_len=dklen, N=N, r=r, p=p, num_keys=1)
def test2(self): for tv in self.data: # TODO: add runtime flag to enable test vectors # with humongous memory usage if tv.N > 100000: continue output = scrypt(tv.P, tv.S, tv.dkLen, tv.N, tv.r, tv.p) self.assertEqual(output, tv.output)
def getCrypter(keyRaw, kdfSalt, nonce): ''' (bytes, bytes, bytes) -> ChaCha20_Poly1305 Derives the encryption key from provided parameters Sets up and returns a ChaCha20_Poly1305 instance ''' keyDerived = scrypt(password = keyRaw, salt = kdfSalt, key_len = 32, \ N = SCRYPT_ITER, r=8, p=1) return ChaCha20_Poly1305.new(key = keyDerived, nonce = nonce)
def make_password(value, salt=None): salt = salt or get_random_string(32) password_hash = scrypt(password=value, salt=salt, key_len=32, N=16384, r=8, p=1) password_encoded = base64.b64encode(password_hash) return f'{salt}${password_encoded.decode()}'
def encrypt(pt, password): salt = get_random_bytes(32) key = scrypt(password, salt, key_len=32, N=2**20, r=8, p=1) nonce = get_random_bytes(12) cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) ct, tag = cipher.encrypt_and_digest(pt) json_k = ["nonce", "salt", "ct", "tag"] json_v = [b64encode(x).decode("utf-8") for x in (nonce, salt, ct, tag)] result = json.dumps(dict(zip(json_k, json_v))) return result
def getPrivateKey(self, path): with open(path, 'rb') as f: salt, nonce, tag, ciphertext = \ [f.read(x) for x in (16, 16, 16, -1)] fileKey = scrypt(self.password.encode('utf-8'), salt, 16, N=2**20, r=8, p=1) cipher_aes = AES.new(fileKey, AES.MODE_GCM, nonce) rsaKey = cipher_aes.decrypt_and_verify(ciphertext, tag) self.serverRSAprivate = RSA.import_key(rsaKey) print("Key Loaded")
def get_data_key(shared_secret, server_random) -> str: d_key_length = 64 #based on Colin Percival suggested choice of Parameters for scrypt in 2009 # since data key will be used for repo(file) encryption we use N = 2^20 # print('Scripting...') data_key = scrypt(shared_secret, server_random, key_len=d_key_length, N=2**20, r=8, p=1) # print('Done Scripting....') dk_obj = SHA256.new(data_key) return dk_obj.hexdigest()
def _scrypt_hash(password, salt, n, r, p, buflen): derived_key = scrypt( password, salt=salt, key_len=buflen, N=n, r=r, p=p, num_keys=1, ) return derived_key
def encrypt(plaintext: str, key: str) -> str: """ encrypt text using AES256 GCM and return output in base64 """ salt = get_random_bytes(16) secret_key = scrypt(key, str(salt), key_len=32, N=2**17, r=8, p=1) cipher = AES.new(secret_key, AES.MODE_GCM) cipher_text, auth_tag = cipher.encrypt_and_digest( bytes(plaintext, encoding="utf-8")) encrypted_bytes = b"".join([salt, cipher.nonce, auth_tag, cipher_text]) return b64encode(encrypted_bytes).decode("utf-8")
def compute_c(key, p_u, P_u, P_s): """ Cette methode va concatener certaines infos généré pour un certain client puis le chiffrer. On utilise AES et HMAC, de ce fait il nous faut deux clés. On va donc utiliser une KDF. :param key: master key qui sera utilisé pour générer les autres clés :return: le text chiffré ainsi que le mac """ # KDF on calcule une fois avec 0 et une fois avec 1. Pour le sel la documentation # conseille quelque chose d'une longueur de 16bytes qui n'a pas besoin d'être secret # c'est pourquoi j'ai choisi de prendre le SSID car le client doit aussi connaitre le sel key_aes = scrypt('\x00'.encode() + key, SSID, 32, N=2 ** 14, r=8, p=1) key_hmac = scrypt('\x01'.encode() + key, SSID, 32, N=2 ** 14, r=8, p=1) cipher = AES.new(key_aes, AES.MODE_CTR, nonce=bytearray(1)) # On concatène avec des séparateurs les datas que on veut chiffrer pour pouvoir les récuperer après data = str(p_u) + ";" + str(P_u[0]) + "," + str(P_u[1]) + ";" + str(P_s[0]) + "," + str(P_s[1]) c = cipher.encrypt(data.encode()) h = HMAC.new(key_hmac, digestmod=SHA256) h.update(c) return b2a_hex(c), h.hexdigest()
def test2(self): for tv in self.data: try: output = scrypt(tv.P, tv.S, tv.dkLen, tv.N, tv.r, tv.p) except ValueError as e: if " 2 " in str(e) and tv.N >= 1048576: import warnings warnings.warn("Not enough memory to unit test scrypt() with N=1048576", RuntimeWarning) continue else: raise e self.assertEqual(output, tv.output)
def _make_password_validator(secret, hardness=None): ''' Re-scrypts the key (should be the scrypt expansion of the password) with hardness, and then compares the result against check_str. If wrong, raises ValueError for a bad password (and logs as such). ''' if hardness is None: hardness = _DEFAULT_SCRYPT_HARDNESS else: hardness = int(hardness) checker = scrypt( password = secret.key, salt = bytes(_PASSWORD_VALIDATOR_LEN), key_len = _PASSWORD_VALIDATOR_LEN, N = hardness, r = 8, p = 1 ) return checker
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. """ enc_private_key_info = DerSequence().decode(data, nr_elements=2) enc_algo = DerSequence().decode(enc_private_key_info[0]) encrypted_data = DerOctetString().decode(enc_private_key_info[1]).payload pbe_oid = DerObjectId().decode(enc_algo[0]).value if pbe_oid != _OID_PBES2: raise PbesError("Not a PBES2 object") pbes2_params = DerSequence().decode(enc_algo[1], nr_elements=2) ### Key Derivation Function selection kdf_info = DerSequence().decode(pbes2_params[0], nr_elements=2) kdf_oid = DerObjectId().decode(kdf_info[0]).value kdf_key_length = None # We only support PBKDF2 or scrypt if kdf_oid == _OID_PBKDF2: pbkdf2_params = DerSequence().decode(kdf_info[1], nr_elements=(2, 3, 4)) salt = DerOctetString().decode(pbkdf2_params[0]).payload iteration_count = pbkdf2_params[1] left = len(pbkdf2_params) - 2 idx = 2 if left > 0: try: kdf_key_length = pbkdf2_params[idx] - 0 left -= 1 idx += 1 except TypeError: pass # Default is HMAC-SHA1 pbkdf2_prf_oid = "1.2.840.113549.2.7" if left > 0: pbkdf2_prf_algo_id = DerSequence().decode(pbkdf2_params[idx]) pbkdf2_prf_oid = DerObjectId().decode(pbkdf2_prf_algo_id[0]).value elif kdf_oid == _OID_SCRYPT: scrypt_params = DerSequence().decode(kdf_info[1], nr_elements=(4, 5)) salt = DerOctetString().decode(scrypt_params[0]).payload iteration_count, scrypt_r, scrypt_p = [scrypt_params[x] for x in (1, 2, 3)] if len(scrypt_params) > 4: kdf_key_length = scrypt_params[4] else: kdf_key_length = None else: raise PbesError("Unsupported PBES2 KDF") ### Cipher selection enc_info = DerSequence().decode(pbes2_params[1]) enc_oid = DerObjectId().decode(enc_info[0]).value if enc_oid == _OID_DES_EDE3_CBC: # DES_EDE3_CBC ciphermod = DES3 key_size = 24 elif enc_oid == _OID_AES128_CBC: # AES128_CBC ciphermod = AES key_size = 16 elif enc_oid == _OID_AES192_CBC: # AES192_CBC ciphermod = AES key_size = 24 elif enc_oid == _OID_AES256_CBC: # AES256_CBC ciphermod = AES key_size = 32 else: raise PbesError("Unsupported PBES2 cipher") if kdf_key_length and kdf_key_length != key_size: raise PbesError("Mismatch between PBES2 KDF parameters" " and selected cipher") IV = DerOctetString().decode(enc_info[1]).payload # Create cipher if kdf_oid == _OID_PBKDF2: if pbkdf2_prf_oid == _OID_HMAC_SHA1: hmac_hash_module = SHA1 elif pbkdf2_prf_oid == _OID_HMAC_SHA224: hmac_hash_module = SHA224 elif pbkdf2_prf_oid == _OID_HMAC_SHA256: hmac_hash_module = SHA256 elif pbkdf2_prf_oid == _OID_HMAC_SHA384: hmac_hash_module = SHA384 elif pbkdf2_prf_oid == _OID_HMAC_SHA512: hmac_hash_module = SHA512 else: raise PbesError("Unsupported HMAC %s" % pbkdf2_prf_oid) key = PBKDF2(passphrase, salt, key_size, iteration_count, hmac_hash_module=hmac_hash_module) else: key = scrypt(passphrase, salt, key_size, iteration_count, scrypt_r, scrypt_p) cipher = ciphermod.new(key, ciphermod.MODE_CBC, IV) # Decrypt data pt = cipher.decrypt(encrypted_data) return unpad(pt, cipher.block_size)
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 | | | (called *N* or CPU/memory cost in scrypt). | | | | | | The default value for PBKDF2 is 1 000. | | | The default value for scrypt is 16 384. | +------------------+-----------------------------------------------+ | 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. | +------------------+-----------------------------------------------+ 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 cipher_mode = DES3.MODE_CBC enc_oid = "1.2.840.113549.3.7" elif protection in ('PBKDF2WithHMAC-SHA1AndAES128-CBC', 'scryptAndAES128-CBC'): key_size = 16 module = AES cipher_mode = AES.MODE_CBC enc_oid = "2.16.840.1.101.3.4.1.2" elif protection in ('PBKDF2WithHMAC-SHA1AndAES192-CBC', 'scryptAndAES192-CBC'): key_size = 24 module = AES cipher_mode = AES.MODE_CBC enc_oid = "2.16.840.1.101.3.4.1.22" elif protection in ('PBKDF2WithHMAC-SHA1AndAES256-CBC', 'scryptAndAES256-CBC'): key_size = 32 module = AES cipher_mode = AES.MODE_CBC enc_oid = "2.16.840.1.101.3.4.1.42" else: raise ValueError("Unknown PBES2 mode") # Get random data iv = randfunc(module.block_size) salt = randfunc(prot_params.get("salt_size", 8)) # Derive key from password if protection.startswith('PBKDF2'): 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) ) ) else: # It must be scrypt count = prot_params.get("iteration_count", 16384) scrypt_r = prot_params.get('block_size', 8) scrypt_p = prot_params.get('parallelization', 1) key = scrypt(passphrase, salt, key_size, count, scrypt_r, scrypt_p) key_derivation_func = newDerSequence( DerObjectId("1.3.6.1.4.1.11591.4.11"), # scrypt newDerSequence( DerOctetString(salt), DerInteger(count), DerInteger(scrypt_r), DerInteger(scrypt_p) ) ) # Create cipher and use it cipher = module.new(key, cipher_mode, 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()
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 PbesError("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 # We only support PBKDF2 or scrypt if key_derivation_oid == "1.2.840.113549.1.5.12": 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: kdf_key_length = pbkdf2_params[2] else: kdf_key_length = None if len(pbkdf2_params) > 3: raise PbesError("Unsupported PRF for PBKDF2") elif key_derivation_oid == "1.3.6.1.4.1.11591.4.11": scrypt_params = decode_der(DerSequence, key_derivation_func[1]) salt = decode_der(DerOctetString, scrypt_params[0]).payload iteration_count, scrypt_r, scrypt_p = [scrypt_params[x] for x in (1, 2, 3)] if len(scrypt_params) > 4: kdf_key_length = scrypt_params[4] else: kdf_key_length = None else: raise PbesError("Unsupported PBES2 KDF") ### 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 PbesError("Unsupported PBES2 cipher") if kdf_key_length and kdf_key_length != key_size: raise PbesError("Mismatch between PBES2 KDF parameters" " and selected cipher") IV = decode_der(DerOctetString, encryption_scheme[1]).payload # Create cipher if key_derivation_oid == "1.2.840.113549.1.5.12": # PBKDF2 key = PBKDF2(passphrase, salt, key_size, iteration_count) else: key = scrypt(passphrase, salt, key_size, iteration_count, scrypt_r, scrypt_p) cipher = ciphermod.new(key, ciphermod.MODE_CBC, IV) # Decrypt data pt = cipher.decrypt(encrypted_data) return unpad(pt, cipher.block_size)