<?xml version="1.0" encoding="iso-8859-1" ?>
<?xml-stylesheet href="blog.xsl" type="text/xsl" ?>
<blog>
	<datum>17.08.2026</datum>
	<headline>Secure Coding</headline>
	<teaser>
		Bei der Programmierung stelle ich immer wieder fest, wie leichtsinnig manche 
		Programmierer:innen bzw. Entwickler:innen mit der Sicherheit der Daten umgehen. 
		In diesem Beitrag beschäftigen wir uns, wie Sie bei der Programmierung wichtige 
		Fehler vermeiden können, die die Sicherheit Ihrer Daten gefährden können.
	</teaser>
	<para>
		Mir selber ist es im Übrigen auch schon mal passiert, dass ich versehentlich 
		eine Konfigurationsdatei mit dem Passwort des DB-Servers in GitHub gestellt habe.
		Wachsamkeit tut selbst den alten Hasen gut. Ich habe natürlich den Fehler sofort
		bemerkt und, weil es eh ein neues Repository war, habe ich es gelöscht, das DB-Kennwort 
		geändert und das Repository neu erzeugt. Und da sind wir auch schon beim ersten 
		Ratschlag, den ich Ihnen geben kann, speichern Sie nie irgendwelche Passwörter 
		in Ihren Quelltextdateien. Auch nicht, wenn Sie etwas kurz ausprobieren wollen. 
		Laden Sie Kennwörter aus Konfigurationsdateien oder Umgebungsvariablen aber NIE
		aus Quelltextdateien. Die Konfigurationsdateien und die Umgebungsvariablen müssen
		natürlich vor unbefugten Zugriff gesichert werden. Wenn Ihnen das dumme Gemini 
		den Rat gibt, betimmte Kennwörter in Ihre build.gradle aufzunehmen, ignorieren 
		Sie den Rat.
	</para>
	<para>
		<b>Speichern von Kennwörtern</b><br/>
		Wenn Ihre Anwendung eine Benutzerdatenbank führen muß und Ihre Benutzer:innen sich 
		anmelden müssen, speichern Sie niemals deren Kennwörter in Ihrer Datenbank. Wenn 
		die Benutzer:innen Ihre Kennwörter ändern, verschlüsseln Sie diese und zwar mit 
		einem nicht umkehrbaren Algorithmus. Beim Anmelden verschlüsseln sie das eingegebene 
		Kennwort ebenfalls und vergleichen den Code mit den gespeicherten, verschlüsselten 
		Kennwörtern. Achten Sie aber trotzdem darauf, daß Ihre Datenbank mit den Kennwörtern 
		vor unbefugten Zugriff geschützt ist.
	</para>
	<para>
		<b>Benutzeranmeldung</b><br/>
		Ein zweiter Tipp, der Ihre Benutzeranmeldung sicherer machen kann, wäre z.B. 
		nach einem Fehlversuch die Wartezeit bis zum nächsten erlaubten Versuch zu verdoppeln.
		Das führt dazu, dass ein versuchter Brute-Force-Angriff ganz schnell scheitern
		wird, weil die Wartezeit zwischen den Versuchen zu lange sein wird. Außerdem  
		sollten Sie spätestens nach dem dritten Versuch, den betroffenen Anwender:innen 
		eine E-Mail schicken, damit diese über den Einbruchsversuch informiert werden. 
		Wenn Ihre Anwendung eine Funktion enthält, mit der die Benutzer:innen sich ein 
		neues Kennwort generieren können, sollte diese von der Wartezeit ausgenommen 
		werden, damit die Benutzer:innen sich trotz des Angriffsversuch anmelden können. 
		Möglicherweise ist es auch sinnvoll, die IP-Adressen zu speichern, von denen 
		die Fehlversuche und die Passwortänderungen ausgingen, damit die Strafverfolgungsbehörden 
		eingeschaltet werden können.
	</para>
	<para>
		<b>Undefined Behaviour</b><br/>
		Sorgen Sie dafür, dass Ihre Programme immer nur das machen, was Sie wollen. Sie 
		sollten <b>immer</b> wissen, warum Ihre Programm was machen. In einer Firma, in
		der ich mal arbeitete, hatte ein Reviewer bei meinem Code kritisiert, dass ich 
		bei einem printf einen size_t-wert zu einem unsigned long gecastet hatte, statt 
		das Formatzeichen für size_t (%z) zu verwenden. Ich selben review kritisiert er
		aber auch die Verwendung des Schlüsselworts nullptr, das nicht eingesetzt werden 
		soll, weil auch ältere Compiler verwendet werden. Dumm nur, dass %z und nullptr 
		mit dem selben C++-Standard 2010 eingeführt wurden. Alte Compiler, die nullptr 
		nicht kennen, kennen daher auch nicht %z. Ein Compiler, der nullptr nicht kennt,
		muß einen Fehler generieren. Eine Bibliothek, die %z nicht kennt, darf alles 
		machen (Undefined Behaviour). Auf meinen Hinweis, dass das Review so nicht akzeptabel
		ist, hat der Kollege nicht reagiert. Das ist eine Arbeitsauffassung, denen die 
		Kunden egal sind.
	</para>
	<para>
		<b>CPU</b><br/>
		Wenn Sie jetzt nicht gerade in Assembler programmieren und sehr hardwarenahe 
		Aufgaben lösen müssen, sollten Sie sich niemals auf die Architektur der CPU verlassen. 
		Insbesondere Bigendian- und Littleendian-CPUs speichern größere Zahlen andersrum.
		Das höherwertige Byte befindet sich bei Bigendian genau da, wo bei Littleendian 
		das niederwertige Byte steht. Die gleiche Firma hat sich auch hier nicht darum 
		gekümmert und ist grundsätzlich von einer Littleendian-CPU ausgegangen. Gut, 
		das mag zur Zeit vielleicht noch OK sein, weil Bigendiansysteme nicht mehr hergestellt 
		werden. Wer garantiert aber, dass neue Prozessoren nicht wieder auf Bigendian 
		umsteigen? Dann kann es passieren, dass im Mischbetrieb Server und Client IDs 
		völlig unterschiedlich interpretieren und im Extremfall Daten verlieren. Daher 
		würde ich zumindest erwarten, dass in der Sourcecodedokumentation dieser Umstand 
		vermerkt wird, so dass man weiß, was getestet werden muss, wenn eine neue CPU 
		eingeführt werden soll.
	</para>
	<para>
		<b>Arrayzugriffe</b><br/>
		Achten Sie bei Arrayzugriffen immer darauf, dass Sie niemals über die Grenzen 
		Ihres Arrays lesen und schreiben. In C müssen Sie die Größe des Array immer speichern
		und gegebenenfalls allen Funktionen zusätzlich mitgeben. In C++ können sie einen
		Vector verwenden, der sie automatisch mitgibt. Sie müssen aber vor dem Zugriff
		die Größe des Array überprüfen  und schauen, ob Ihr Index nicht zu groß ist. 
		Wenn Performanz eine Rolle spielt, können Sie auch auch mit einer globalen Konstante 
		arbeiten, die die Größe des Arrays enthält. Mein Synthesizer arbeitet mit 4 Tongeneratoren,
		die in einem Array gespeichert sind. Ein Array, das wie in Java jeden Zugriff 
		überprüft, ist hier natürlich unbrauchbar, wenn mehrere 100 Tausend Zugriffe 
		pro Sekunde erforderlich sind. Abgesehen von diesen extremen Anforderungen sollten
		Sie aber immer geprüfte Arrayzugriffe verwenden, auch wenn sie eine Ausnahme 
		werfen sollten.
	</para>
	<para>
		<b>Exceptionsafe</b><br/>
		Achten Sie darauf, dass alle Ihre Funktionen Exceptionsafe sind. Das heißt alle 
		Aufräumarbeiten am Ende Ihrer Funktion müssen über Destruktoren erledigt werden.
		Verwenden Sie am besten dazu die Smartpointer von C++ oder die C++ iostream Klassen
		In Java sollten Sie darauf achten, dass Sie try-catch Blöcke verwenden, die für die
		notwendigen Aufräumarbeiten sorgen. Wenn Sie keine Standardklassen und auch keine 
		eigenen verwenden können, müssen Sie mit try ... catch (C++) oder try ... finaly 
		(java) Ihre Aufräumarbeiten erledigen.<br/>
		Wenn Sie das nicht machen, kann es passieren, daß Ressourcen nicht freigegeben 
		werden und Ihr Programm oder der ganze Rechner immer langsamer wird oder gar 
		abstürzt und angreifbar wird (vulnerabilities).
	</para>
	<para>
		<b>Threadsafe</b><br/>
		Sobald eine Funktion Daten verarbeitet, die <b>nicht</b> lokal gespeichert werden,
		sollten Sie sich überlegen, ob ein gleichzeitiger Zugriff auf diese Daten in 
		einem anderen Thread möglich ist oder nicht. Wenn in Ihrem Programm gar keine 
		Threads verwendet werden, sind Sie fein raus und müssen nichts machen. Wenn doch,
		müssen Sie sich überlegen, ob und wie Sie den Schutz gewährleisten müssen. Da 
		das sehr von Ihrem Programm und der Programmiersprache abhängt, werde ich hier 
		nicht genauer darauf eingehen. Es geht mir nur darum, dass Sie sich Gedanken 
		darum machen. Achten Sie aber darauf, dass Ihre Funktionen nach Möglchkeit mit 
		lokalen Variablen auskommen sollten. Dann wird es später Ihnen leichter Fallen,
		Threads einzuführen.
	</para>
	<para>
		<b>Valgrind</b><br/>
		Arbeiten Sie mit valgrind oder einem anderen Prüfprogramm. Valgrind kann Ihnen 
		zeigen, wenn Speichergrenzen missachtet wurden, Speicherblöcke benutzt wurden,
		die freigegeben wurden, Kopieroperation sich überlagerten, Variablem nicht 
		initialisiert wurden und noch viel mehr. Zumindest bei jedem größeren Testzyklus
		sollten Sie sich überlegen, valgrind reports zu erstellen und bearbeiten.
	</para>
	<para>
		<b>Unittests</b><br/>
		Machen Sie Unittests für Ihre Quellen. Unittests haben die Aufgabe, die Funktionalität
		Ihrer Quellen zu überprüfen. Sie werden üblicherweise automatisch im Rahmen des 
		Buildprozesses ausgeführt. Verwenden Sie z.B. JUnit, Google Test oder mein eigenes 
		Framework auf Github. Beim Implementieren Ihrer Unittests achten Sie darauf, 
		eine möglichst hohe Codeabdeckung zu erreichen, aber auch alle "Corner cases" zu
		überprüfen. Also die Werte, an denen sich das Verhalten Ihrer Funktionen ändern 
		soll. Die "Corner cases" sind in der Regel wichtiger wie eine 100% Abdeckung. 
		Das müssen Sie aber selbst entscheiden. Jeder Fehler, der mit Unittests vermieden 
		werden kann, erhöht umindest indirekt auch die Sicherheit Ihrer Programme.
	</para>
	<para>
		<b>Schlussbemerkung</b><br/>
		Die Sicherheit Ihrer Programme bekommen Sie natürlich nicht umsonst, aber sie 
		erhöht die Stabilität Ihrer Software, veringert somit Ihre Supportkosten und 
		stärkt das Vertrauen Ihrer Kunden. Darüberhinaus werden Hacker:innen es sich 
		zweimal überlegen, ob sie Ihr System angreifen wollen, wenn sie wissen, dass 
		Sie gut gerüstet dem begegnen.
	</para>
</blog>
