“Matthias”, sagte mein Chef, “wir brauchen mal eben einen Passwort-Generator” und schickte mir diese Anforderungen
1 2 3 4 5 6 |
Zw. 8 - 14 Zeichen lang Start und Ende KEIN Sonderzeichen Mind. 2 Sonderzeichen Mind. 2 Großbuchstaben Mind. 2 Kleinbuchstaben Mind. 2 Ziffern |
Das ist nicht schwer, oder? Die Kommentare im Java-Quelltext sind etwas ausführlicher als für mich üblich, weil ich hoffe, es allgemein unterhaltsam zu machen. LotusScript ist unkommentiert – quick’n’dirty.
Der gezeigte Java-Quelltext entstand, nachdem ich die Aufgabe in LotusScript erledigt hatte. Die Variante in Java gefällt mir, weil sie deutlich zeigt, welche Anforderungen erfüllt werden. Der Quelltext in Java ist viel länger – das kann man als Nachteil werten. Aber er ist flexibler – dem LotusScript-Code kann man wegen der Starrheit keinen Vorwurf machen, denn schließlich musste es fertig werden.
Im grossen und ganzen beruhen beide auf derselben Idee, man erkennt viele Ähnlichkeiten, beispielsweise Definition der Menge auszuwählender Zeichen, beispielsweise zufallsgeleitetes finden einer freien Position.
WordPress erlaubt es mir nicht, jar-Dateien hochzuladen. What a crap! Anyway, hier ist der Quelltext. Du willst diesen Code benutzen? Erzeuge ein PasswordGenerator-Objekt und rufe getPassword auf. Deadsimple.
Im folgenden die wichtigsten Klassen.
PasswordGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
package de.aysx.pwgen; import java.util.Random; import de.aysx.pwgen.common.Auffueller; import de.aysx.pwgen.common.Bedingungserfueller; import de.aysx.pwgen.impl.AuffuellerAlleZeichen; import de.aysx.pwgen.impl.AuffuellerGross; import de.aysx.pwgen.impl.AuffuellerKlein; import de.aysx.pwgen.impl.AuffuellerSZ; import de.aysx.pwgen.impl.AuffuellerZiffer; import de.aysx.pwgen.impl.KeinSZamAnfang; import de.aysx.pwgen.impl.KeinSZamEnde; /** * Einstiegspunkt für das Verständnis des PasswordGenerator-Projekts. * Diese Klasse wird benutzt, um ein Passwort zu generieren. * * Kontrolliert, wie das Passwort erzeugt wird. * Steuert die Erzeuger-Klassen, steckt sie passend zusammen. * */ public class PasswordGenerator { /** * Minimale Länge des Passworts.<br> * Wenn man es noch flexibler möchte, dann macht man aus diesem Wert eine * Variable. Für's erste ist es eine Konstante. * */ public static final int MIN = 8; /** * Maximale Länge des Passworts.<br> * s.a. MIN. */ public static final int MAX = 14; /** * * @return ein zufälliges Password, das den Anforderungen entspricht. */ public String getPassword() { int lange = zufallsLaenge(); // Eine Zahl zwischen MIN und MAX. int egal = 0; // Wird für die syntaktische Korrektheit benötigt. /* Diese Variable enthält die Zeichen des Passworts. Noch ist * sie leer. */ char[] c = new char[lange]; /* Hier spiegelt sich deutlich die Anforderung wider, welche Zeichen * enthalten sein sollen und wieviele jeweils. * SZ steht für "Sonderzeichen". */ Auffueller fueller = new AuffuellerKlein ( 2, new AuffuellerSZ ( 2, new AuffuellerZiffer ( 2, new AuffuellerGross ( 2, /* Wenn das PW mehr als 8 Zeichen hat, dann wird der restliche Freiraum mit beliebigen Zeichen aufgefüllt. Die Anzahl ist "egal", weil ein AuffuellerAlleZeichen dafür sorgt, dass alle restlichen freien Positionen belegt werden. */ new AuffuellerAlleZeichen ( egal, null ))))); /* Ein weiterer Teil der Anforderung - deutlich sichtbar mit * wenig Phantasie. */ Bedingungserfueller bed = new KeinSZamAnfang ( new KeinSZamEnde (null)); // Passwort erzeugen, Variable c verändern; Obacht: Output-Parameter. fueller.fuelle ( c ); bed.pruefenKorrigierenWeitergeben ( c, 0, c.length - 1 ); // Rückgabewert aufbereiten; String erg = ""; for (int i = 0; i < c.length; i++) { erg += new Character(c[i]); } // Fertig. Der uninteressierte Leser kann hier aufhören. return erg; } /** * @return eine Zahl zwischen MIN und MAX; */ private int zufallsLaenge() { int erg = 0; Random r = new Random(); erg = MIN + r.nextInt(MAX - MIN + 1); return erg; } } |
Konstanten
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * Konstanten im Projekt PasswordGenerator. */ public class Constants { /* Auf Wunsch sind einige Zeichen nicht enthalten. */ public static final String SONDERZEICHEN = "!§$%&/()=}][{-_.:,;<>"; /* Kleinbuchstaben */ public static final String KLEINBUCHSTABEN = "abcdefghijklmnopqrstuvwxyz"; /* Grossbuchstaben */ public static final String GROSSBUCHSTABEN = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /* Ziffern */ public static final String ZIFFERN = "0123456789"; } |
Auffueller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
package de.aysx.pwgen.common; import java.util.Random; /** * Fuellt ein Char[] mit der angegebenen Anzahl von Zeichen.<br> * Danach wird der nächste Auffueller aufgerufen, wenn einer vorhanden ist.<br> * <br> * Die unterschiedlichen konkreten Ausprägungen dieser Klasse leisten * wesentlich eins: Sie stellen eine jeweils passende Menge von Zeichen zur * Verfügung, aus der mittels Zufallsgenerator eine bestimmte Anzahl ausgewählt * wird.<br> * Beispiel: AuffuellerGross ( 2, ...) kennt alle Grossbuchstaben und wählt * aus dieser Menge zwei Buchstaben aus. */ public abstract class Auffueller { public Auffueller(int zeichenAnzahl, Auffueller next) { setAnzahl(zeichenAnzahl); setNext(next); } /** * Aus welcher Zeichenklassen sollen Zeichen gezogen werden? Aus dieser! */ public abstract String getZeichenklasse(); /** * Wieviele Zeichen sollen gezogen werden? So viele! */ public int getAnzahl() { return anzahl; } /** * Diskutieren: Soll dieser Member immutable sein? * s.a. http://en.wikipedia.org/wiki/Immutable */ public void setAnzahl(int i) { anzahl = i; } private int anzahl; /** * Ein Auffueller kann einen anderen Auffueller dekorieren, so dass zuerst * das eine getan wird und dann der nächste Auffueller das nächste tut usw. * bis zum Ende der Kette. * * @return Den dekorierten Auffueller. */ public Auffueller getNext() { return nextAuffueller; } /** * TODO: Immutable? */ public void setNext(Auffueller a) { nextAuffueller = a; } private Auffueller nextAuffueller; /** * TODO Beachten Konstraints, z.B. Laenge des Arrays >= getAnzahl, z.B. * Anzahl der freien Plätze im Array >= getAnzahl. Erstmal gehe ich davon * aus, dass schon alles gutgeht. Dieser Code ist noch provisorisch.<br> * * @param c das zu fuellende Array * @return ein char[], das entsprechend diesem Auffueller und seinen * dekorierten Auffuellern bearbeitet wurde. */ public char[] fuelle ( char[] c ) { for (int i = 0; i < getAnzahl(); i++) { c [ getLeerePosZufaellig ( c )] = getZufallschar (); } /* Wie Yoda ich spreche, weil oft zum Vorteil es ist in LotusScript * und abgefärbt das hat. http://de.wikipedia.org/wiki/Yoda_Conditions */ if (null != getNext()) { getNext().fuelle(c); } return c; } /** * @return Ein Zeichen, das zufällig aus der Menge der hier bekannten * Zeichen ausgewählt wird. */ public char getZufallschar () { Random random = new Random (); int max = getZeichenklasse ().length (); int pos = random.nextInt ( max ); char erg = getZeichenklasse ().charAt( pos ); return erg; } /** * Im LotusScript findet sich eine andere Implmentierung und die * Idee zum folgenden Allgorithmus. * * @param c Das zu füllende Array. * @return Eine zufällig ausgewählte leere Stelle in c. */ private int getLeerePosZufaellig ( char[] c ) { int erg = 0; /* Anzahl Leerstelle ermitteln. */ int anzahlLeer = 0; for ( int i = 0; i < c.length; i++ ){ if ( 0 == c[i]){ anzahlLeer ++; } } /* Mapping Leerstelle zu Index. */ int[] auswahl = new int [ anzahlLeer ]; int k = 0; for ( int i = 0; i < c.length; i++ ){ if ( 0 == c[i]){ auswahl [k] = i; k ++; } } /* Zufaellig auswaehlen und Zurueck-mapping. */ Random r = new Random (); erg = r.nextInt ( anzahlLeer ); return auswahl [erg]; } } |
Bedingungserfueller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package de.aysx.pwgen.common; /** * Prüft, ob ein Char[] einer bestimmten Bedingung genügt.<br> * Wenn es der Bedingung nicht genügt, dann werden Zeichen getauscht, so * dass die Bedingung erfüllt ist.<br> * Nachdem die Bedingung erfüllt ist, wird das zu prüfende Array an den * nächsten Bedingungsprüfer weitergegeben (Dekorator-Pattern). * */ public abstract class Bedingungserfueller { private Bedingungserfueller next; /** * @return Der nächste Bedingungserfüller. Darf null sein. */ public Bedingungserfueller getNext () { return next; } public void setNext ( Bedingungserfueller next ) { this.next = next; } public Bedingungserfueller ( Bedingungserfueller next ) { this.next = next; } /** * Diese Methode prüft, ob die jeweils zu implementierende Bedingung erfüllt * ist. Falls nicht, wird korrigiert. Und wenn es einen nächsten * Bedingungserfüller gibt, dann wird er aufgerufen. * * @param c das zu prüfende Array * @param vonPos Die Stelle, von der an das Array geprüft und verändert * werden soll. Wenn ich z.B. einen Erste-Stelle-Prüfer habe, und der hat die erste * Stelle korrigiert, dann muss verhindert werden, dass die Korrektur verändert * wird. Das erreiche ich, indem der vonPos einen passenden Wert hat. * * @param bisPos Die Stelle, bis zu der das Array geprüft und verändert * werden soll. Gegenstück zu vonPos. * * @return ein korrigiertes Array. * * TODO Methode umbenennen, weil "Weitergeben" redundant ist. */ public abstract char[] pruefenKorrigierenWeitergeben ( char[] c, int vonPos, int bisPos ); } |
Die konkreten Implementierungen von Auffueller und Bedingserfueller finden sich in der jar-Datei.
Und nun zu etwas ganz anderem: Ein Password-Generator in LotusScript. Entstanden ist er nach einer Skizze an der Tafel. So sind einige Grobheiten enthalten, Beispiel die Variablen S, T, G, K, Z, die zugehörenden *unten und *oben und deren Initialisierung. Die Java-Implementierung hat davon “gelernt”. Änderungen werden an diesem Quelltext nicht mehr vorgenommen: Er funktioniert, ist ausreichend schnell und leserlich, so daß auch die Kollegen ihn warten können. Mission erfüllt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
'++LotusScript Development Environment:2:5:(Options):0:74 %REM Library RandomPassword Created 10.07.2014 by Description: Comments for Library %END REM Option Public Option Declare '++LotusScript Development Environment:2:5:(Forward):0:1 Declare Sub Initialize Declare Function pickZufallUndEntferne ( stringArray As Variant ) As String Declare Function ArrayToString ( stringArray As Variant ) As String Declare Function findeLeerePosZufaellig ( stringArray As Variant ) As Integer Declare Function berechnePassword As String '++LotusScript Development Environment:2:5:(Declarations):0:10 Dim S As String Dim Sunten As Integer Dim Soben As Integer Dim T As String Dim Tunten As Integer Dim Toben As Integer Dim G As String Dim Gunten As Integer Dim Goben As Integer Dim K As String Dim Kunten As Integer Dim Koben As Integer Dim Z As String Dim Zunten As Integer Dim Zoben As Integer Dim sonder (1 To 2 ) As String Dim gkz (1 To 6 ) As String Dim Laenge_min As Integer Dim Laenge_max As Integer Dim Laenge_akt As Integer '++LotusScript Development Environment:2:2:Initialize:1:10 Sub Initialize ' --------------------------------------------------------------------------------------- ' init Konstanten ' --------------------------------------------------------------------------------------- S = "!§$%&/()=*'_:;,.-<>" Sunten = 1 Soben = Len ( S ) T = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" Tunten = 1 Toben = Len ( T ) G = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" Gunten = 1 Goben = Len ( G ) K = "abcdefghijklmnopqrstuvwxyz" Kunten = 1 Koben = Len ( K ) Z = "0123456789" Zunten = 1 Zoben = Len ( Z ) Laenge_min = 8 Laenge_max = 14 Laenge_akt = ( Laenge_max - Laenge_min )*Rnd() + Laenge_min ' --------------------------------------------------------------------------------------- ' init Variablen ' --------------------------------------------------------------------------------------- Dim i As Integer i = 0 Dim tmp As Integer For i = 1 To 2 tmp = ( Soben - Sunten )*Rnd() + Sunten sonder ( i ) = Mid ( S, tmp, 1 ) Next For i = 1 To 2 tmp = ( Goben - Gunten )*Rnd() + Gunten gkz ( i ) = Mid ( G, tmp, 1 ) Next For i = 3 To 4 tmp = ( Koben - Kunten )*Rnd() + Kunten gkz ( i ) = Mid ( K, tmp, 1 ) Next For i = 5 To 6 tmp = ( Zoben - Zunten )*Rnd() + Zunten gkz ( i ) = Mid ( Z, tmp, 1 ) Next Dim erg As String erg = ArrayToString ( sonder ) erg = erg + ArrayToString ( gkz ) End Sub '++LotusScript Development Environment:2:1:pickZufallUndEntferne:5:8 %REM Function pickZufallUndEntferne Description: Comments for Function %END REM Function pickZufallUndEntferne ( stringArray As Variant ) As String Dim max As Integer max = UBound( stringArray ) Dim tmp As Integer tmp = ( max - 1 ) * Rnd() + 1 Dim erg As String erg = stringArray ( tmp ) While ( erg = "" ) tmp = ( max - 1 ) * Rnd() + 1 erg = stringArray ( tmp ) Wend stringArray (tmp) = "" pickZufallUndEntferne = erg End Function '++LotusScript Development Environment:2:1:ArrayToString:2:8 Function ArrayToString ( stringArray As Variant ) As String Dim erg As String erg = "" ForAll x In stringArray erg = erg + x End ForAll ArrayToString = erg End Function '++LotusScript Development Environment:2:1:findeLeerePosZufaellig:5:8 %REM Function findeLeerePosZufaellig Description: Bedenke, dass es lange dauert in einem grossen Array, das nur noch eine Leerstelle hat, die freie Stelle so zu "finden". Vorschlag, Skizze: Sei n Anzahl Leerstellen und es gebe ein Mapping für 0 <= i <= n für den Index der Leerstelle. Berechne k als Zufallszahl zwischen 0 und n-1 und ermittle durch das Mapping den gesuchten Index. %END REM Function findeLeerePosZufaellig ( stringArray As Variant ) As Integer Dim erg As Integer erg = 0 Dim s As String s = stringArray ( erg ) Dim max As Integer max = UBound( stringArray ) While ( s <> "" ) erg = ( max - 1 ) * Rnd() + 1 s = stringArray ( erg ) Wend findeLeerePosZufaellig = erg End Function '++LotusScript Development Environment:2:1:berechnePassword:5:8 %REM Function berechnePassword Description: Comments for Function %END REM Function berechnePassword As String Call Initialize() Dim erg () As String ReDim erg ( Laenge_akt ) As String ' Kein SZ am Anfang und Ende. erg (0) = pickZufallUndEntferne ( gkz ) erg ( Laenge_akt-1 ) = pickZufallUndEntferne ( gkz ) Dim rndPos As Integer ' Grossbuchstaben, Kleinbuchstaben und Ziffern verteilen. ForAll x In gkz If Not ( x = "" ) Then rndPos = findeLeerePosZufaellig( erg ) erg ( rndPos ) = x End If End ForAll ' Sonderzeichen verteilen. ForAll x In sonder rndPos = findeLeerePosZufaellig( erg ) erg ( rndPos ) = x End ForAll ' Nun enthaelt erg 8 nicht-leere Zeichen. ' Die Leerstellen fuellen mit Zufallsstelle aus T. ForAll x In erg If ( x = "" ) Then rndPos = ( Toben - Tunten )*Rnd() + Tunten x = Mid ( T, rndPos, 1 ) End If End ForAll berechnePassword = ArrayToString( erg ) End Function |
Im LotusScript hat man einen guten Überblick über den gesamten konkreten Algorithmus, in Java erkennt man nur Umrisse. Die Java-Klassen kann ich an anderer Stelle wiederverwenden a) falls so etwas wieder gebraucht wird – mit der Perspektive “nie” und b) falls ich mich daran erinnere, dass die genau passenden Klassen genau hier zu finden sind. Das LotusScript ist “nur” als ganzes wiederverwendbar. Nur weil es nicht objektorientiert ist, heisst das nicht, dass es schlecht ist. Just because you’re paranoid it don’t mean they’re not after you. Have a nice day.