Donnerstag, 9. Oktober 2008

Strings verschlüsseln mit C# - Part 2

Vor einiger Zeit hat chico beschrieben, wie man einen String mit C# verschlüsseln kann. Leider hatte der Beitrag den kleinen Schönheitsfehler, dass die Methode zur Verschlüsselung ein Byte-Array zurückgeliefert hat. Für manche Situationen mag das durchaus ausreichend sein, doch wenn man den verschlüsselten String beispielsweise in einer Datenbank ablegen möchte, wird man mit dem Byte-Array vermutlich nicht weit kommen. Daher möchte ich heute, aufbauend auf dem ersten Artikel zur Thematik, erläutern, wie man statt dem Byte-Array einen String zurückliefern kann.

Auch hier müssen zuerst wieder ein Schlüssel sowie ein Initialisierungsvektor angelegt werden.

   1:  private readonly byte[] key = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
   2:      13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
   3:  private readonly byte[] iv = new byte[] { 65, 110, 68, 26, 69, 178, 200, 219 };

Die Methode zur Verschlüselung sieht sich ebenfalls noch ziemlich ähnlich. Es wurde lediglich eine Zeile geändert, eine neue kam hinzu.

   1:  /// <summary>
   2:  /// Verschlüsselt einen Eingabestring.
   3:  /// </summary>
   4:  /// <param name="input">Der zu verschlüsselnde String.</param>
   5:  /// <returns>Byte-Array mit dem verschlüsselten String.</returns>
   6:  public string StringVerschluesseln(string input)
   7:  {
   8:      try
   9:      {
  10:          // MemoryStream Objekt erzeugen
  11:          MemoryStream memoryStream = new MemoryStream();
  12:   
  13:          // CryptoStream Objekt erzeugen und den Initialisierungs-Vektor
  14:          // sowie den Schlüssel übergeben.
  15:          CryptoStream cryptoStream = new CryptoStream(
  16:          memoryStream, 
  17:          new TripleDESCryptoServiceProvider().CreateEncryptor(this.key, this.iv),
  18:          CryptoStreamMode.Write);
  19:   
  20:          // Eingabestring in ein Byte-Array konvertieren
  21:          byte[] toEncrypt = new ASCIIEncoding().GetBytes(input);
  22:   
  23:          // Byte-Array in den Stream schreiben und flushen.
  24:          cryptoStream.Write(toEncrypt, 0, toEncrypt.Length);
  25:          cryptoStream.FlushFinalBlock();
  26:   
  27:          // Ein Byte-Array aus dem Memory-Stream auslesen
  28:          byte[] encrypted = memoryStream.ToArray();
  29:   
  30:          // Stream schließen.
  31:          cryptoStream.Close();
  32:          memoryStream.Close();
  33:   
  34:          // Konvertierung in Base64-String
  35:          string encryptedString = Convert.ToBase64String(encrypted);
  36:   
  37:          // Rückgabewert.
  38:          return encryptedString;
  39:      }
  40:      catch (CryptographicException e)
  41:      {
  42:          Console.WriteLine(string.Format(
  43:              CultureInfo.CurrentCulture,
  44:              "Fehler beim Verschlüsseln: {0}", 
  45:              e.Message));
  46:   
  47:          return null;
  48:      }
  49:  }

Die Änderung finden wir in Zeile 21, statt ASCIIEncoding wird jetzt Encoding.UTF8 verwendet. Hinzugekommen ist Zeile 35. Hier wird das ermittelte Byte-Array in einen Base64String umgewandelt, der dann auch zurückgegeben wird.
Genau wie in der Verschlüsselungs-Methode ändert sich auch in ihrem Pendant wenig.

   1:  /// <summary>
   2:  /// Entschlüsselt einen String aus einem Byte-Array.
   3:  /// </summary>
   4:  /// <param name="data">Das verscghlüsselte Byte-Array.</param>
   5:  /// <returns>Entschlüsselter String.</returns>
   6:  public string StringEntschluesseln(string data)
   7:  {
   8:      try
   9:      {
  10:          // Verschluesselten Base64String in Byte-Array konvertieren
  11:          byte[] encrypted = Convert.FromBase64String(data);
  12:   
  13:          // Ein MemoryStream Objekt erzeugen und das Byte-Array
  14:          // mit den verschlüsselten Daten zuweisen.
  15:          MemoryStream memoryStream = new MemoryStream(encrypted);
  16:   
  17:          // Ein CryptoStream Objekt erzeugen und den MemoryStream hinzufügen.
  18:          // Den Schlüssel und Initialisierungsvektor zum entschlüsseln verwenden.
  19:          CryptoStream cryptoStream = new CryptoStream(
  20:          memoryStream,
  21:          new TripleDESCryptoServiceProvider().CreateDecryptor(this.key, this.iv), 
  22:          CryptoStreamMode.Read);
  23:   
  24:          // Buffer erstellen um die entschlüsselten Daten zuzuweisen.
  25:          byte[] fromEncrypt = new byte[data.Length];
  26:   
  27:          // Read the decrypted data out of the crypto stream
  28:          // and place it into the temporary buffer.
  29:          // Die entschlüsselten Daten aus dem CryptoStream lesen
  30:          // und im temporären Puffer ablegen.
  31:          cryptoStream.Read(fromEncrypt, 0, fromEncrypt.Length);
  32:   
  33:          // Den Puffer in einen String konvertieren und zurückgeben.
  34:          return Encoding.UTF8.GetString(fromEncrypt).Replace("\0", string.Empty);
  35:      }
  36:      catch (CryptographicException e)
  37:      {
  38:          Console.WriteLine(String.Format(
  39:              CultureInfo.CurrentCulture,
  40:              "Fehler beim Entschlüsseln: {0}",
  41:              e.Message));
  42:   
  43:          return null;
  44:      }
  45:  }

Die erste Änderung findet sich - natürlich - direkt in der Signatur der Methode (Zeile 6). Entgegengenommen wird jetzt natürlich kein Byte-Array mehr, sondern ein String. Entsprechend haben wir in Zeile 11 dann die Umwandlung des Base64Strings in ein Byte-Array, das dann weiter verarbeitet wird.  Schlussendlich haben wir dann in Zeile 34 die finale Konvertierung in den Rückgabe-String. Auch hier wird statt ASCIIEncoding wieder UTF8 verwendet, schließlich ist das ja jetzt die Basis. Eine Besonerheit ist hierbei, dass am fertigen String einige "\0"s hängen. Diese müssen noch entfernt werden, was hier durch den Aufruf von Replace geschieht.

Der jetzt zurückgegebene String entspricht wieder genau dem, der ursprünglich verschlüsselt wurde. Alles andere wäre natürlich auch inakzeptabel.

Bleiben jetzt nur die Fragen, warum der Umstieg von ASCIIEncoding auf UTF8 sein musste und warum das Byte-Array nicht direkt in einen normalen String, sondern in einen Base64String konvertiert wird. Beides hängt damit zusammen, dass ein Aufruf von ToString() zu falschen Ergebnissen bzw. sogar zu einer Exception in Zeile 29 (beim Entschlüsseln) führt. Dies hängt offenbar damit zusammen, dass der String im Normalfall Steuerzeichen (Stichwort Backslash) enthält. Durch die gemachten Änderungen wird dies umgangen.

Für die endgültige Lösung möchte ich chico danken, der maßgeblich an der Entwicklung beteiligt war.

2 Kommentare:

Anonym hat gesagt…

Sehr hilfreich, auch wenn sich hier ein kleiner Fehler eingeschlichen hat...(in Zeile 21 der Verschlüsselungsfunktion wurde immer noch in ASCII kodiert)
von der MSDN-Doku wird man ja zu dem Thema ziemlich im Stich gelassen, wenn man nicht mit FileStreams arbeiten will

Seb hat gesagt…

DANKE DANKE DANKE!! Ich glaube da wäre ich in Jahren nicht drauf gekommen. Trotz massiver Suche über google.. es gibt zwar ein paar Codefetzen, aber zu so einem simplen Codebeispiel ist MS wohl nicht fähig. Es gibt noch einen netten Beitrag über AES, allerdings muss dort die AES-Implementierung selbst vorgenommen werden ->
http://msdn.microsoft.com/de-de/magazine/cc164055%28en-us%29.aspx#S8

Für AES einfach den TripleDESCryptoServiceProvider() durch AesCryptoServiceProvider() ersetzen, die Variablen key und iv entsprechend der Länge anpassen, fertig :)