Skip to main content
Loading...

More Python Posts

"""
Assignment 6

The goal is to make a graph of
who bit who and who was bitten.
There should be 10 nodes and 15 edges.
3 arrows of biting each other and
3 arrows of someone biting themselves.
Networkx can not do self biting
arrows, but it is in the code.
"""

from graphviz import Digraph as DDotGraph
from graphviz import Graph as UDotGraph
import networkx as nx
from networkx.algorithms.dag import transitive_closure
import graphviz as gv
import matplotlib.pyplot as plt
import numpy as np
from numpy.linalg import matrix_power

"""
class DGraph:
    def __init__(self):
        self.d = dict()

    def clear(self):
        self.d = dict()

    def add_node(self,n):
        if not self.d.get(n):
            self.d[n] = set()

    def add_edge(self,e):
        f,t=e
        self.add_node(f)
        self.add_node(t)
        vs=self.d.get(f)
        if not vs:
            self.d[f] = {t}
        else:
            vs.add(t)

    def add_edges_from(self,es):
        for e in es:
            self.add_edge(e)

    def edges(self):
        for f in self.d:
            for t in self.d[f]:
                yield (f,t)

    def number_of_nodes(self):
        return len(self.d)

    def __repr__(self):
        return self.d.__repr__()

    def show(self):
        dot = gv.Digraph()
        for e in self.edges():
            #print(e)
            f, t = e
            dot.edge(str(f), str(t), label='')
        #print(dot.source)
        show(dot)

# displays graph with graphviz
def show(dot, show=True, file_name='graph.gv'):
    dot.render(file_name, view=show)


def showGraph(g,label="",directed=True):
    if directed:
        dot = gv.Digraph()
    else:
        dot = gv.Graph()

    for e in g.edges():
        print(e)
        f, t = e
        dot.edge(str(f), str(t), label=label)
    print(dot.source)
    show(dot)


def bit():
    G = DGraph()
    G.add_edge(("Blade","Samara"))
    G.add_edge(("Shadow","Wolfe"))
    G.add_edge(("Raven", "Austin"))
    G.add_edge(("Blade", "Alice"))
    G.add_edge(("Alice","Brandon"))
    G.add_edge(("Blade", "Wolfe"))
    G.add_edge(("Samara", "Robin"))
    G.add_edge(("Samara", "Raven"))
    G.add_edge(("Samara", "Hamed"))
    G.add_edge(("Wolfe", "Blade"))
    G.add_edge(("Hamed", "Samara"))
    G.add_edge(("Wolfe", "Shadow"))
    G.add_edge(("Brandon", "Brandon"))
    G.add_edge(("Hamed", "Hamed"))
    G.add_edge(("Austin", "Austin"))
    showGraph(G, label="bit")

bit()

def bitten():
    G=DGraph()
    G.add_edge(("Samara","Blade"))
    G.add_edge(("Wolfe","Shadow"))
    G.add_edge(("Austin", "Raven"))
    G.add_edge(("Alice","Blade"))
    G.add_edge(("Brandon", "Alice"))
    G.add_edge(("Wolfe", "Blade" ))
    G.add_edge(("Robin", "Samara"))
    G.add_edge(("Raven", "Samara"))
    G.add_edge(("Hamed", "Samara"))
    G.add_edge(("Blade", "Wolfe"))
    G.add_edge(("Samara", "Hamed"))
    G.add_edge(("Shadow", "Wolfe"))
    G.add_edge(("Brandon", "Brandon"))
    G.add_edge(("Hamed", "Hamed"))
    G.add_edge(("Austin", "Austin"))
    showGraph(G, label="bitten by")

#bitten()

family = ["Blade", "Samara", "Shadow", "Wolfe", "Raven", "Alice"]
"""

#Do transitive closure call out and the
#matrix power operation should be the same
D = nx.DiGraph()
#D.add_nodes_from("SamaraBladeWolfeShadowAliceRavenBrandonRobinHamedAustin")
D.add_edge("Blade","Samara")
D.add_edge("Shadow","Wolfe")
D.add_edge("Raven", "Austin")
D.add_edge("Blade", "Alice")
D.add_edge("Alice","Brandon")
D.add_edge("Blade", "Wolfe")
D.add_edge("Samara", "Robin")
D.add_edge("Samara", "Raven")
D.add_edge("Samara", "Hamed")
D.add_edge("Wolfe", "Blade")
D.add_edge("Hamed", "Samara")
D.add_edge("Wolfe", "Shadow")
D.add_edge("Brandon", "Brandon")
D.add_edge("Hamed", "Hamed")
D.add_edge("Austin", "Austin")

T = transitive_closure(D)

for e in D.edges(): print(e)
for n in D.nodes(): print(n)

def show(H):
    nx.draw(H, with_labels=True, font_weight='bold')
    plt.show()
#Use nx.to_numpy_matrix instead of nx.adjacency_matrix

# M = nx.adjacency_matrix(D)
# MT = nx.adjacency_matrix(T)
M = nx.to_numpy_matrix(D)
MT = nx.to_numpy_matrix(T)
M2 = M@M

def mPower(M, k): #M is numpy matrix
    assert k >= 1
    P = M
    for _ in range(k):
       P = P @ M
    return P

def tc(M):
    #compute transitive closure
    pass

D1 = nx.DiGraph(M)
D2 = nx.DiGraph(M2)

print('Matrix for Original\n', M)
N = nx.to_numpy_array(D,dtype=int)
print('np_array for Original\n', N)
print('\nMatrix for Transitive Closure\n', MT)
N2 = nx.to_numpy_array(T,dtype=int)
print('np_array for Transitive Closure\n', N2)

show(D) #can use D, T, and numpy matrix power operation
show(T)
show(T)
class ProposalParser:
    """A class to parse IKE and ESP proposal strings into human-readable formats.

    This class supports parsing of IKE and ESP proposals, extracting encryption, hash, PRF (for IKE),
    and Diffie-Hellman (DH) group information. It also handles the concatenation of these components
    into a structured format, indicating whether Perfect Forward Secrecy (PFS) is enabled for ESP proposals.
    The parser uses predefined mappings for DH groups, encryption algorithms, hash functions, and Pseudo-Random Functions (PRFs).
    It can process a list of proposals and return a formatted string summarizing the cryptographic parameters.
    Attributes:

        dh_mapping (dict): A mapping of Diffie-Hellman groups to their corresponding identifiers

        enc_mapping (dict): A mapping of encryption algorithms to their corresponding identifiers

        hash_mapping (dict): A mapping of hash functions to their corresponding identifiers

        prf_mapping (dict): A mapping of Pseudo-Random Functions to their corresponding identifiers

    Methods:
        parse_ike_proposal(proposal): Parses a single IKE or ESP proposal string into a structured dictionary with encryption, hash, PRF, and DH group information.

        process_proposals(proposal_list): Processes a comma-separated list of IKE or ESP proposals, concatenating encryption, hash, PRF (for IKE), and DH group values, and indicating whether PFS is enabled for ESP proposals.

    """
    
    def __init__(self):
        """Initialize the parser with mappings for DH groups, encryption, hash, and PRF."""
        self.dh_mapping = {
            'MODP_768': '1',
            'MODP_1024': '2',
            'MODP_1536': '5',
            'MODP_2048': '14',
            'MODP_3072': '15',
            'MODP_4096': '16',
            'MODP_6144': '17',
            'MODP_8192': '18',
            'ECP_256': '19',
            'ECP_384': '20',
            'ECP_521': '21',
            'ECP_192': '25',
            'ECP_224': '26',
            'MODP_1024_160': '22',
            'MODP_2048_224': '23',
            'MODP_2048_256': '24',
            'FFDHE_2048': '256',
            'FFDHE_3072': '257',
            'FFDHE_4096': '258',
            'FFDHE_6144': '259',
            'FFDHE_8192': '260',
            'ECP_224_BP': '27',
            'ECP_256_BP': '28',
            'ECP_384_BP': '29',
            'ECP_512_BP': '30',
            'CURVE_25519': '31',
            'CURVE_448': '32',
        }
        
        self.enc_mapping = {
            'AES_CBC_128': 'AES128',
            'AES_CBC_192': 'AES192',
            'AES_CBC_256': 'AES256',
            'AES_GCM_16_128': 'AES128-GCM-16',
            'AES_GCM_16_192': 'AES192-GCM-16',
            'AES_GCM_16_256': 'AES256-GCM-16',
            'AES_GCM_8_128': 'AES128-GCM-8',
            'AES_GCM_8_256': 'AES256-GCM-8',
            'AES_GCM_12_128': 'AES128-GCM-12',
            'AES_GCM_12_256': 'AES256-GCM-12',
            'AES_CCM_16_128': 'AES128-CCM-16',
            'AES_CCM_16_256': 'AES256-CCM-16',
            'AES_CTR_128': 'AES128-CTR',
            'AES_CTR_192': 'AES192-CTR',
            'AES_CTR_256': 'AES256-CTR',
            '3DES_CBC': '3DES',
            'DES_CBC': 'DES',
            'CAMELLIA_CBC_128': 'CAMELLIA128',
            'CAMELLIA_CBC_256': 'CAMELLIA256',
            'CHACHA20_POLY1305': 'CHACHA20-POLY1305',
            'BLOWFISH_CBC': 'BLOWFISH',
            'CAST5_CBC': 'CAST5'
        }
        
        self.hash_mapping = {
            'HMAC_MD5': 'MD5',
            'HMAC_MD5_96': 'MD5',
            'HMAC_SHA1': 'SHA1',
            'HMAC_SHA1_96': 'SHA1',
            'HMAC_SHA2_256': 'SHA2-256',
            'HMAC_SHA2_256_128': 'SHA2-256',
            'HMAC_SHA2_384': 'SHA2-384',
            'HMAC_SHA2_384_192': 'SHA2-384',
            'HMAC_SHA2_512': 'SHA2-512',
            'HMAC_SHA2_512_256': 'SHA2-512',
            'HMAC_SHA3_224': 'SHA3-224',
            'HMAC_SHA3_256': 'SHA3-256',
            'HMAC_SHA3_384': 'SHA3-384',
            'HMAC_SHA3_512': 'SHA3-512',
            'AES_GMAC_128': 'GMAC-128',
            'AES_GMAC_192': 'GMAC-192',
            'AES_GMAC_256': 'GMAC-256',
            'POLY1305': 'POLY1305'
        }
        
        self.prf_mapping = {
            'PRF_HMAC_MD5': 'MD5',
            'PRF_HMAC_SHA1': 'SHA1',
            'PRF_HMAC_SHA2_256': 'SHA2-256',
            'PRF_HMAC_SHA2_384': 'SHA2-384',
            'PRF_HMAC_SHA2_512': 'SHA2-512',
            'PRF_AES128_CMAC': 'AES128-CMAC',
            'PRF_AES128_XCBC': 'AES128-XCBC',
            'PRF_HMAC_SHA3_224': 'SHA3-224',
            'PRF_HMAC_SHA3_256': 'SHA3-256',
            'PRF_HMAC_SHA3_384': 'SHA3-384',
            'PRF_HMAC_SHA3_512': 'SHA3-512'
        }

    def parse_ike_proposal(self, proposal):
        """
        Parse an IKE or ESP proposal string into a structured format.

        Args:
            proposal (str): The proposal string, e.g., "IKE:AES_CBC_256/HMAC_SHA2_256/PRF_HMAC_SHA2_256/MODP_2048"
            
        Returns:
            dict: A dictionary with keys 'encryption', 'hash', 'prf', and 'dh_group'
        """

        # Split the proposal into components based on '/'
        components = proposal.split('/')
        
        result = {
            'encryption': [],
            'hash': [],
            'prf': [],
            'dh_group': []
        }
        
        
        is_ike = proposal.startswith('IKE:')
        is_esp = proposal.startswith('ESP:')
        

        # Remove IKE or ESP prefix if present for easier parsing later
        if is_ike or is_esp:
            components[0] = components[0].replace('IKE:', '').replace('ESP:', '')
        

        
        # Determine the current section based on the first component
        # Determine the current section based on the first component
        enc_components = []
        hash_components = []
        prf_components = []
        dh_components = []
        unknown_components = []  # To track unrecognized components

        for component in components:
            categorized = False

            # Encryption components
            if (component in self.enc_mapping or 
                any(s in component for s in ['AES_CBC', 'AES_GCM', 'AES_CTR', 'CHACHA20', 
                                            'BLOWFISH', 'CAST5', 'DES', '3DES', 'CAMELLIA'])):
                enc_components.append(component)
                categorized = True

            # Hash components
            if (component in self.hash_mapping or 
                any(s in component for s in [ 'HMAC_MD5', 'POLY1305', 'AES_GMAC'])):
                hash_components.append(component)
                categorized = True
            else:
                hash_components.append("None")

            # PRF components (only if is_ike is True)
            if is_ike and 'PRF_' in component:
                prf_components.append(component)
                categorized = True

            # Diffie-Hellman components
            if (component in self.dh_mapping or 
                any(s in component for s in ['MODP', 'ECP', 'FFDHE', 'CURVE'])):
                dh_components.append(component)
                categorized = True

            # Skip irrelevant components
            if component == 'NO_EXT_SEQ':
                continue

            # Log unrecognized components
            if not categorized:
                unknown_components.append(component)

        # Optional: Log or handle unknown components
        if unknown_components:
            print(f"Warning: Unrecognized components: {unknown_components}")
        

        # Map encryption components
        for enc in enc_components:
            mapped_enc = self.enc_mapping.get(enc, 'Unknown')
            if mapped_enc != 'Unknown' and mapped_enc not in result['encryption']:
                result['encryption'].append(mapped_enc)
        
        # Map hash components (skip for AEAD ciphers like AES-GCM)
        #if not any(enc.startswith('AES_GCM') or enc.startswith('AES_CCM') or enc == 'CHACHA20_POLY1305' for enc in enc_components):
        for hash_val in hash_components:
            print(hash_components)
            mapped_hash = self.hash_mapping.get(hash_val, 'Unknown')
            if mapped_hash != 'Unknown' and mapped_hash not in result['hash']:
                result['hash'].append(mapped_hash)
            if mapped_hash == 'Unknown':
                result['hash'].append(mapped_hash)

        
        # Map PRF components
        for prf in prf_components:
            mapped_prf = self.prf_mapping.get(prf, 'Unknown')
            if mapped_prf == "Unknown":
                result['prf'].append(mapped_prf)
            if mapped_prf != 'Unknown' and mapped_prf not in result['prf']:
                result['prf'].append(mapped_prf)


        # Map DH group components
        for dh in dh_components:
            mapped_dh = self.dh_mapping.get(dh)
            if mapped_dh != 'None' and mapped_dh not in result['dh_group']:
                result['dh_group'].append(mapped_dh)

        
        # Handle ESP case (no PRF for ESP proposals)
        if is_esp:
            result['prf'] = ['None']
        
        if not result['encryption']:
            result['encryption'] = ['Unknown']

        # if not result['hash']:
        #     result['hash'] = ['Unknown']
        
        print(result['hash'])

        return result

    def process_proposals(self, proposal_list):
        """
        Process a list of IKE or ESP proposals, concatenating encryption, hash, PRF (for IKE only), 
        and DH group values, and indicate whether PFS is enabled for ESP proposals only.
        
        Args:
            proposal_list (str): Comma-separated string of IKE or ESP proposals
        
        Returns:
            str: Formatted string with concatenated encryption, hash, PRF (for IKE), DH groups, and PFS status (for ESP)
        """
        proposal_list = proposal_list.replace(',', ', ')
        proposals = proposal_list.strip().split(', ')

        
        # Collect unique encryption, hash, PRF, and DH groups
        enc_set = set()
        hash_set = set()
        prf_set = set()
        dh_set = set()
        


        # Parse each proposal and update the sets for later sorting and formatting
        for proposal in proposals:
            parsed = self.parse_ike_proposal(proposal.strip())
            print(parsed)
            enc_set.update(parsed['encryption'])
            if parsed['hash'] != ['None']:
                hash_set.update(parsed['hash'])
            else:
                hash_set.update(parsed['hash'])
            if parsed['prf'] != ['None']:
                prf_set.update(parsed['prf'])
            if parsed['dh_group'] != ['None']:
                dh_set.update(parsed['dh_group'])

        
        # Convert sets to sorted lists
        enc_list = sorted(list(enc_set))
        hash_list = sorted(list(hash_set))
        prf_list = sorted(list(prf_set))
        dh_list = sorted(list(dh_set), key=lambda x: int(x))

        
        # Determine PFS status for ESP proposals only
        is_ike = any(proposal.startswith('IKE:') for proposal in proposals)
        pfs_status = "PFS: Enabled" if dh_set and not is_ike else "PFS: None"

        
        # Format output as a single concatenated string
        enc_part = f"Encryption: {', '.join(enc_list)}" if enc_list else "Encryption: None"
        hash_part = f"Hash: {', '.join(hash_list)}"
        dh_part = f"DH Group(s): {', '.join(dh_list)}" if dh_list else "DH Group(s): None"
        prf_part = f"PRF: {', '.join(prf_list)}" if prf_list else "PRF: None"

        
        # Return formatted string based on whether it's an IKE or ESP proposal
        if is_ike:
            return f"{enc_part} {hash_part} {prf_part} {dh_part}"
        else:
            return f"{enc_part} {hash_part} {dh_part} {pfs_status}"
            
# Example usage
if __name__ == "__main__":
    parser = ProposalParser()

    unknown_hash = "IKE:AES_CBC_256/INVALID_HASH/PRF_HMAC_SHA2_256/MODP_2048"
    none_hash = "IKE:AES_CBC_256/PRF_HMAC_SHA2_256/MODP_2048"

    # Outputs Encryption: AES256 Hash: Unknown PRF: SHA2-256 DH Group(s): 14 <-- Correct!
    print(parser.process_proposals(unknown_hash))

    # Outputs Encryption: AES256 Hash: Unknown PRF: SHA2-256 DH Group(s): 14 <-- Incorrect
    print(parser.process_proposals(none_hash))