def decrypt_frame_header(self, data): """ Decrypts frame header :param data: frame header data :return: decrypted header data """ if len(data) != eth_common_constants.FRAME_HDR_TOTAL_LEN: raise ValueError( "Frame header len is expected to be {0} but was {1}".format( eth_common_constants.FRAME_HDR_TOTAL_LEN, len(data))) if not self._is_ready: raise CipherNotInitializedError( "failed to decrypt frame header, the cipher was never initialized!" ) header_cipher_text = data[:eth_common_constants.FRAME_HDR_DATA_LEN] header_mac = data[eth_common_constants.FRAME_HDR_DATA_LEN: eth_common_constants.FRAME_HDR_TOTAL_LEN] # ingress-mac.update(aes(mac-secret,ingress-mac) ^ header-ciphertext).digest mac1 = self.mac_ingress()[:eth_common_constants.FRAME_MAC_LEN] mac_enc = self._mac_enc(mac1) mac_sxor = crypto_utils.string_xor(mac_enc, header_cipher_text) expected_header_mac = self.mac_ingress( mac_sxor)[:eth_common_constants.FRAME_MAC_LEN] # expected_header_mac = self.updateMAC(self.ingress_mac, header_ciphertext) if not expected_header_mac == header_mac: raise AuthenticationError("Invalid header mac") return self.aes_decode(header_cipher_text)
def test_string_xor(self): str1 = helpers.generate_bytearray(111) str2 = helpers.generate_bytearray(111) sxor = crypto_utils.string_xor(str1, str2) self.assertIsNotNone(sxor) self.assertEqual(len(sxor), len(str1))
def encrypt_frame(self, frame): """ Encrypts frame :param frame: frame data :return: encrypted frame """ if not isinstance(frame, Frame): raise TypeError("frame must be of type Frame but was {0}".format( type(frame))) if not self._is_ready: raise CipherNotInitializedError( f"failed to encrypt frame {frame}, the cipher was never initialized!" ) header = frame.get_header() body = frame.get_body() # header header_ciphertext = self.aes_encode(header) # egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digest header_mac = self.mac_egress( crypto_utils.string_xor( self._mac_enc( self.mac_egress()[:eth_common_constants.FRAME_MAC_LEN]), header_ciphertext))[:eth_common_constants.FRAME_MAC_LEN] # frame frame_ciphertext = self.aes_encode(body) assert len(frame_ciphertext) == len(body) # egress-mac.update(aes(mac-secret,egress-mac) ^ # left128(egress-mac.update(frame-ciphertext).digest)) fmac_seed = self.mac_egress(frame_ciphertext) frame_mac = self.mac_egress( crypto_utils.string_xor( self._mac_enc( self.mac_egress()[:eth_common_constants.FRAME_MAC_LEN]), fmac_seed[:eth_common_constants.FRAME_MAC_LEN]) )[:eth_common_constants.FRAME_MAC_LEN] return bytearray(header_ciphertext + header_mac + frame_ciphertext + frame_mac)
def create_auth_message(self): """ Generates authentication message bytes 1. initiator generates ecdhe-random and nonce and creates auth 2. initiator connects to remote and sends auth New: E(remote-pubk, S(ephemeral-privk, ecdh-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0 ) Known: E(remote-pubk, S(ephemeral-privk, token ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x1) :param remote_pubkey: public key of remote node :param nonce: nonce :return: authentication message bytes """ assert self._is_initiator ecdh_shared_secret = self._ecc.get_ecdh_key(self._remote_pubkey) token = ecdh_shared_secret flag = 0x0 self._initiator_nonce = eth_common_utils.keccak_hash( rlp_utils.int_to_big_endian(random.randint(0, sys.maxsize))) assert len( self._initiator_nonce) == eth_common_constants.SHA3_LEN_BYTES token_xor_nonce = crypto_utils.string_xor(token, self._initiator_nonce) assert len(token_xor_nonce) == eth_common_constants.SHA3_LEN_BYTES ephemeral_pubkey = self._ephemeral_ecc.get_raw_public_key() assert len(ephemeral_pubkey) == eth_common_constants.PUBLIC_KEY_LEN if not self._ecc.is_valid_key(ephemeral_pubkey): raise InvalidKeyError("Invalid ephemeral pubkey") # S(ephemeral-privk, ecdh-shared-secret ^ nonce) S = self._ephemeral_ecc.sign(token_xor_nonce) assert len(S) == eth_common_constants.SIGNATURE_LEN # S || H(ephemeral-pubk) || pubk || nonce || 0x0 auth_message = S + eth_common_utils.keccak_hash(ephemeral_pubkey) + self._ecc.get_raw_public_key() + \ self._initiator_nonce + rlp_utils.ascii_chr(flag) return auth_message
def parse_auth_message(self, message): assert not self._is_initiator if self._is_eip8_auth: (signature, pubkey, nonce, version) = self.parse_eip8_auth_message(message) else: (signature, pubkey, nonce, version) = self.parse_plain_auth_message(message) token = self._ecc.get_ecdh_key(pubkey) remote_ephemeral_pubkey = crypto_utils.recover_public_key( crypto_utils.string_xor(token, nonce), signature) if not self._ecc.is_valid_key(remote_ephemeral_pubkey): raise InvalidKeyError("Invalid remote ephemeral pubkey") self._remote_ephemeral_pubkey = remote_ephemeral_pubkey self._initiator_nonce = nonce self._remote_pubkey = pubkey self._remote_version = eth_common_constants.P2P_PROTOCOL_VERSION
def decrypt_frame_body(self, data, body_size): """ Decrypts frame body :param data: frame data :param body_size: body size :return: decrypted frame body """ if not self._is_ready: raise CipherNotInitializedError( "failed to decrypt frame body, the cipher was never initialized!" ) # frame-size: 3-byte integer size of frame, big endian encoded (excludes padding) # frame relates to body w/o padding w/o mac read_size = crypto_utils.get_padded_len_16(body_size) if not len(data) >= read_size + eth_common_constants.FRAME_MAC_LEN: raise ParseError("Insufficient body length") frame_cipher_text = data[:read_size] frame_mac = data[read_size:read_size + eth_common_constants.FRAME_MAC_LEN] # ingres-mac.update(aes(mac-secret,ingres-mac) ^ # left128(ingres-mac.update(frame-ciphertext).digest)) frame_mac_seed = self.mac_ingress(frame_cipher_text) expected_frame_mac = self.mac_ingress( crypto_utils.string_xor( self._mac_enc( self.mac_ingress()[:eth_common_constants.FRAME_MAC_LEN]), frame_mac_seed[:eth_common_constants.FRAME_MAC_LEN]) )[:eth_common_constants.FRAME_MAC_LEN] if not frame_mac == expected_frame_mac: raise AuthenticationError("Invalid frame mac") return self.aes_decode(frame_cipher_text)[:body_size]
def setup_cipher(self): """ Sets up cipher parameters. Needs to be called after initial authentication handshake """ assert self._responder_nonce assert self._initiator_nonce assert self._auth_init assert self._auth_ack assert self._remote_ephemeral_pubkey if not self._ecc.is_valid_key(self._remote_ephemeral_pubkey): raise InvalidKeyError("Invalid remote ephemeral pubkey") # derive base secrets from ephemeral key agreement # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) ecdhe_shared_secret = self._ephemeral_ecc.get_ecdh_key( self._remote_ephemeral_pubkey) # shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) shared_secret = eth_common_utils.keccak_hash( ecdhe_shared_secret + eth_common_utils.keccak_hash(self._responder_nonce + self._initiator_nonce)) self.ecdhe_shared_secret = ecdhe_shared_secret # used in tests self.shared_secret = shared_secret # used in tests # token = sha3(shared-secret) self._token = eth_common_utils.keccak_hash(shared_secret) # aes-secret = sha3(ecdhe-shared-secret || shared-secret) self._aes_secret = eth_common_utils.keccak_hash(ecdhe_shared_secret + shared_secret) # mac-secret = sha3(ecdhe-shared-secret || aes-secret) self.mac_secret = eth_common_utils.keccak_hash(ecdhe_shared_secret + self._aes_secret) # setup sha3 instances for the MACs # egress-mac = sha3.update(mac-secret ^ recipient-nonce || auth-sent-init) mac1 = crypto_utils.get_sha3_calculator( crypto_utils.string_xor(self.mac_secret, self._responder_nonce) + self._auth_init) # ingress-mac = sha3.update(mac-secret ^ initiator-nonce || auth-recvd-ack) mac2 = crypto_utils.get_sha3_calculator( crypto_utils.string_xor(self.mac_secret, self._initiator_nonce) + self._auth_ack) if self._is_initiator: self._egress_mac, self._ingress_mac = mac1, mac2 else: self._egress_mac, self._ingress_mac = mac2, mac1 iv = "\x00" * eth_common_constants.IV_LEN self._aes_enc = pyelliptic.Cipher( self._aes_secret, iv, eth_common_constants.CIPHER_ENCRYPT_DO, ciphername=eth_common_constants.RLPX_CIPHER_NAME) self._aes_dec = pyelliptic.Cipher( self._aes_secret, iv, eth_common_constants.CIPHER_DECRYPT_DO, ciphername=eth_common_constants.RLPX_CIPHER_NAME) self._mac_enc = AES.new(self.mac_secret, AES.MODE_ECB).encrypt self._is_ready = True