Benutzer mit den meisten Antworten
SymmetricAlgorithm - Initialisierungsvektor Informationen trotzdem wiederherstellbar?

Frage
-
Ich habe mal folgende Frage zum Initialisierungsvektor (IV) in der Basisklasse SymmetricAlgorithm.
Ein String, der mittels einer Symetrischen Klasse verschlüsselt wird, sollte doch gemäss CBC-Mode-Definition jeden Block
mit dem vorangehenden verschlüsselten Block mittels XOR-Operation verketten. Wenn nun aber der IV verloren gehen würde, dann wären doch
auch alle nachfolgenden Blöcke, die mittels XOR verkettet wurden, verloren bzw nicht rekonstruierbar, Oder?Aber wie erklärt sich dann, dass wenn ich mittels Symetrischen Verschlüsselung in .NET diese Informationen Trotzdem wiederherstellen kann?
(Mit ausnahme des ersten Blocks, eben des IV's, und natürlich der Kenntniss des Schlüssels)Im Beispiel habe ich mal eine kleine Test-Anwendung zusammengestellt, die diesen Vorgang verdeutlichen soll.
Die Methode DecryptV2 kennt in diesem zusammenhang den IV nicht und überspringt bei der Rückgabe einfach die ersten 16-Bytes.
Trotzdem sind die restlichen Informationen noch rekonstruierbar.
Dieses Verhalten dürfte doch eigentlich nur beim ECB-Modus (Electronic Codebook) funktionieren, oder liege ich falsch?using System; using System.Linq; using System.Text; using System.IO; using System.Security.Cryptography; public class Test { private static SymmetricAlgorithm mCryptoProvider; public static void Main() { mCryptoProvider = new RijndaelManaged() { Key = new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes("geheim")) }; var lTests = new string[] { "Administrator", "12345", "the fox jumps over the lazy dog", "[" + new string('*', 50) + "]" }; foreach (var test in lTests) { var lChiffre = Encrypt(test); Console.WriteLine("Test : " + test); mCryptoProvider.GenerateIV(); // Generate a new Init-Vector Console.WriteLine("Result V1: " + DecryptV1(lChiffre)); mCryptoProvider.GenerateIV(); // Generate a new Init-Vector and Decrypt with skip Lenght of Init-Vector Console.WriteLine("Result V2: " + DecryptV2(lChiffre)); Console.WriteLine(); } Console.ReadLine(); } private static string Encrypt(string text) { mCryptoProvider.GenerateIV(); var lMemory = new MemoryStream(); lMemory.Write(mCryptoProvider.IV, 0, mCryptoProvider.IV.Length); var lStream = new CryptoStream(lMemory, mCryptoProvider.CreateEncryptor(), CryptoStreamMode.Write); var lBuffer = Encoding.UTF8.GetBytes(text); lStream.Write(lBuffer, 0, lBuffer.Length); lStream.FlushFinalBlock(); return Convert.ToBase64String(lMemory.ToArray()); } private static string DecryptV1(string base64) { var lCount = 16; var lMemory = new MemoryStream(Convert.FromBase64String(base64)); var lBuffer = new byte[lCount]; lMemory.Read(lBuffer, 0, lCount); mCryptoProvider.IV = lBuffer; lBuffer = new byte[(lMemory.Length - lCount) + 1]; var lStream = new CryptoStream(lMemory, mCryptoProvider.CreateDecryptor(), CryptoStreamMode.Read); var lIndex = lStream.Read(lBuffer, 0, lBuffer.Length); return Encoding.UTF8.GetString(lBuffer, 0, lIndex); } private static string DecryptV2(string base64) { var lBuffer = Convert.FromBase64String(base64); lBuffer = mCryptoProvider.CreateDecryptor().TransformFinalBlock(lBuffer, 0, lBuffer.Length); // return Encoding.UTF8.GetString(lBuffer); return Encoding.UTF8.GetString(lBuffer.Skip(mCryptoProvider.IV.Length).ToArray()); } }
Antworten
-
Hallo Donald,
> Ich gebe gerne zu, dass das Thema für mich sehr schwierig zu verstehen ist, aber aus reiner Neugier interessiert es mich halt doch [...]
Entschuldige, wenn es gleich ein bischen trocken wird, aber es geht leider nicht anders.
Deine zwei Code-Auflistungen unterscheiden sich in einem wesentlichen Punkt: Während im ersten Code der Initialisierungsvektor in das MemoryStream kopiert wird und damit als Teil des base64-kodierten String an die Decrypt-Methoden mit übergeben wird, fehlt der IV aus dem base64 String der an die Decrypt-Methode im zweiten Code übergeben wird.
Die mathematische Formel für die CBC-Entschlüsselung ist:Wie man hier sehen kann, wird der (C)ipherblock erst (D)ekodiert [entschlüsselt] und das Resultat mit dem vorherigen (C)ipherblock über XOR verknüpft. Grundvoraussetzung dafür ist, dass der erste Cipherblock den Initialisierungsvektor enthält.
Lass uns nun sehen, wie dies in System.Security.Cryptography.RijndaelManaged (mscorlib.dll) umgesetzt wurde.
Es wird Dir vielleicht aufgefallen sein, dass es hier keine Decrypt-Methode gibt, an die man das IV direkt übergeben könnte. Wie kommt das IV also in die Algorithmus-Implementation rein? Ganz einfach: Über den Konstruktor von RijndaelManagedTransform. Eine Instanz dieser Klasse wird nämlich zurückgegeben, wenn man mCryptoProvider.CreateDecryptor() aufruft. Bei diesem Aufruf wird der mit dem CryptoProvider gecachte IV an den Konstruktor von RijndaelManagedTransform übergeben, wo er ein zweites Mal gecacht wird: Der Konstruktor von RijndaelManagedTransform kopiert den IV an den Anfang eines Arrays von 4 Integern das den Namen m_lastBlockBuffer hat.
Nun läuft die Entschlüsselung ab [in RijndaelManagedTransform.DecryptData()]. Dabei wird die Formel oben berücksichtig: Erst entschlüsseln, dann XOR-en. Die inputBytes werden in Blöcke aufgeteilt, und diese Blöcke sequentiell durch den Algorithmus gejagt. Der erste 16 Byte Block wird also entschlüsselt und das Resultat mit dem Inhalt von m_lastBlockBuffer XOR-t. Da der Buffer bei der Bearbeitung des ersten Blocks den IV enthält, wird also gegen den IV XOR-t. Dann werden die ersten 16 Bytes des verschlüsselten Textes in m_lastBlockBuffer gespeichert und der zweite Block von 16 Input-Bytes entschlüsselt und gegen den Buffer XOR-t. Usw. usf.
Wenn man nun - wie Du im ersten Posting - die IV-Bytes dem verschlüsselten Text voranstellt, passiert folgendes: Der Algorithmus interpretiert diese 16 Bytes IV als Ciphertext und entschlüsselt sie. Dann werden die entschlüsselten Bytes gegen den falschen IV XOR-t (diesen hast Du zwischenzeitlich über GenerateIV neu erstellt), was zur Garbage-Ausgabe in der Konsole führt. Nun macht der Algorithmus mit dem nächsten Block weiter, und das ist der erste Block des eigentlichen, verschlüsselten Texts. Der erste Cipher-Block wird entschlüsselt und gegen die Bytes des IV XOR-t, was diesmal die korrekte Ausgabe bewirkt! Der IV wurde zwar nicht über den Konstruktor von RijndaelManagedTransform nach m_lastBlockBuffer kopiert, sondern durch direkte Voranstellung des IVs vor dem Ciphertext, dem Algorithmus ist das aber egal.
Hier eine schematische Übersicht:[IV1][CB1][CB2][CB3] (Ciphertextblock 1-3 mit vorangestelltem IV1)
[IV2] (über GenerateIV()geänderter IV)RijndaelManagedTransform-Konstruktor:
[IV2] nach [Buffer] kopierenRijndaelManagerTransform.DecryptData:
[IV1] entschlüsseln zu [IV1e] -> [IV1e] XOR [IV2] -> Garbage
----------------------------------------------------------------------
[CB1] entschlüsseln zu [CB1e] -> [CB1e] XOR [IV1] -> korrekte Ausgabe[CB2] entschlüsseln zu [CB2e] -> [CB2e] XOR [CB1] -> korrekte Ausgabe
[CB3] entschlüsseln zu [CB3e] -> [CB3e] XOR [CB2] -> korrekte Ausgabe
Fazit: Wenn man nun die ersten 16 Bytes wegläßt (Garbage), erhält man den korrekt entschlüsselten und xor-ten Klartext. Erklärung: Der IV wurde auf Umwegen übergeben.
Gruß
Marcel
- Als Antwort vorgeschlagen Marcel RomaModerator Sonntag, 15. Mai 2011 12:57
- Als Antwort markiert Robert BreitenhoferModerator Freitag, 3. Juni 2011 12:02
-
Hallo Marcel,
Erst mal vielen herzlichen Dank für deine ausführliche Antwort! (Grosses Dankeschön!)
Huj, da wird einem beim lesen ja ganz Schummerig ... :-)
Irgenwie glaube ich das Konzept von IV nun einigermassen gut verstanden zu haben.
Deshalb habe ich als Ergänzung, um das ganze noch etwas zu festigen, mir noch eine kleine Test-App geschrieben,
wo ich den IV und sein verhalten debuggen und evt. verständtlich nachvollziehen kann.
Und wie du oben beschrieben hast, ich bin zwar überrascht, aber es funktioniert tatsächlich so!Nochmals vielen Dank und Gruss:
Donaldusing System; using System.Collections.Generic; public class Test { public static void Main() { var lSecureText = "Geheim"; var lEncryptedText = new List<int>(); // Array mit verschlüsselten Bytes var lPlaintext = string.Empty; // Klartext (sollte nach der Entschlüsselung mit lSecureText übereinstimmen) // Verschlüsselungsfunktion ... (Sehr einfach :-) Func<int, int> lEncrypt = c => c ^ 128, lDecrypt = lEncrypt; // Grundsatz für CBC-Modus: (Wiki) // Vor dem Verschlüsseln eines Klartextblocks wird dieser zunächst mit dem im vorhergehenden Schritt erzeugten Geheimtextblock per XOR (exklusives Oder) verknüpft. // In diesem Beispiel stellt jeder Buchstabe einen einzelnen Block dar (Pseudo-Code) var lIV = (int)'X'; // Initialisierungvektor var lNext = lIV; // Geheimtextblock aus dem vorhergehenden Schritt noch nicht vorhanden, auf IV setzen. var lName = " IV"; // Hilfsvariable für ausgabe an der Konsole Console.WriteLine("Step 1: Verschlüsseln im CBC-Modus ...\n"); foreach (var c in lSecureText) { var lXor = c ^ lNext; var lChiffre = lEncrypt(lXor); WriteXor(c, lNext, lXor, c.ToString(), lName); // Debugausgabe an Konsole ... lName = c + "(e)"; lNext = lChiffre; lEncryptedText.Add(lChiffre); } lIV = (int)'X'; // Initialisierungvektor // Wenn hier der IV geändert wird, kann das Verhalten von IV recht gut nachvollzogen werden lNext = lIV; // Geheimtextblock aus dem vorhergehenden Schritt noch nicht vorhanden, auf IV setzen. lName = " IV"; // Hilfsvariable für ausgabe an der Konsole Console.WriteLine("\nStep 2: Entschlüsseln im CBC-Modus ...\n"); foreach (var c in lEncryptedText) { var lByte = lDecrypt(c); var lXor = lByte ^ lNext; WriteXor(lByte, lNext, lXor, lByte.ToString(), lName); lName = (char)lXor + "(d)"; lNext = c; lPlaintext += (char)lXor; } Console.WriteLine("\nPlaintext: {0}", lPlaintext); Console.ReadLine(); } private static void WriteXor(int a, int b, int xor, string nameA, string nameB) { Console.WriteLine("({0,3}) {1:00000000} ^ ({2}) {3:00000000} = {4:00000000} ({5})", nameA, int.Parse(Convert.ToString(a, 2)), nameB, int.Parse(Convert.ToString(b, 2)), int.Parse(Convert.ToString(xor, 2)), xor); } }
Ausgabe an der Konsole:
Step 1: Verschlüsseln im CBC-Modus ... ( G) 01000111 ^ ( IV) 01011000 = 00011111 (31) ( e) 01100101 ^ (G(e)) 10011111 = 11111010 (250) ( h) 01101000 ^ (e(e)) 01111010 = 00010010 (18) ( e) 01100101 ^ (h(e)) 10010010 = 11110111 (247) ( i) 01101001 ^ (e(e)) 01110111 = 00011110 (30) ( m) 01101101 ^ (i(e)) 10011110 = 11110011 (243) Step 2: Entschlüsseln im CBC-Modus ... ( 31) 00011111 ^ ( IV) 01011000 = 01000111 (71) (250) 11111010 ^ (G(d)) 10011111 = 01100101 (101) ( 18) 00010010 ^ (e(d)) 01111010 = 01101000 (104) (247) 11110111 ^ (h(d)) 10010010 = 01100101 (101) ( 30) 00011110 ^ (e(d)) 01110111 = 01101001 (105) (243) 11110011 ^ (i(d)) 10011110 = 01101101 (109) Plaintext: Geheim
- Als Antwort vorgeschlagen Marcel RomaModerator Sonntag, 15. Mai 2011 12:57
- Als Antwort markiert Robert BreitenhoferModerator Freitag, 3. Juni 2011 12:02
Alle Antworten
-
Hallo Donald,
> Ein String, der mittels einer Symetrischen Klasse verschlüsselt wird, sollte doch gemäss CBC-Mode-Definition jeden Block
mit dem vorangehenden verschlüsselten Block mittels XOR-Operation verketten. Wenn nun aber der IV verloren gehen würde, dann wären doch
auch alle nachfolgenden Blöcke, die mittels XOR verkettet wurden, verloren bzw nicht rekonstruierbar, Oder?Theoretisch JA.
> Aber wie erklärt sich dann, dass wenn ich mittels Symetrischen Verschlüsselung in .NET diese Informationen Trotzdem wiederherstellen kann?
Das ist ein Trugschluss. Der Initialisierungsvektor wird intern gecacht (s. Erklärungen in meinem folgenden Posting).
Gruß
Marcel- Bearbeitet Marcel RomaModerator Samstag, 14. Mai 2011 04:58 Revidiert
-
Vielen danke für deine Antwort
Irgendwie habe ich versucht dein Beispiel nachzuvollziehen, habe es aber nich so recht gerafft ...
Wenn ich dich irgendwie richtig verstanden habe, wird der IV durch die API selbst im chiffre gecacht?
Falls dann ein neuer IV generiert wird, und ich aber an den alten IV nicht mehr rankomme, macht das dann nichts?
Ich habe versucht deinen deinen Ratschlag zu interpretieren und irgendwie umzusetzten.
Es zeigt sich dann aber, dass die ersten 16 Byte (Länge des IV) leider nicht rekonstruiert werden können.
Ich gebe gerne zu, dass das Thema für mich sehr schierig zu verstehen ist, aber aus reiner Neugier interessiert es micht halt doch.Eine andere Umsetzung mit anschliessendem BEEP :-)
using System; using System.Text; using System.Security.Cryptography; public class Test { private static SymmetricAlgorithm mCryptoProvider; public static void Main() { Console.WindowHeight = 50; Console.WindowWidth = 120; mCryptoProvider = new RijndaelManaged() { Key = new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes("geheim")) }; var lTests = new string[] { "1234567890123456Administrator", "123456789012345612345", "the fox jumps over the lazy dog", "[" + new string('*', 50) + "]" }; foreach (var test in lTests) { WriteIV(); var lChiffre = Encrypt(test); Console.WriteLine("Test : " + test); // Generate a new Init-Vector mCryptoProvider.GenerateIV(); WriteIV(); // try decrypt chiffred text with unknow IV ... Console.WriteLine("Result V2: " + Decrypt(lChiffre)); WriteIV(); Console.WriteLine(); } Console.ReadLine(); } // Write IV to console private static void WriteIV() { Console.Write("IV : "); foreach (var b in mCryptoProvider.IV) Console.Write(b + ", "); Console.WriteLine(); } private static string Encrypt(string text) { var lBuffer = Encoding.UTF8.GetBytes(text); lBuffer = mCryptoProvider.CreateEncryptor().TransformFinalBlock(lBuffer, 0, lBuffer.Length); return Convert.ToBase64String(lBuffer); } private static string Decrypt(string base64) { var lBuffer = Convert.FromBase64String(base64); lBuffer = mCryptoProvider.CreateDecryptor().TransformFinalBlock(lBuffer, 0, lBuffer.Length); return Encoding.Default.GetString(lBuffer); } }
Ausgabe an der Konsole:
IV : 165, 75, 241, 10, 166, 252, 242, 166, 240, 45, 44, 253, 56, 86, 228, 129, Test : 1234567890123456Administrator IV : 217, 160, 43, 245, 181, 39, 194, 57, 98, 176, 245, 172, 151, 137, 67, 84, Result V2: MÙéË&í§«ècoë'ãAdministrator IV : 217, 160, 43, 245, 181, 39, 194, 57, 98, 176, 245, 172, 151, 137, 67, 84, IV : 217, 160, 43, 245, 181, 39, 194, 57, 98, 176, 245, 172, 151, 137, 67, 84, Test : 123456789012345612345 IV : 190, 219, 181, 238, 29, 164, 214, 244, 231, 94, 133, 131, 147, 233, 216, 104, Result V2: VI/?µ#õ¼ÞA↔7T®12345 IV : 190, 219, 181, 238, 29, 164, 214, 244, 231, 94, 133, 131, 147, 233, 216, 104, IV : 190, 219, 181, 238, 29, 164, 214, 244, 231, 94, 133, 131, 147, 233, 216, 104, Test : the fox jumps over the lazy dog IV : 248, 68, 202, 189, 27, 48, 66, 39, 215, 169, 139, 174, 132, 114, 74, 194, Result V2: 2÷→s`ûìóZ'c]d»ýÜer the lazy dog IV : 248, 68, 202, 189, 27, 48, 66, 39, 215, 169, 139, 174, 132, 114, 74, 194, IV : 248, 68, 202, 189, 27, 48, 66, 39, 215, 169, 139, 174, 132, 114, 74, 194, Test : [**************************************************] IV : 181, 192, 235, 29, 177, 95, 199, 202, 75, 89, 2, 85, 150, 89, 134, 182, Result V2: ▬®♂S?E¯Ç¶Ú£Ñ8☺æ^***********************************] IV : 181, 192, 235, 29, 177, 95, 199, 202, 75, 89, 2, 85, 150, 89, 134, 182,
-
Hallo Donald,
> Ich gebe gerne zu, dass das Thema für mich sehr schwierig zu verstehen ist, aber aus reiner Neugier interessiert es mich halt doch [...]
Entschuldige, wenn es gleich ein bischen trocken wird, aber es geht leider nicht anders.
Deine zwei Code-Auflistungen unterscheiden sich in einem wesentlichen Punkt: Während im ersten Code der Initialisierungsvektor in das MemoryStream kopiert wird und damit als Teil des base64-kodierten String an die Decrypt-Methoden mit übergeben wird, fehlt der IV aus dem base64 String der an die Decrypt-Methode im zweiten Code übergeben wird.
Die mathematische Formel für die CBC-Entschlüsselung ist:Wie man hier sehen kann, wird der (C)ipherblock erst (D)ekodiert [entschlüsselt] und das Resultat mit dem vorherigen (C)ipherblock über XOR verknüpft. Grundvoraussetzung dafür ist, dass der erste Cipherblock den Initialisierungsvektor enthält.
Lass uns nun sehen, wie dies in System.Security.Cryptography.RijndaelManaged (mscorlib.dll) umgesetzt wurde.
Es wird Dir vielleicht aufgefallen sein, dass es hier keine Decrypt-Methode gibt, an die man das IV direkt übergeben könnte. Wie kommt das IV also in die Algorithmus-Implementation rein? Ganz einfach: Über den Konstruktor von RijndaelManagedTransform. Eine Instanz dieser Klasse wird nämlich zurückgegeben, wenn man mCryptoProvider.CreateDecryptor() aufruft. Bei diesem Aufruf wird der mit dem CryptoProvider gecachte IV an den Konstruktor von RijndaelManagedTransform übergeben, wo er ein zweites Mal gecacht wird: Der Konstruktor von RijndaelManagedTransform kopiert den IV an den Anfang eines Arrays von 4 Integern das den Namen m_lastBlockBuffer hat.
Nun läuft die Entschlüsselung ab [in RijndaelManagedTransform.DecryptData()]. Dabei wird die Formel oben berücksichtig: Erst entschlüsseln, dann XOR-en. Die inputBytes werden in Blöcke aufgeteilt, und diese Blöcke sequentiell durch den Algorithmus gejagt. Der erste 16 Byte Block wird also entschlüsselt und das Resultat mit dem Inhalt von m_lastBlockBuffer XOR-t. Da der Buffer bei der Bearbeitung des ersten Blocks den IV enthält, wird also gegen den IV XOR-t. Dann werden die ersten 16 Bytes des verschlüsselten Textes in m_lastBlockBuffer gespeichert und der zweite Block von 16 Input-Bytes entschlüsselt und gegen den Buffer XOR-t. Usw. usf.
Wenn man nun - wie Du im ersten Posting - die IV-Bytes dem verschlüsselten Text voranstellt, passiert folgendes: Der Algorithmus interpretiert diese 16 Bytes IV als Ciphertext und entschlüsselt sie. Dann werden die entschlüsselten Bytes gegen den falschen IV XOR-t (diesen hast Du zwischenzeitlich über GenerateIV neu erstellt), was zur Garbage-Ausgabe in der Konsole führt. Nun macht der Algorithmus mit dem nächsten Block weiter, und das ist der erste Block des eigentlichen, verschlüsselten Texts. Der erste Cipher-Block wird entschlüsselt und gegen die Bytes des IV XOR-t, was diesmal die korrekte Ausgabe bewirkt! Der IV wurde zwar nicht über den Konstruktor von RijndaelManagedTransform nach m_lastBlockBuffer kopiert, sondern durch direkte Voranstellung des IVs vor dem Ciphertext, dem Algorithmus ist das aber egal.
Hier eine schematische Übersicht:[IV1][CB1][CB2][CB3] (Ciphertextblock 1-3 mit vorangestelltem IV1)
[IV2] (über GenerateIV()geänderter IV)RijndaelManagedTransform-Konstruktor:
[IV2] nach [Buffer] kopierenRijndaelManagerTransform.DecryptData:
[IV1] entschlüsseln zu [IV1e] -> [IV1e] XOR [IV2] -> Garbage
----------------------------------------------------------------------
[CB1] entschlüsseln zu [CB1e] -> [CB1e] XOR [IV1] -> korrekte Ausgabe[CB2] entschlüsseln zu [CB2e] -> [CB2e] XOR [CB1] -> korrekte Ausgabe
[CB3] entschlüsseln zu [CB3e] -> [CB3e] XOR [CB2] -> korrekte Ausgabe
Fazit: Wenn man nun die ersten 16 Bytes wegläßt (Garbage), erhält man den korrekt entschlüsselten und xor-ten Klartext. Erklärung: Der IV wurde auf Umwegen übergeben.
Gruß
Marcel
- Als Antwort vorgeschlagen Marcel RomaModerator Sonntag, 15. Mai 2011 12:57
- Als Antwort markiert Robert BreitenhoferModerator Freitag, 3. Juni 2011 12:02
-
Hallo Marcel,
Erst mal vielen herzlichen Dank für deine ausführliche Antwort! (Grosses Dankeschön!)
Huj, da wird einem beim lesen ja ganz Schummerig ... :-)
Irgenwie glaube ich das Konzept von IV nun einigermassen gut verstanden zu haben.
Deshalb habe ich als Ergänzung, um das ganze noch etwas zu festigen, mir noch eine kleine Test-App geschrieben,
wo ich den IV und sein verhalten debuggen und evt. verständtlich nachvollziehen kann.
Und wie du oben beschrieben hast, ich bin zwar überrascht, aber es funktioniert tatsächlich so!Nochmals vielen Dank und Gruss:
Donaldusing System; using System.Collections.Generic; public class Test { public static void Main() { var lSecureText = "Geheim"; var lEncryptedText = new List<int>(); // Array mit verschlüsselten Bytes var lPlaintext = string.Empty; // Klartext (sollte nach der Entschlüsselung mit lSecureText übereinstimmen) // Verschlüsselungsfunktion ... (Sehr einfach :-) Func<int, int> lEncrypt = c => c ^ 128, lDecrypt = lEncrypt; // Grundsatz für CBC-Modus: (Wiki) // Vor dem Verschlüsseln eines Klartextblocks wird dieser zunächst mit dem im vorhergehenden Schritt erzeugten Geheimtextblock per XOR (exklusives Oder) verknüpft. // In diesem Beispiel stellt jeder Buchstabe einen einzelnen Block dar (Pseudo-Code) var lIV = (int)'X'; // Initialisierungvektor var lNext = lIV; // Geheimtextblock aus dem vorhergehenden Schritt noch nicht vorhanden, auf IV setzen. var lName = " IV"; // Hilfsvariable für ausgabe an der Konsole Console.WriteLine("Step 1: Verschlüsseln im CBC-Modus ...\n"); foreach (var c in lSecureText) { var lXor = c ^ lNext; var lChiffre = lEncrypt(lXor); WriteXor(c, lNext, lXor, c.ToString(), lName); // Debugausgabe an Konsole ... lName = c + "(e)"; lNext = lChiffre; lEncryptedText.Add(lChiffre); } lIV = (int)'X'; // Initialisierungvektor // Wenn hier der IV geändert wird, kann das Verhalten von IV recht gut nachvollzogen werden lNext = lIV; // Geheimtextblock aus dem vorhergehenden Schritt noch nicht vorhanden, auf IV setzen. lName = " IV"; // Hilfsvariable für ausgabe an der Konsole Console.WriteLine("\nStep 2: Entschlüsseln im CBC-Modus ...\n"); foreach (var c in lEncryptedText) { var lByte = lDecrypt(c); var lXor = lByte ^ lNext; WriteXor(lByte, lNext, lXor, lByte.ToString(), lName); lName = (char)lXor + "(d)"; lNext = c; lPlaintext += (char)lXor; } Console.WriteLine("\nPlaintext: {0}", lPlaintext); Console.ReadLine(); } private static void WriteXor(int a, int b, int xor, string nameA, string nameB) { Console.WriteLine("({0,3}) {1:00000000} ^ ({2}) {3:00000000} = {4:00000000} ({5})", nameA, int.Parse(Convert.ToString(a, 2)), nameB, int.Parse(Convert.ToString(b, 2)), int.Parse(Convert.ToString(xor, 2)), xor); } }
Ausgabe an der Konsole:
Step 1: Verschlüsseln im CBC-Modus ... ( G) 01000111 ^ ( IV) 01011000 = 00011111 (31) ( e) 01100101 ^ (G(e)) 10011111 = 11111010 (250) ( h) 01101000 ^ (e(e)) 01111010 = 00010010 (18) ( e) 01100101 ^ (h(e)) 10010010 = 11110111 (247) ( i) 01101001 ^ (e(e)) 01110111 = 00011110 (30) ( m) 01101101 ^ (i(e)) 10011110 = 11110011 (243) Step 2: Entschlüsseln im CBC-Modus ... ( 31) 00011111 ^ ( IV) 01011000 = 01000111 (71) (250) 11111010 ^ (G(d)) 10011111 = 01100101 (101) ( 18) 00010010 ^ (e(d)) 01111010 = 01101000 (104) (247) 11110111 ^ (h(d)) 10010010 = 01100101 (101) ( 30) 00011110 ^ (e(d)) 01110111 = 01101001 (105) (243) 11110011 ^ (i(d)) 10011110 = 01101101 (109) Plaintext: Geheim
- Als Antwort vorgeschlagen Marcel RomaModerator Sonntag, 15. Mai 2011 12:57
- Als Antwort markiert Robert BreitenhoferModerator Freitag, 3. Juni 2011 12:02
-
Hallo Donald,
Erst mal vielen herzlichen Dank für deine ausführliche Antwort! (Grosses Dankeschön!)
Gern. Sich mit Gleichgesinnten zu interessanten Themen auszutauschen, ist doch der Sinn eines Forums. Schön zu sehen, dass es - trotz Alltagsstress - noch Leute wie Dich gibt, die den Problemen auf den Grund gehen.Gruß
Marcel -
Hallo Peter,
ich möchte mich gar nicht hier einmischen, aber sehr interessant bezüglich der Hintergründe bei IV's ist auch der Webcast:
[Kryptografie - Teil 1 | Lori | Channel 9]
http://channel9.msdn.com/Blogs/Lori/Kryptografie-Teil-1
(etwa bei 25ter Minute, aber im Prinzip von vorne sehen)
ciao Frank -
Hallo Donald,
> Aber wie erklärt sich dann, dass wenn ich mittels Symetrischen Verschlüsselung in .NET diese Informationen Trotzdem wiederherstellen kann?
Die Frage ist bereits beantwortet. Vielleicht noch ein kleiner Hinweis für den Leser, dem vielleicht die Zeit fehlt, das hier beschriebene in allen Details nachzuvollziehen: Die Übergabe des IVs als Ciphertext kompromitiert nicht das verwendete kryptograpische Verfahren. Dass die Entschlüsselung im CBC-Modus auch mit einem falschen IV funktioniert, stellt kein vermehrtes Risiko dar. Denn: Der Angreifer muss nach wie vor sowohl den richtigen IV als auch den kryptographischen Schlüssel für eine erfolgreiche Verschlüsselung bereitstellen. Nur die Art und Weise wie Donald den (richtigen) IV in seinem Code intelligenterweise übergibt, ist anders. Das dies überhaupt möglich ist, scheint mir ein Implementierungsdetail zu sein (sapere aude).Gruß
Marcel