Anwendungen Teil I

Passwörter kodiert speichern

Die Kryptografie ist beim Verschlüsseln auf exklusive Verfahren angewiesen, die möglichst zeitintensive "Brute-Force-Methoden" benötigen, um verschlüsselte Texte zu entschlüsseln. Hier sollen Passwörter nach einem einfachen Verfahren entschlüsselt werden, wobei das Passwort selbst der Schlüssel ist, sodass ein Entschlüsseln nicht möglich ist. Nehmen wir als Beispiel das gewählte Passwort 15ghM;OP, was relativ sicher ist, allerdings etwas kurz. Eine Brute-Force-Methode käme sehr schnell zum Ziel. Die Verschlüsselung soll jetzt wie folgt erfolgen:

  • Alle Unicode-Zeichen sind zulässig
  • Das verschlüsselte Passwort soll 24 Zeichen lang sein.
  • Hat das gewählte Passwort weniger als 24 Zeichen, wird es solange kopiert, bis die Zeichenzahl errreicht ist.
  • Für die Vertauschung der Zeichen berücksichtigen wir die Platznummern (Ordinalzahlen) der einzelnen Zeichen:

    PW = "15ghM;OP"
    for i in PW:
      print(ord(i))

    Das Passwort ist acht Zeichen lang und jedes Zeichen soll um diese Zahl einschließlich des laufenden Indexes verschoben werden. Das erste Zeichen, hier die 1, soll durch chr(ord(i)+8+1) (49+9) nach rechts verschoben werden. Wobei hier vorerst auf ein Rotieren verzichtet werden soll, falls die Zahl größer als $2^{16}$ wird. Die 1 wird somit zum Doppelpunkt . Die anderen Zeichen werden entsprechend kodiert. Für das zweite Zeichen ist es chr(ord(i)+8+2) (50+10), was dem L entspricht.
  • PW = "12ghM;OP"
    print()
    print(PW + " -> ",end="")
    l_PW = len(PW)
    f = 24 // l_PW 
    PW *= f 
    j = 0
    for i in PW:
      j += 1
      print(chr(ord(i)+len(PW)+j),end="")
    print()

    Vergleicht man das erzeugte verschlüsselte Passwort mit einem ähnlichen, beispielsweise PW = "22ghM;OP", so fällt auf, dass das verschlüsselte Passwort ähnlich ist. Somit wäre es ein einfaches, das Prinzip zu entschlüsseln und ein "Rückwärtsverfahren" zu entwickeln.

    PW = ["12ghM;OP", "22ghM;OP", "22ghM;OPP" ]
    
    print()
    for pw_ in PW: 
      print(pw_ + " -> ",end="")
      l_PW = len(pw_)
      f = 24 // l_PW + 1 
      pw_ *= f 
      j = 0
      for i in pw_[:24]:
        j += 1
        print(chr(ord(i)+len(pw_)+j),end="")
      print()

    Verwendet man dagegen eine sogenannte Hashfunktion, die normalerweise eine große Datenmenge auf eine kleinere abbildet, aber dennoch hier benutzt werden kann, denn sie ergibt eine vollständig andere Ausgabe, sobald auch nur ein Zeichen verändert wird:

    from hashlib import blake2b
    PW = ["12ghM;OP", "22ghM;OP", "22ghM;OPP", "ä", "ö"]
    
    print()
    for pw_ in PW: 
      pw_decode = blake2b(bytes(pw_,'utf-8'),digest_size=24).hexdigest()
      print("{:<10} -> {}".format(pw_, pw_decode))
    print()

    Voraussetzung ist, dass die Zeichenkette in Byteform vorliegt, was hier mit der Funktion bytes() einfach erfolgen kann.

    Auswahlmenü

    Folgendes Python-Programm soll so erweitert werden, dass der Anwender aus den vorgegebenen fünf Varianten durch Eingabe von 1,2,...,5 wählt, oder alternativ durch Einagbe der Parameter für die komplexe Konstante c=a+i b oder durch Eingabe von q für quit alles beendet.

    import numpy as np
    import matplotlib.pyplot as plt
    
    def julia_set(c=-0.4 + 0.6j, height=800, width=1000, x=0, y=0, zoom=1, max_iterations=100):
        # To make navigation easier we calculate these values
        x_width = 1.5
        y_height = 1.5*height/width
        x_from = x - x_width/zoom
        x_to = x + x_width/zoom
        y_from = y - y_height/zoom
        y_to = y + y_height/zoom
        # Here the actual algorithm starts
        x = np.linspace(x_from, x_to, width).reshape((1, width))
        y = np.linspace(y_from, y_to, height).reshape((height, 1))
        z = x + 1j * y
        # Initialize z to all zero
        c = np.full(z.shape, c)
        # To keep track in which iteration the point diverged
        div_time = np.zeros(z.shape, dtype=int)
        # To keep track on which points did not converge so far
        m = np.full(c.shape, True, dtype=bool)
        for i in range(max_iterations):
            z[m] = z[m]**2 + c[m]
            m[np.abs(z) > 2] = False
            div_time[m] = i
        return div_time
    
    plt.imshow(julia_set(), cmap='magma')
    #plt.imshow(julia_set(x=0.125, y=0.125, zoom=10), cmap='magma')
    # plt.imshow(julia_set(c=-0.8j), cmap='magma')
    #plt.imshow(julia_set(c=-0.8+0.156j, max_iterations=512), cmap='magma')
    #plt.imshow(julia_set(c=-0.7269 + 0.1889j, max_iterations=256), cmap='magma')
    plt.show()

    Eine Liste aller colormaps erhält man mit folgendem Program:

    import matplotlib as mpl
    import matplotlib.pyplot as plt
    def plot_all_cmaps():
        N_ROWS, N_COLS = 8, 7 # 13, 13 <-- for all in one figure 
        HEIGHT, WIDTH = 7, 14
        cmap_ids = plt.colormaps()
        n_cmaps = len(cmap_ids)
        print(f'mpl version: {mpl.__version__},\nnumber of cmaps: {n_cmaps}')
        index = 0
        while index < n_cmaps:
            fig, axes = plt.subplots(N_ROWS, N_COLS, figsize=(WIDTH, HEIGHT))
            for row in range(N_ROWS):
                for col in range(N_COLS):
                    ax = axes[row, col]
                    cmap_id = cmap_ids[index]
                    cmap = plt.get_cmap(cmap_id)
                    mpl.colorbar.ColorbarBase(ax, cmap=cmap,
                                              orientation='horizontal')
                    ax.set_title(f"'{cmap_id}', {index}", fontsize=8)
                    ax.tick_params(left=False, right=False, labelleft=False,
                                   labelbottom=False, bottom=False)
                    
                    last_iteration = index == n_cmaps-1
                    if (row==N_ROWS-1 and col==N_COLS-1) or last_iteration:
                        plt.tight_layout()
                        #plt.savefig('colormaps'+str(index)+'.png')
                        plt.show()
                        if last_iteration: return
                    index += 1
    plot_all_cmaps()

    Texte lesen und analysieren

    Textanalyse

    Das automatisierte Trennen von Wörtern ist immer noch eine Herausforderung. Da es keine Datenbanken gibt, die alle Wörter mit richtigen Trennungen enthält, muss mit wahrscheinlichkeitsbasierten Trennmustern (Pattern) gearbeitet werden. Dazu benötigt man erst einmal Wortlisten und zwar möglichst große, beispielsweise mindestens 1 GByte. Die werden entsprechend der Häufigkeit der Wörter sortiert und dann die häufigsten 500.000 Wörter per Hand getrennt. Das muss sein, damit man sicher ist, dass die Trennungen korrekt sind. Aus diesen Wörter ermitteln man dann Muster, die angeben, wie groß die Wahrscheinlichkeit ist, dass beispielsweise zwischen den Zeichen "ac" und "he" getrennt werden kann. In diesem Fall wäre es für die deutsche Sprache sehr unwahrscheinlich. Dieses Verfahren wurde bereits 1980 für das Satzprogramm $\TeX$ entwickelt und die nach dem Prinzip bestimmten entsprechenden Trennmuster noch heute benutzt.

    Das folgende Programm ermittelt die Worthäufigkeit der Datei maturin.txt (maturin.zip), einem englischsprachigen Text. Es werden dabei einige lokale Dateien erzeugt:

  • Alphaliste_alpha.txt
  • Alphaliste_anzahl.txt
  • Wortliste_alph.txt
  • Wortliste_anzahl.txt

    Das Program lautet:

    from collections import Counter # pip3 install collections
    
    # Zählt Worthäufigkeit
    def count_words(text, output=False):				 
      skips = [".", ",", ":", ";", "'", '"',")","(","#", "*", "“", "”", "_", "!", "--", "[", "]", "/", "&", "}", "´", "`", "'", "’", "‘", "£", "?" ] 
      for ch in skips: 
        text = text.replace(ch, " ") # alles durch Leerzeichen ersetzen
      word_counts = {} 
      alpha_counts = {}
      max_alpha = 1
      max_alpha_view = 100           # nur prozentuale Ausgabe
      for word in text.split(" "):   # alles zwischen zwei Leerzeichen ist ein Wort
        if word in word_counts: 
          word_counts[word]+= 1      # Worthäufigkeit
        else: 
          word_counts[word]= 1 
        for c in word.upper():       # keine Groß-/Kleinschreibung
          if c in alpha_counts:      # Zeichenhäufigkeit
            alpha_counts[c]+= 1 
            if alpha_counts[c] > max_alpha:
              max_alpha += 1
          else: 
            alpha_counts[c]= 1 
        
      if output:
        out = open("Wortliste_alph.txt","w")
        sortedList = dict(sorted(word_counts.items()))#  nur für Python Version > 3.6
        for key, value in sortedList.items():
          out.write("{:14} {:.0f}".format(key, value))
          out.write("\n")
        out.close()
    
        out = open("Wortliste_anzahl.txt","w")
        sorted_values = sorted(word_counts, key=word_counts.get, reverse=True)
        for r in sorted_values:
          out.write("{:14} {:.0f}".format(r, word_counts[r]))
          out.write("\n")
        out.close()
    
        out = open("Alphaliste_alpha.txt","w")
        sortedList = dict(sorted(alpha_counts.items()))#  nur für Python Version > 3.6
        for key, value in sortedList.items():
          out.write("{:14} {:.0f}".format(key, value))
          out.write("\n")
        out.close()
        rel_max_alpha = max_alpha_view / max_alpha   # relative value
        for key, value in sortedList.items():
          for i in range(0,int(value*rel_max_alpha)+1):
            print(key,end="")
          print()
    
        out = open("Alphaliste_anzahl.txt","w")
        sorted_values = sorted(alpha_counts, key=alpha_counts.get, reverse=True)#  nur für Python Version > 3.6
        for r in sorted_values:
          out.write("{:3} {:.0f}".format(r, alpha_counts[r]))
          out.write("\n")
        out.close()
        for r in sorted_values:
          for i in range(0,int(alpha_counts[r]*rel_max_alpha)+1):
            print(r,end="")
          print()
    
      return word_counts
    
    
    # Worthäufigkeit mit dem Zähler des Moduls collections bestimmen
    def count_words_fast(text, output=False):	 
      text = text.lower() 
      skips = [".", ", ", ":", ";", "'", '"'] 
      for ch in skips: 
        text = text.replace(ch, "") 
        word_counts = Counter(text.split(" ")) 
      return word_counts 
    
    #read a book and return it as a string 
    def read_book(title_path): 
      with open(title_path, "r", encoding ="utf8") as current_file: 
        text = current_file.read() 
        text = text.replace("\n", " ").replace("\r", " ") 
      return text 
    
    # word_counts = count_words_fast(text) 
    def word_stats(word_counts):	 
      num_unique = len(word_counts) 
      counts = word_counts.values() 
      return (num_unique, counts) 
    
    text = read_book("maturin.txt") 
    
    #print(text)
    #word_counts = count_words_fast(text,output=False)
    word_counts = count_words(text,output=True)
    (num_unique, counts) = word_stats(word_counts) 
    print("{} verschiedene Wörter bei insgesamt {} Wörtern.".format(num_unique, sum(counts))) 

    Daten aus HTML-Seiten extrahieren

    Das Institut für Meteorologie der Freien Universität Berlin liefert alle 10 Minuten aktuelle Wetterdaten: https://www.geo.fu-berlin.de/met/. Diese erscheinen in einem sogenannten Frame; sie werden erst im Moment des Aufrufens der Webseite geladen. Die ensprechende URL ist BerlinWetter; das PHP-Programm generiert die HTML-Seite "on-the-fly!.

    Von dieser Webseite lassen sich die Daten extrahieren, um sie für eigene Bedürfnisse weiter verwenden zu können:

    import os
    from datetime import date
    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    import ssl
    
    ssl._create_default_https_context = ssl._create_unverified_context
    
    def holeHTMLdaten(url,ausgabe=False):
      htmlfile = urlopen(url)
      htmltext = htmlfile.read().decode('utf-8')
      if ausgabe:
        print("Der HTML-code der Seite: \n", htmltext)
      htmlObject = BeautifulSoup(htmltext, 'html.parser')
      text = htmlObject.get_text()#                         html nach text wandeln
      heute = date.today().strftime("%Y-%m-%d")
      #  print(text,heute)
      datenzeile = text[text.find(heute):] # bis zum Rest kopieren
      return datenzeile.replace("\n"," ")
      # Beispieldatensatz
      #2022-03-06 18:10:00 MEZ Temperatur1.3 °C Luftfeuchte61 %
      #WindrichtungNNW ... 342 °  Geschwindigkeit2.5 m/s Windspitze4.1 m/s
      #Luftdruck NN1025.5 hPa   
    
    def holeWerte(daten, ausgabe=False):
      print(daten)#   nur zur Kontrolle!
      Werte = {}
      Werte["Datum"] = daten[0:daten.find("Temperatur")]#  von vorne bis Wort "Temperatur" erscheint 
      Werte["Temperatur"] = daten[daten.find("Temperatur"):daten.find("Luftfeuchte")].split()
      Werte["Temperatur"][0] = Werte["Temperatur"][0].replace("Temperatur","") 
      Werte["Luftfeuchte"] = daten[daten.find("Luftfeuchte"):daten.find("Windrichtung")].split()
      Werte["Luftfeuchte"][0] = Werte["Luftfeuchte"][0].replace("Luftfeuchte","") 
      Werte["Windrichtung"] = daten[daten.find("Windrichtung"):daten.find("Geschwindigkeit")].split()
      Werte["Windrichtung"][0] = Werte["Windrichtung"][0].replace("Windrichtung","") 
      Werte["Geschwindigkeit"] = daten[daten.find("Geschwindigkeit"):daten.find("Windspitze")].split()
      Werte["Geschwindigkeit"][0] = Werte["Geschwindigkeit"][0].replace("Geschwindigkeit","") 
      Werte["Windspitze"] = daten[daten.find("Windspitze"):daten.find("Luftdruck")].split()
      Werte["Windspitze"][0] = Werte["Windspitze"][0].replace("Windspitze","") 
      Werte["Luftdruck"] = daten[daten.find("Luftdruck"):].split()
      Werte["Luftdruck"][0] = Werte["Luftdruck"][0].replace("Luftdruck","") 
      if ausgabe:
        print()
        print("Ermittelte Daten aus der Webseite:")
        for w in Werte:
          print(w,Werte[w])
      return Werte
    
    datenZeile = holeHTMLdaten('https://page.met.fu-berlin.de/BerlinWetter',ausgabe=False)
    Werte = holeWerte(datenZeile,ausgabe=True)

    Benötigt werden die Python-Module:

  • os
  • datetime
  • urllib.request
  • bs4
  • Sie können auf dem eigenen Rechner durch pip3 install ... installiert werden oder direkt aus dem Python-Code heraus mit:

    import subprocess
    import sys
    
    def install(package):
        subprocess.check_call([sys.executable, "-m", "pip", "install", package])
        
    # Beispiel:
    install("bs4")

    Alternative Lösung mit html2text

    Das Modul vereinfacht die Umwandlung einer html-Seite in reinen Text:

    import html2text as ht
    import requests
    
    def holeDaten(url, Ausgabe=False):
      html = requests.get(url)
      text = ht.html2text(html.text)  # html nach text wandeln
      if Ausgabe:
        print(text) # text ist _eine_ Zeichenkette    (string)
      z_text = text.split("\n") # in Zeilen aufteilen (list)
      z_text.pop(1)#   jetzt nur noch Daten, Zeile mit --- ist "entsorgt" worden
      if Ausgabe:
        for z in z_text:
          print(z)  # nur für Kontrolle
      return z_text
    
    data = holeDaten('https://page.met.fu-berlin.de/BerlinWetter',True)

    Weitere Informationen zu "komma-separierten" Datensätzen findet man hier.


    Nächste Einheit: 07 Grafische Ausgaben