C# Performance von Entschlüsselungsalgorithmen auf Android/iOS

Physikbuddha

Lt. Commander
Registriert
Aug. 2014
Beiträge
1.078
Mahlzeit miteinander,

vielleicht könnt ihr mir ein bisschen Input geben.
Ich habe eine App im App- und Play Store, bei der es zu Assetklau gekommen ist.
Daher möchte ich besagte Dateien in Zukunft verschlüsselt mitliefern und zur Laufzeit entschlüsseln.
Bei den Daten handelt es sich um etwa 150 XML-Dateien mit einer Gesamtgröße von 2,5 MB.

Da diese Daten nicht mission-critical sind, reicht ein einfacher, unsicherer Algorithmus aus. Von mir aus auch XOR, auch wenn das offen wie ein Scheunentor ist. Ich möchte damit lediglich die meisten Leute davon abhalten, die Daten einfach zu kopieren.

Wichtig ist mir, dass der Entschlüsselungsalgorithmus möglichst schnell arbeitet, da die Daten relativ zeitkritisch, und dann auch noch in großer Stückzahl auf einmal in der App benötigt werden.

iOS flutscht da eigentlich ganz gut, nur auf Android hakelt es ein wenig.
Welche Entschlüsselungsalgorithmen würdet ihr für diesen Einsatzzweck empfehlen?
So gut wie alle x86-CPUs haben heute AES hardwarebeschleunigt on-board. ARM in Android auch?
Vielleicht habt ihr da ein paar Tipps für mich.

Danke und mit Gruß
Physikbuddha
 
Die meisten guten Arm Kerne haben sowas wie AES auch. Aber definitiv nicht alle, Google hat ja extra einen Algorithmus für solche billig-Geräte entwickelt.
 
Vielleicht die XML-Dateien einfach in eine einzelne Binärdatei umwandeln oder einfach als Embedded Resource in die Assembly mit rein kompilieren?
Was steht denn drin und was genau ist passiert?
Ich habe noch nicht genau verstanden, gegen welches Szenario Du Schutz brauchst.
 
  • Gefällt mir
Reaktionen: new Account()
@new Account() Wenn du mit prefetchen vom Server laden meinst: könnte man machen, will ich aber nicht wirklich. Die App soll offline funktionieren.

@Ruheliebhaber Wie gesagt, die Assets wurden entwendet und in einer Konkurrenzapp verwendet. Um das Thema kümmert sich der Anwalt, aber ich möchte das in Zukunft einschränken bis vermeiden. Da die App offline funktionieren soll, kann ich natürlich nie zu 100 % verhindern, dass jemand doch ne Menge Zeit in Reverse Engineering investiert.
Die Daten werden bei iOS als BundleResource, bei Android als AndroidAsset mitgeliefert.
Reine Binärdatei halte ich mal im Hinterkopf, falls die Entschlüsselung nicht so performt, wie ich möchte.

@dbeuebeb Gefällt mir schonmal, danke.
 
@new Account() Android und alles in den RAM laden. :lol:
Ja gut, der reine String dürfte noch gehen. Wenn ich alle Daten als Objekte parse, krieg ich ne OutOfMemoryException geknallt.

Müsste ich den Splashscreen mal etwas aufbohren. Aber der Ansatz gefällt mir eigentlich ganz gut. Gehst anders heran. Danke.

@Toms Ja, hab ich auch gefunden. Hat leider einen blöden Nachteil: Ich müsste alle Assets doppelt mitliefern: Einmal mit Adiantum und einmal whatever-verschlüsselt. Werde das mal näher anschauen, wenn es auf den Billigkrücken wirklich zu sehr lahmt. :)
 
Physikbuddha schrieb:
Ja gut, der reine String dürfte noch gehen. Wenn ich alle Daten als Objekte parse, krieg ich ne OutOfMemoryException geknallt.
Lol?
Es sind doch nur 2.5MB? Da dürften die Objekte doch nicht extrem viel mehr sein?

Naja, string reicht auf alle Fälle ja auch.
 
Ich glaube, am Ende kommst Du nicht um Testen und Messen herum.

Ich habe einmal einen GML-Parser im .NET Compact-Framework für Windows CE geschrieben.
Im Vergleich zu heute waren die Geräte früher viel schwächer.
Aber nur einmal als Anekdote.
Wenn ich die ganze Datei als String eingelesen und geparst habe, war es elend langsam.
Habe ich aber jede Zeile einzeln eingelesen und geparst, war es gefühlt 5 mal so schnell.

Ich will damit sagen, nicht immer ist es so, wie man sich das denkt.
Man muss selber einen kleinen Benchmark schreiben.

Auf der anderen Seite darf man auch nicht um Probleme herum entwickeln, die man noch gar nicht hat.
Soll heißen, entwickle doch erst einmal und schaue Dir hinterher die bottlenecks an, wenn es zu langsam ist.

Vorschlag:
Probiere erst einmal AES 128, 192 und 256 aus.
Verschlüssele jede Datei einzeln und entschlüssele sie wieder.
Und dann misst Du die Zeit über alle Dateien.

Also ungefähr so:

C#:
var start = DateTime.Now;
foreach (file in dir)
{
    var data = Encrypt(file, Aes.256);
    Decrypt(data);
}
var end = DateTime.Now;
Console.Writeline("Dauer: {0}", end - start);


Teste das einmal über 2 oder 3 Geräte und eventuell noch mit anderen Algorithmen.
Dann hast Du eine Ministatistik, wie sich das verhält.
Du musst halt sehen, dass Du den symmetrischen Schlüssel gut versteckst.

Außerdem könntest Du schauen, ob bestimmte XML-Dateien mehr als einmal gelesen werden (eventuell eine kleine Statistik anlegen) und dann die häufigsten 10 genutzten Dateien gleich beim Bootstrapping der Anwendung laden und als Objekte im Speicher behalten.
Also so eine Art Mini-Cache bauen.
 
Zuletzt bearbeitet:
Bin auch schon am Testen und nur so mittelglücklich.
Entschlüsseln einer 80 KB großen Datei, 100 Durchläufe:
  • iPad 2017: 153 ms
  • iPhone SE: 252 ms
  • Galaxy A5 (2017): 5081 ms
  • Galaxy S5 mini: 8266 ms
Jeweils mit AES-256. Mit AES-128 reduziert sich die Zeit des letzten Geräts auf etwa 7000 ms.
 
Zuletzt bearbeitet:
Bei 150 Dateien könnte auch noch die I/O-Leistung der Geräte eine Rolle spielen.
Es geht ja nur um Verschlüsseln von Text.

Vorschlag:
Schreibe mal ein kurzes Programm, dass den Inhalt aller XML-Dateien in eine einzige Text-Datei merged.
Z.B. als JSON-Format.
Ein einfaches Feld reicht.
Und dann wiederhole Deinen Test einmal auf dem langsamsten Gerät.
Einmal mit und einmal ohne Parallele Ausführung.

Ich finde übrigens 80 ms für einen Durchlauf auf dem langsamsten Gerät nicht so sehr langsam.
Vor allem, wenn ich mir denke, dass Du zur Laufzeit Deiner App höchst wahrscheinlich nicht alle Dateien auf einmal entschlüsseln musst.

Und denke daran, dass Du die am häufigsten verwendeten Dateien vorladen und im RAM cachen kannst.

PS:
Hast Du eigentlich vor Deinem Test alle anderen Apps beendet?
Das kann ja auch noch einen Einfluss haben, wenn z.B. Android erst noch den Garbage Collector anwerfen und Apps beenden muss.
 
Ruheliebhaber schrieb:
Vor allem, wenn ich mir denke, dass Du zur Laufzeit Deiner App höchst wahrscheinlich nicht alle Dateien auf einmal entschlüsseln musst.
Physikbuddha schrieb:
Wichtig ist mir, dass der Entschlüsselungsalgorithmus möglichst schnell arbeitet, da die Daten relativ zeitkritisch, und dann auch noch in großer Stückzahl auf einmal in der App benötigt werden.
 
Verstehe.
Aber ist das ein hehrer Wunsch oder eine wirkliche Anforderung?
Was ist mit einem intelligenten Prefetch, bevor der User überhaupt erst die erste Eingabe machen kann?
Wie werden die Dateien verarbeitet?
Sequentiell oder parallel?
Wenn sie sequentiell verarbeitet werden, könnte es sich lohnen, parallel die Dateien vorzuladen und dabei zu entschlüsseln.
Also frei nach dem Producer Consumer Pattern.

Wir wissen halt zu wenig über die konkrete Problemstellung und Arbeitsweise der App.
Deshalb muss ich ein bisschen raten.
 
Also ich wollte zwar gar nicht so sehr ins Detail gehen, aber was solls.

Genau gesprochen handelt es sich um KML-Dateien, also Geodaten von Polygonen.
Diese Polygone stelle ich auf eine Karte dar.

Da selbst auf iOS 150 Polygone dieser Größe stark ruckeln, zeige ich die Polygone zunächst als Punkt, und stelle sie erst ab einer gewissen Zoomstufe als Polygon dar.
Dabei bestimme ich den aktuell sichtbaren Kartenausschnitt, sodass nur die nötigen Polygone geladen werden.

Jetzt ist Android in diesem Punkt unperformant af. Wenn ich alle 150 Polygone auf einmal darstellen möchte, läuft mir der Arbeitsspeicher über. Genauer gesagt, kriege ich gar nicht mal OutOfMemoryError, sondern die App friert ein und im Debugger sehe ich, dass der Garbage Collector durchdreht. Versucht Speicher freizuräumen, schafft es nicht, startet den nächsten Versuch usw.

Daher bin ich auf Android gezwungen die nativen Polygon-Objekte (List<LatLng>) zu löschen, sobald sie nicht mehr auf der Karte angezeigt werden. Später muss ich sie dann wieder frisch aus der KML-Datei laden.

Das Laden aller KMLs erfolgt weitestgehend parallel über den Threadpool.

Da ich nicht weiß, welche Polygone zuerst vom User angezeigt werden, kann ich auch nicht wirklich prefetchen.
 
Da muss es nen anderen Fehler geben.
2 MB Daten sind nicht wirklich viel, davon läuft der Speicher nicht über...
Hört sich für mich nach nem memory leak an...
Rufst du beim Laden der Dateien auch die Dispose Methode auf oder verwendest du nen using Block?
 
An der Stelle musst du mir mal "ausnahmsweise" glauben.

Die Dateien sind ja nicht das Problem, sondern die daraus resultierenden Koordinatenobjekte LatLng. Die kommen mit nem Rattenschwanz an Zusatzinfos.

Ich hab da schon ewig Zeit und Ressourcen reingesteckt und mich mit dem Xamarin Profiler durch die Ebenen durchgebohrt. Jede Instanz eines LatLng frisst ne Menge Speicher. Das sind native Objekte der Android-API, also kann ich da nichts optimieren. Die GoogleMap frisst nur diese Teile.

Mit ein paar Polygonen habe ich dann mal schnell 200.000 Byte-Arrays, und dann kriegt der GC seinen Rappel.

Meine Lösch- und Aufräumfunktionen laufen auch vernünftig. Wenn ich komplett aus der Karte rauszoome werden auch alle Byte-Arrays brav gelöscht.

Ich hab dann selbst mal ne ganz nackige App gemacht, mit dem Allernotwendigsten. Bringt nix. Und ich bin da nicht der einzige mit dem Problem. Google hat bei den Polygonen einfach ein paar kleine Bereiche wie Häuser oder Plätze im Kopf gehabt, keine ganzen Bundesländergrenzen. Das muss ich halt irgendwie umschiffen.

Jedenfalls ist mir das ja auch recht gut gelungen. Kleine Ruckler und Ladezeiten bleiben halt.
Und die möchte ich halt ungern durch die Entschlüsselung unnötig weiter aufpusten.
 
Zuletzt bearbeitet:
Also so als Rückmeldung für euch; nach umfangreichen Tests bin ich jetzt schlauer.

Ich habe zunächst den ChaCha20-Algorithmus implementiert.
Mit diesem stiegen die Werte beim iPhone von 250 ms auf etwa 2500 ms, beim Gammel-Androidtelefon von 8000 ms auf etwa 8800 ms.

Mit AES-128 statt AES-256 braucht das Android 7000 ms statt 8000 ms.

Dann habe ich alle Dateien in eine einzelne Datei gemergt, was erwartungsgemäß nichts gebracht hat (da in meinen 100 Iterationen aus dem RAM gelesen wird, und nicht 100 mal die Datei von der Platte geladen wird, also nichts I/O-Bound).
Die Datei ist 2600 KB groß und braucht mit ChaCha20 2500 ms, mit AES ein bisschen weniger, ca 2200-2300 ms.

Jetzt schlussendlich habe ich den ChaCha-Algorithmus für parallele Ausführung umgeschrieben.
Mit Overhead braucht der mit einem Thread 3000 ms, mit zwei Threads 1500 ms, und mit vier Threads 850 ms.
Mehr als vier Threads bringens nicht, da das Gerät nur vier Kerne hat.

Außerdem braucht das Umwandeln der 2,6 MB großen Datei von Base64 zu byte[] um die 770 ms. Das ist fast so lange wie das Entschlüsseln selber. Daher habe ich mich entschlossen die verschlüsselten Daten direkt als Binärdatei zu speichern.

Ich werde also die Daten bei iOS mit AES mitliefern, bei Android mit ChaCha20.
Ob ich es bei einer großen Datei lasse, bin ich mir noch nicht sicher. Ich glaube, ich zahle die Kosten lieber in Raten, wenn die Dateien bei Bedarf gelesen werden. Die entschlüsselten Daten cache ich dann zwischen.
 
Zurück
Oben