def test_challenge18(): key = b'YELLOW SUBMARINE' nonce = urandom(16) be_cipher = challenge18.AESCTR(key, nonce, byteorder='big') reference = Cipher(AES(key), CTR(nonce), default_backend()) check_cipher_interoperability( be_cipher, reference, b'welcome to the wonderful world of cryptography', ) # The ciphertext provided by the exercise prompt was produced using the # increment function in little endian mode (which seems to not be the # default) le_cipher = challenge18.AESCTR( key, bytes(16), byteorder='little', ) ciphertext = b64decode( b'L77na/nrFsKvynd6HzOoG7GHTLXsTVu9qvY/2syLXzhPweyyMTJULu/6/kXX0KSvoOLSFQ==' ) plaintext = le_cipher.crypt(ciphertext) assert plaintext == b"Yo, VIP Let's kick it Ice, Ice, baby Ice, Ice, baby "
def tape_gen(self, data): """Return a bit string, generated from the given data string""" # FIXME data = str(data).encode() # Derive a key from data hmac_obj = hmac.HMAC(self.key, digestmod=hashlib.sha256) hmac_obj.update(data) assert hmac_obj.digest_size == 32 digest = hmac_obj.digest() # Use AES in the CTR mode to generate a pseudo-random bit string aes_algo = algorithms.AES(digest) aes_cipher = Cipher(aes_algo, mode=CTR(b'\x00' * 16), backend=default_backend()) encryptor = aes_cipher.encryptor() while True: encrypted_bytes = encryptor.update(b'\x00' * 16) # Convert the data to a list of bits bits = util.str_to_bitstring(encrypted_bytes) for bit in bits: yield bit
def decrypt_ctr(key, iv, aad, data): """ verify HMAC and decrypt using CTR mode key: 16 byte key iv: 12 bytes nonce. 4 zero bytes are appended to create the 16 byte nonce used for CTR mode aad: bytes. additional unencrypted data to include in the signature data: bytes. plaint text to encrypt """ nonce = iv + b'\x00\x00\x00\x00' ct = data[:-16] tag = data[-16:] h = HMAC(key, SHA256(), backend=default_backend()) h.update(aad) h.update(ct) act = h.finalize()[:16] if not bytes_eq(tag, act): raise ValueError() decryptor = Cipher(AES(key), CTR(nonce), backend=default_backend()).decryptor() return decryptor.update(ct) + decryptor.finalize()
def decrypt(self): pad = self.enc_pad(self.enckey, self.pad_iv) aes = Cipher(AES(self.enckey), CTR(self.iv), default_backend()).decryptor() self.fwd = pad + aes.update(self.onion[:self.fwd_end]) self.msg = aes.update(self.onion[self.fwd_end:self.msg_end])
def encrypt_ctr(key, iv, aad, data): """ encrypt using CTR then sign with an HMAC Note: this implementation is similar to AES-CCM, but is slower. The implementation is left in only for benchmarking purposes. Note: in theory this should parallize well, however in practice it is about 2x slower than GCM mode. The HMAC is not the bottleneck key: 16 byte key iv: 12 bytes nonce. 4 zero bytes are appended to create the 16 byte nonce used for CTR mode aad: bytes. additional unencrypted data to include in the signature data: bytes. plaint text to encrypt future work: require key to be 32 bytes, and then use 16 bytes for the HMAC and the other 16 bytes for CTR mode """ nonce = iv + b'\x00\x00\x00\x00' encryptor = Cipher(AES(key), CTR(nonce), backend=default_backend()).encryptor() ct = encryptor.update(data) + encryptor.finalize() h = HMAC(key, SHA256(), backend=default_backend()) h.update(aad) h.update(ct) tag = h.finalize()[:16] return ct + tag
def initialize_cipher(self) -> None: """Creates the cipher-related objects needed for AES-CTR encryption and decryption. """ self.cipher = Cipher(AES(self.key), CTR(self.iv), default_backend()) self.encryptor = self.cipher.encryptor() self.decryptor = self.cipher.decryptor()
def cryptography_aes_ctr(key, index): from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import CTR from struct import pack ctr = b'\0' * 8 + pack('>Q', index) cipher = Cipher(AES(key), CTR(ctr), default_backend()) e = cipher.encryptor() e.encrypt = e.update return e
def decrypt_and_unpack( input_file: IOIter, output_file: IOIter, key_pair: Optional[bytes], options: OptionsDict, ) -> None: """ Read encrypted, GZIPed data from an open file descriptor, and write the decoded data to another file descriptor; verify the HMAC of the encrypted data to ensure integrity :param input_file: an IOIter object to read compressed ciphertext from :param output_file: an IOIter object to write plaintext data to """ key, nonce, signature = ( key_pair[:AES_KEY_SIZE], key_pair[AES_KEY_SIZE:AES_KEY_SIZE + AES_BLOCK_SIZE], key_pair[AES_KEY_SIZE + AES_BLOCK_SIZE:] ) if key_pair else (b'', b'', b'') decrypted_data = b'' decrypt_fn: Callable[[bytes], bytes] = ( Cipher(AES(key), CTR(nonce), backend=default_backend()).decryptor().update if options['use_encryption'] else identity ) decompress_obj = zlib.decompressobj() unzip_fn: Callable[[bytes], bytes] = ( decompress_obj.decompress # type: ignore if options['use_compression'] else identity ) hmac = HMAC(key, SHA256(), default_backend()) writer = output_file.writer(); next(writer) for encrypted_data in input_file.reader(): if options['use_encryption']: hmac.update(encrypted_data) decrypted_data += decrypt_fn(encrypted_data) logger.debug2(f'decrypt_fn returned {len(decrypted_data)} bytes') block = unzip_fn(decrypted_data) logger.debug2(f'unzip_fn returned {len(block)} bytes') writer.send(block) decrypted_data = decompress_obj.unused_data # Decompress and write out the last block if decrypted_data: block = unzip_fn(decrypted_data) logger.debug2(f'unzip_fn returned {len(block)} bytes') writer.send(block) try: if options['use_encryption']: hmac.verify(signature) except InvalidSignature as e: raise BackupCorruptedError("The file's signature did not match the data") from e
def set_shared_secret(self, data): """ When used in the NTOR handshake, the first HASH_LEN bytes form the forward digest Df; the next HASH_LEN form the backward digest Db; the next KEY_LEN form Kf, the next KEY_LEN form Kb, and the final DIGEST_LEN bytes are taken as a nonce to use in the place of KH in the hidden service protocol. Excess bytes from K are discarded. :type data: bytes """ forward_digest, backward_digest, encryption_key, decryption_key = struct.unpack( "!20s20s16s16s", data) self._forward_digest = self.set_digest(forward_digest) self._backward_digest = self.set_digest(backward_digest) self.encryption_key = encryption_key self.decryption_key = decryption_key self._forward_cipher = Cipher(AES(self.encryption_key), CTR(b'\0' * 16), backend=default_backend()).encryptor() self._backward_cipher = Cipher(AES(self.decryption_key), CTR(b'\0' * 16), backend=default_backend()).decryptor()
def mdp_crypt(main_password: str, password_to_crypt: str): """ @param main_password: str -> the userpassword @param password_to_crypt: str -> to crypt @return: byte string """ nonce = os.urandom(32) key = bcrypt.kdf(password=bytes(main_password, "utf8"), salt=nonce, desired_key_bytes=32, rounds=100) ciph = Cipher(AES(key), CTR(nonce), default_backend()) encryptor = ciph.encryptor() return encryptor.update(bytes(password_to_crypt, "utf8")).hex(), nonce.hex()
def seek(self, n, whence=0): if whence == 0: goal = n elif whence == 1: goal = self._pos + n else: raise ValueError("SEEK_END not supported; keystreams are infinite.") closest_block, beyond = divmod(goal, self._octets_per_block) self._remaining = b"" self._pos = closest_block * self._octets_per_block self._encryptor = Cipher( self._algorithm, CTR(int_to_bytes(closest_block, self._octets_per_block)), backend=default_backend(), ).encryptor() self.read(beyond)
def test_decrypt_and_unpack_bad_signature(caplog, mock_open_streams): orig, new, _ = mock_open_streams orig_contents = orig._fd.getvalue() cipher = Cipher(AES(TMP_KEY), CTR(TMP_NONCE), backend=default_backend()).encryptor() ct = cipher.update(zlib.compress(orig_contents)) hmac = HMAC(TMP_KEY, SHA256(), default_backend()) hmac.update(ct) signature = hmac.finalize() orig._fd.write(ct) with pytest.raises(BackupCorruptedError): decrypt_and_unpack( orig, new, TMP_KEYPAIR + signature[:-2] + b'f', dict(use_compression=True, use_encryption=True), )
def aesctr_decrypt_stream(key: AES128Key, iv: bytes, cipher_text: bytes) -> Iterator[int]: aes_key = AES(key) ctr = CTR(iv) try: cipher = Cipher(aes_key, ctr, backend=default_backend()) except ValueError as err: raise DecryptionError(str(err)) from err decryptor = cipher.decryptor() num_blocks = int(math.ceil(len(cipher_text) / 16)) for i in range(num_blocks): cipher_text_block = cipher_text[i * 16:(i + 1) * 16] plain_text_block = decryptor.update(cipher_text_block) yield from plain_text_block yield from decryptor.finalize()
def test_compress_and_encrypt_no_compression(caplog, mock_open_streams): orig, new, _ = mock_open_streams signature = compress_and_encrypt( orig, new, TMP_KEYPAIR, dict(use_compression=False, use_encryption=True), ) cipher = Cipher(AES(TMP_KEY), CTR(TMP_NONCE), backend=default_backend()).decryptor() hmac = HMAC(TMP_KEY, SHA256(), default_backend()) decrypted = cipher.update(new._fd.getvalue()) hmac.update(new._fd.getvalue()) assert decrypted == orig._fd.getvalue() assert count_matching_log_lines('read 2 bytes from /orig', caplog) == 4 assert count_matching_log_lines('wrote 2 bytes to /new', caplog) == 4 hmac.verify(signature)
def mdp_decrypt(main_password: str, pasword_to_decrypt: str, nonce: str) -> str: """ @summary décrypte un mot de passe a partir du maitre et du nonce @param main_password: str @param pasword_to_decrypt: str @param nonce: str @return: bytes .decode("utf8") pour utf8 """ nonce = bytes.fromhex(nonce) main_password = bytes(main_password, 'utf8') key = bcrypt.kdf(password=main_password, salt=nonce, desired_key_bytes=32, rounds=100) ciph = Cipher(AES(key), CTR(nonce), default_backend()) decryptor = ciph.decryptor() return decryptor.update(bytes.fromhex(pasword_to_decrypt))
def compress_and_encrypt( input_file: IOIter, output_file: IOIter, key_pair: Optional[bytes], options: OptionsDict, ) -> bytes: """ Read data from an open file descriptor, and write the compressed, encrypted data to another file descriptor; compute the HMAC of the encrypted data to ensure integrity :param input_file: an IOIter object to read plaintext data from :param output_file: an IOIter object to write compressed ciphertext to """ key, nonce = (key_pair[:AES_KEY_SIZE], key_pair[AES_KEY_SIZE:]) if key_pair else (b'', b'') compressobj = zlib.compressobj() zip_fn: Callable[[bytes], bytes] = ( # type: ignore compressobj.compress if options['use_compression'] else identity ) encrypt_fn: Callable[[bytes], bytes] = ( Cipher(AES(key), CTR(nonce), backend=default_backend()).encryptor().update if options['use_encryption'] else identity ) hmac = HMAC(key, SHA256(), default_backend()) def last_block() -> Generator[Tuple[bytes, bool], None, None]: yield (compressobj.flush(), False) if options['use_compression'] else (b'', False) writer = output_file.writer(); next(writer) logger.debug2('starting to compress') for block, needs_compression in chain(zip(input_file.reader(), repeat(True)), last_block()): if needs_compression: block = zip_fn(block) logger.debug2(f'zip_fn returned {len(block)} bytes') block = encrypt_fn(block) logger.debug2(f'encrypt_fn returned {len(block)} bytes') if options['use_encryption']: hmac.update(block) writer.send(block) if options['use_encryption']: return hmac.finalize() else: return b''
def test_decrypt_and_unpack_no_compression(caplog, mock_open_streams): orig, new, _ = mock_open_streams orig_contents = orig._fd.getvalue() cipher = Cipher(AES(TMP_KEY), CTR(TMP_NONCE), backend=default_backend()).encryptor() ct = cipher.update(orig_contents) hmac = HMAC(TMP_KEY, SHA256(), default_backend()) hmac.update(ct) signature = hmac.finalize() orig._fd.write(ct) decrypt_and_unpack( orig, new, TMP_KEYPAIR + signature, dict(use_compression=False, use_encryption=True), ) assert new._fd.getvalue() == orig_contents assert count_matching_log_lines('read 2 bytes from /orig', caplog) == 4 assert count_matching_log_lines('wrote 2 bytes to /new', caplog) == 4
def aesctr_encrypt(key: AES128Key, iv: bytes, plain_text: bytes) -> bytes: cipher = Cipher(AES(key), CTR(iv), backend=default_backend()) encryptor = cipher.encryptor() return encryptor.update(plain_text) + encryptor.finalize()
def test_aes_ctr_always_available(self): # AES CTR should always be available, even in 1.0.0. assert backend.cipher_supported(AES(b"\x00" * 16), CTR( b"\x00" * 16)) is True
def aes_ctr_decryptor(key, iv=b'\0' * 16): return Cipher(AES(key), CTR(iv), backend=bend).decryptor()
def __init__(self, msgs, pubkeys): assert len(msgs) == len(pubkeys) assert 0 < len(msgs) <= 20 assert all( len(m) <= self.MSG_LEN for m in msgs ) msgs = [m + "\0"*(self.MSG_LEN - len(m)) for m in msgs] pubkeys = [ecc.ECC(pubkey=pk, curve='secp256k1') for pk in pubkeys] n = len(msgs) tmpkeys = [] tmppubkeys = [] for i in range(n): while True: t = ecc.ECC(curve='secp256k1') if ord(t.pubkey_y[-1]) % 2 == 0: break # or do the math to "flip" the secret key and pub key tmpkeys.append(t) tmppubkeys.append(t.pubkey_x) enckeys, hmacs, ivs, pad_ivs = zip(*[self.get_ecdh_secrets(tmpkey, pkey.pubkey_x, pkey.pubkey_y) for tmpkey, pkey in zip(tmpkeys, pubkeys)]) # padding takes the form: # E_(n-1)(0000s) # D_(n-1)( # E(n-2)(0000s) # D(n-2)( # ... # ) # ) padding = "" for i in range(n-1): pad = self.enc_pad(enckeys[i], pad_ivs[i]) aes = Cipher(AES(enckeys[i]), CTR(ivs[i]), default_backend()).decryptor() padding = pad + aes.update(padding) if n < 20: padding += str(bytearray(random.getrandbits(8) for _ in range(len(self.ZEROES) * (20-n)))) # to encrypt the message we need to bump the counter past all # the padding, then just encrypt the final message aes = Cipher(AES(enckeys[-1]), CTR(ivs[-1]), default_backend()).encryptor() aes.update(padding) # don't care about cyphertext msgenc = aes.update(msgs[-1]) msgenc = padding + msgenc + tmppubkeys[-1] del padding msgenc += hmac_sha256(hmacs[-1], msgenc) # *PHEW* # now iterate for i in reversed(range(n-1)): # drop the padding this node will add msgenc = msgenc[len(self.ZEROES):] # adding the msg msgenc += msgs[i] # encrypt it aes = Cipher(AES(enckeys[i]), CTR(ivs[i]), default_backend()).encryptor() msgenc = aes.update(msgenc) # add the tmp key msgenc += tmppubkeys[i] # add the hmac msgenc += hmac_sha256(hmacs[i], msgenc) self.onion = msgenc
def enc_pad(self, enckey, pad_iv): aes = Cipher(AES(enckey), CTR(pad_iv), default_backend()).encryptor() return aes.update(self.ZEROES)
async def _decrypt_v2(self, file_data: bytes, metadata: Dict[str, str], entire_file_length: int, range_start: Optional[int] = None, desired_start: Optional[int] = None, desired_end: Optional[int] = None) -> bytes: decryption_key = base64.b64decode(metadata['x-amz-key-v2']) material_description = json.loads(metadata['x-amz-matdesc']) aes_key = await self._crypto_context.get_decryption_aes_key( decryption_key, material_description) # x-amz-key-v2 - Contains base64 encrypted key # x-amz-iv - AES IVs # x-amz-matdesc - JSON Description of client-side master key (used as encryption context as is) # x-amz-unencrypted-content-length - Unencrypted content length # x-amz-wrap-alg - Key wrapping algo, either AESWrap, RSA/ECB/OAEPWithSHA-256AndMGF1Padding or KMS # x-amz-cek-alg - AES/GCM/NoPadding or AES/CBC/PKCS5Padding # x-amz-tag-len - AEAD Tag length in bits iv = base64.b64decode(metadata['x-amz-iv']) # TODO look at doing AES as stream if metadata.get('x-amz-cek-alg', 'AES/CBC/PKCS5Padding') == 'AES/GCM/NoPadding': # AES/GCM/NoPadding # So begin the nastyness if range_start is not None: # Generate IV's as if you were doing so for each block until we get to the one we need iv = _adjust_iv_for_range(iv, range_start) # IV is now 16 bytes not 12 aesctr = Cipher(AES(aes_key), CTR(iv), backend=self._backend).decryptor() result = await self._loop.run_in_executor( None, lambda: (aesctr.update(file_data) + aesctr.finalize())) # Possible remove AEAD tag if our range covers the end aead_tag_len = int(metadata['x-amz-tag-len']) // 8 max_offset = entire_file_length - aead_tag_len - 1 desired_end = max_offset if desired_end > max_offset else desired_end # Chop file result = result[desired_start:desired_end] else: aesgcm = AESGCM(aes_key) try: result = await self._loop.run_in_executor( None, lambda: aesgcm.decrypt(iv, file_data, None)) except InvalidTag: raise DecryptError( 'Failed to decrypt, AEAD tag is incorrect. Possible key or IV are incorrect' ) else: if range_start: raise DecryptError('Cannot decrypt AES-CBC file with range') # AES/CBC/PKCS5Padding aescbc = Cipher(AES(aes_key), CBC(iv), backend=self._backend).decryptor() padded_result = await self._loop.run_in_executor( None, lambda: (aescbc.update(file_data) + aescbc.finalize())) unpadder = PKCS7(AES.block_size).unpadder() result = await self._loop.run_in_executor( None, lambda: (unpadder.update(padded_result) + unpadder.finalize())) return result
import ssl from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.ciphers.modes import CTR dsa.generate_private_key(key_size=2048, backend=backends.default_backend()) dsa.generate_private_key(key_size=4096, backend=backends.default_backend()) ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLS) mode = CTR(iv)
def __init__(self, key, iv): cipher = Cipher(AES(key), CTR(iv), backend) self.encrypter = cipher.encryptor() self.decrypter = cipher.decryptor() self.block_size = 128
def complete_handshake(self, Y, auth): # The server's handshake reply is: # SERVER_PK Y [G_LENGTH bytes] # AUTH H(auth_input, t_mac) [H_LENGTH bytes] # The client then checks Y is in G^* [see NOTE below], and computes # secret_input = EXP(Y,x) | EXP(B,x) | ID | B | X | Y | PROTOID si = self.x.get_shared_key(curve25519.Public(Y), hash_func) si += self.x.get_shared_key(self.B, hash_func) si += b64decode(self.node['identity']) si += self.B.serialize() si += self.X.serialize() si += Y si += 'ntor-curve25519-sha256-1' # KEY_SEED = H(secret_input, t_key) # verify = H(secret_input, t_verify) key_seed = hmac(self.t_key, si) verify = hmac(self.t_verify, si) # auth_input = verify | ID | B | Y | X | PROTOID | "Server" ai = verify ai += b64decode(self.node['identity']) ai += self.B.serialize() ai += Y ai += self.X.serialize() ai += self.protoid ai += 'Server' # The client verifies that AUTH == H(auth_input, t_mac). if auth != hmac(self.t_mac, ai): raise NtorError('auth input does not match.') # Both parties check that none of the EXP() operations produced the # point at infinity. [NOTE: This is an adequate replacement for # checking Y for group membership, if the group is curve25519.] # Both parties now have a shared value for KEY_SEED. They expand this # into the keys needed for the Tor relay protocol, using the KDF # described in 5.2.2 and the tag m_expand. # 5.2.2. KDF-RFC5869 # For newer KDF needs, Tor uses the key derivation function HKDF from # RFC5869, instantiated with SHA256. (This is due to a construction # from Krawczyk.) The generated key material is: # K = K_1 | K_2 | K_3 | ... # Where H(x,t) is HMAC_SHA256 with value x and key t # and K_1 = H(m_expand | INT8(1) , KEY_SEED ) # and K_(i+1) = H(K_i | m_expand | INT8(i+1) , KEY_SEED ) # and m_expand is an arbitrarily chosen value, # and INT8(i) is a octet with the value "i". # In RFC5869's vocabulary, this is HKDF-SHA256 with info == m_expand, # salt == t_key, and IKM == secret_input. keys = hkdf(key_seed, length=72, info=self.m_expand) # When used in the ntor handshake, the first HASH_LEN bytes form the # forward digest Df; the next HASH_LEN form the backward digest Db; the # next KEY_LEN form Kf, the next KEY_LEN form Kb, and the final # DIGEST_LEN bytes are taken as a nonce to use in the place of KH in the # hidden service protocol. Excess bytes from K are discarded. Df, Db, Kf, Kb = struct.unpack('>20s20s16s16s', keys) # we do what we can with what we've got. del self.X del self.x del self.B del key_seed del keys del verify del ai del auth del si del Y self.send_digest = Hash(SHA1(), backend=bend) self.send_digest.update(Df) self.recv_digest = Hash(SHA1(), backend=bend) self.recv_digest.update(Db) self.encrypt = Cipher(AES(Kf), CTR('\x00' * 16), backend=bend).encryptor() self.decrypt = Cipher(AES(Kb), CTR('\x00' * 16), backend=bend).decryptor()
def _get_cipher(key): backend = default_backend() return Cipher(AES(key), CTR(bytes(STATIC_IV)), backend=backend)
def test_aes_ctr_always_available(self): # AES CTR should always be available in both 0.9.8 and 1.0.0+ assert backend.cipher_supported(AES(b"\x00" * 16), CTR( b"\x00" * 16)) is True
def decrypt(self, relay_payload): cipher = Cipher(AES(self.decryption_key), CTR(b'\x00' * 16), backend=default_backend()).decryptor() return cipher.update(relay_payload)