def decrypt_raw_bytes(self, data: bytes, size: int) -> bytes: """ same as decrypt_body() but no roundup """ if len(data) < size + MAC_LEN: raise ValueError( "Insufficient body length; Got {}, wanted {} + {}".format( len(data), size, MAC_LEN ) ) frame_ciphertext = data[:size] frame_mac = data[size : size + MAC_LEN] self.ingress_mac.update(frame_ciphertext) fmac_seed = self.ingress_mac.digest()[:MAC_LEN] self.ingress_mac.update(sxor(self.mac_enc(fmac_seed), fmac_seed)) expected_frame_mac = self.ingress_mac.digest()[:MAC_LEN] if not bytes_eq(expected_frame_mac, frame_mac): raise DecryptionError( "Invalid frame mac: expected {}, got {}".format( expected_frame_mac, frame_mac ) ) return self.aes_dec.update(frame_ciphertext)[:size]
def decode_authentication( ciphertext: bytes, privkey: datatypes.PrivateKey ) -> Tuple[datatypes.PublicKey, bytes, datatypes.PublicKey]: """ Decrypts and decodes the ciphertext msg. Returns the initiator's ephemeral pubkey, nonce, and pubkey. """ if len(ciphertext) < ENCRYPTED_AUTH_MSG_LEN: raise DecryptionError(f"Auth msg too short: {len(ciphertext)}") elif len(ciphertext) == ENCRYPTED_AUTH_MSG_LEN: sig, initiator_pubkey, initiator_nonce, _ = decode_auth_plain( ciphertext, privkey ) else: sig, initiator_pubkey, initiator_nonce, _ = decode_auth_eip8( ciphertext, privkey ) # recover initiator ephemeral pubkey from sig # S(ephemeral-privk, ecdh-shared-secret ^ nonce) shared_secret = ecies.ecdh_agree(privkey, initiator_pubkey) ephem_pubkey = sig.recover_public_key_from_msg_hash( sxor(shared_secret, initiator_nonce) ) return ephem_pubkey, initiator_nonce, initiator_pubkey
def derive_secrets( self, initiator_nonce: bytes, responder_nonce: bytes, remote_ephemeral_pubkey: datatypes.PublicKey, auth_init_ciphertext: bytes, auth_ack_ciphertext: bytes, ) -> Tuple[bytes, bytes, BasePreImage, BasePreImage]: """Derive base secrets from ephemeral key agreement.""" # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) ecdhe_shared_secret = ecies.ecdh_agree( self.ephemeral_privkey, remote_ephemeral_pubkey ) # shared-secret = keccak(ecdhe-shared-secret || keccak(nonce || initiator-nonce)) shared_secret = keccak( ecdhe_shared_secret + keccak(responder_nonce + initiator_nonce) ) # aes-secret = keccak(ecdhe-shared-secret || shared-secret) aes_secret = keccak(ecdhe_shared_secret + shared_secret) # mac-secret = keccak(ecdhe-shared-secret || aes-secret) mac_secret = keccak(ecdhe_shared_secret + aes_secret) # setup keccak instances for the MACs # egress-mac = sha3.keccak_256(mac-secret ^ recipient-nonce || auth-sent-init) mac1 = keccaklib.new( data=sxor(mac_secret, responder_nonce) + auth_init_ciphertext, digest_bits=256, update_after_digest=True, ) # ingress-mac = sha3.keccak_256(mac-secret ^ initiator-nonce || auth-recvd-ack) mac2 = keccaklib.new( data=sxor(mac_secret, initiator_nonce) + auth_ack_ciphertext, digest_bits=256, update_after_digest=True, ) if self._is_initiator: egress_mac, ingress_mac = mac1, mac2 else: egress_mac, ingress_mac = mac2, mac1 return aes_secret, mac_secret, egress_mac, ingress_mac
def encrypt(self, header: bytes, frame: bytes) -> bytes: if len(header) != HEADER_LEN: raise ValueError(f"Unexpected header length: {len(header)}") header_ciphertext = self.aes_enc.update(header) mac_secret = self.egress_mac.digest()[:HEADER_LEN] self.egress_mac.update( sxor(self.mac_enc(mac_secret), header_ciphertext)) header_mac = self.egress_mac.digest()[:HEADER_LEN] frame_ciphertext = self.aes_enc.update(frame) self.egress_mac.update(frame_ciphertext) fmac_seed = self.egress_mac.digest()[:HEADER_LEN] mac_secret = self.egress_mac.digest()[:HEADER_LEN] self.egress_mac.update(sxor(self.mac_enc(mac_secret), fmac_seed)) frame_mac = self.egress_mac.digest()[:HEADER_LEN] return header_ciphertext + header_mac + frame_ciphertext + frame_mac
def create_auth_message(self, nonce: bytes) -> bytes: ecdh_shared_secret = ecies.ecdh_agree(self.privkey, self.remote.pubkey) secret_xor_nonce = sxor(ecdh_shared_secret, nonce) S = self.ephemeral_privkey.sign_msg_hash(secret_xor_nonce).to_bytes() if self.use_eip8: data = rlp.encode( [S, self.pubkey.to_bytes(), nonce, SUPPORTED_RLPX_VERSION], sedes=eip8_auth_sedes, ) return _pad_eip8_data(data) else: # S || H(ephemeral-pubk) || pubk || nonce || 0x0 return (S + keccak(self.ephemeral_pubkey.to_bytes()) + self.pubkey.to_bytes() + nonce + b"\x00")
def decrypt_header(self, data: bytes) -> bytes: if len(data) != HEADER_LEN + MAC_LEN: raise ValueError( f"Unexpected header length: {len(data)}, expected {HEADER_LEN} + {MAC_LEN}" ) header_ciphertext = data[:HEADER_LEN] header_mac = data[HEADER_LEN:] mac_secret = self.ingress_mac.digest()[:HEADER_LEN] aes = self.mac_enc(mac_secret)[:HEADER_LEN] self.ingress_mac.update(sxor(aes, header_ciphertext)) expected_header_mac = self.ingress_mac.digest()[:HEADER_LEN] if not bytes_eq(expected_header_mac, header_mac): raise DecryptionError( f"Invalid header mac: expected {expected_header_mac}, got {header_mac}" ) return self.aes_dec.update(header_ciphertext)
def decrypt_body(self, data: bytes, body_size: int) -> bytes: read_size = roundup_16(body_size) if len(data) < read_size + MAC_LEN: raise ValueError( f"Insufficient body length; Got {len(data)}, wanted {read_size} + {MAC_LEN}" ) frame_ciphertext = data[:read_size] frame_mac = data[read_size:read_size + MAC_LEN] self.ingress_mac.update(frame_ciphertext) fmac_seed = self.ingress_mac.digest()[:MAC_LEN] self.ingress_mac.update(sxor(self.mac_enc(fmac_seed), fmac_seed)) expected_frame_mac = self.ingress_mac.digest()[:MAC_LEN] if not bytes_eq(expected_frame_mac, frame_mac): raise DecryptionError( f"Invalid frame mac: expected {expected_frame_mac}, got {frame_mac}" ) return self.aes_dec.update(frame_ciphertext)[:body_size]