Laden...

Forenbeiträge von Weressated Ingesamt 28 Beiträge

23.03.2023 - 12:30 Uhr

en werden und dann ist das Kennwort wieder abgreifbar (je nach Umsetzung für eine kurze oder sehr lange Zeitspanne)[/list]
Wenn man sich also auf den SecureString verlässt, dann hat man lediglich die Script-Kiddies ausgesperrt, die mal eben so im Speicher nach Passwörtern Ausschau halten und dann aufgeben, wenn sie da nichts finden. Für alle anderen - die auf wirklichen Schaden aus sind - stellt das keine ernst zu nehmende Hürde dar und ist damit gleichbedeutend wie gar kein Schutz.

Du argumentierst hier schon wieder gegen eine vermeintliche "perfekte" Sicherheit, die hier niemals auch nur ansatzweise behauptet wurde. Das nennt man Strohmann-Argument 😉

Dass ein SecureString keine "perfekte" Sicherheit gegen alle denkbaren Angriffe bietet, wurde nie bestritten. Dass ein SecureString niemals weniger sicher als ein "normaler" String ist, sondern im Gegenteil gegen bestimmte Angriffe schützt (bzw. diese deutlich erschwert) ist aber ebenfalls unstrittig. Dabei geht es übrigens keines Wegs nur (aber auch) darum, dass ein lokaler Angreifer den Speicher Deines Prozesses auslesen könnte. Sensible Daten, die länger als unbedingt nötig als Klartext im Speicher liegen, können auch schnell mal in Crash-Dumps, in der Auslagerungsdatei, o.ä. landen. Oder denke mal an prominente Sicherheitslücken wie "Heartbleed", wo ein Remote-Angreifer einfach mal aufgrund eines Bugs ganze Teile des Arbeitsspeichers aus der Ferne auslesen konnte. Da willst Du dann lieber nichts sensibles als Klartext drin rumliegen haben. Da der Angreifer pro Request nur wenige Daten auf einmal ausspähen konnte, das aber beliebig oft immer wieder tun konnte, kam es also ganz entscheidend darauf an, wie lange die "kritischen" Daten im Arbeitsspeicher lagen. Die Erfahrung lehrt: Solche oder ähnliche Sicherheitslücken können und werden immer wieder auftreten, egal wie viel man Testet. Um so wichtiger ist es, proaktiv entsprechende Maßnahme vorzusehen, die den Schaden im Fall der Fälle eindämmen.

Dass ein "nicht perfekter" Schutz gleichbedeutend mit "gar keine" Schutz sein soll, ist also nach wie vor Schwarz-Weiß-Denken, das hier überhaupt nicht weiter hilft. Du wirst in der Realität niemals eine "perfekten" Schutz gegen alle denkbaren Angriffe haben können. Schon gar nicht durch eine einzelne Maßnahme. Das ist doch aber keine Ausrede dafür, bereits vorhandene Sicherheitsmechanismen nicht zu nutzen! Sicherheit ist immer eine Kombination aus vielen Einzelmaßnahmen. Es geht immer darum bekannte Angriffe entweder unmöglich oder zumindest deutlich schwieriger zu machen. Nach Deiner Argumentation wären auch Sicherheitsmaßnahmen wie DEP oder ASLR komplett sinnlos, weil diese ebenfalls mit bekannten Angriffs-Techniken (Spraying, Return-to-libc-Attack, etc.) doch wieder umgangen werden können. Trotzdem sind Techniken wie DEP und ASLR sehr wohl nützlich, eben weil sich bestimmte Sicherheitslücken, die in der Praxis des öfteren vorkommen, damit deutlich schwerer ausnutzen lassen.

Konkret: Eine PasswordBox verwendet intern sowieso einen SecureString, um die Benutzer-Eingabe zu halten – wie bereits zuvor erwähnt. Das Passwort kannst Du über die entsprechenden Properties wahlweise als SecureString oder als String von der PasswordBox bekommen – wobei in letzterem Fall einfach die get-Methode den intern verwendeten SecureString in einen "nicht-secure" String raus kopiert, womit das Passwort dann unmittelbar und auf unbestimmte Zeit als Klartext im Speicher landet. Was ist jetzt bitte der konkrete Grund bzw. Vorteil, wieso man hier das Passwort besser als String anstatt naheliegenderweise als SecureString entgegen nehmen sollte 🤔

Die Lösung dafür liegt eher im Anmelde-Konzept, wie z.B.
Anmeldung mit 2FA

Passwortlose Anmeldung (wie z.B. die Anmeldung bei Office 365 mit der MS Authenticator APP)

Dort spielt es keine Rolle, ob das Kennwort erbeutet wird, bzw. es gibt einfach keins zu erbeuten.

Letzten Endes verlagerst Du damit aber auch nur das "Problem". Denn auch die Authenticator App muss zwangsläufig das "Shared Secret", das in die HMAC-Berechnung zur Erzeugung des Anmelde-Codes einfließt, irgendwo abspeichern - und es für die Dauer der Berechnung des Anmelde-Codes zumindest temporär in den Arbeitsspeicher laden. Der Vorteil von 2FA mit Authenticator App ist eher darin zu sehen, dass das "Shared Secret" selbst - im Unterschied zu einem Passwort - niemals irgendwo eingegeben oder übermittelt werden muss (nachdem man es initial einmalig in die Authenticator App geladen hat). Stattdessen werden immer nur Anmelde-Codes mit kurzer Gültigkeitsdauer eingegeben bzw. übermittelt, was "Phishing" Angriffe quasi unmöglich bzw. sinnfrei macht. Ein weiterer Punkt ist natürlich auch, dass die Authenticator App typischerweise auf einem separaten Gerät läuft und der Angreifer somit zwei Geräte kompromittieren müsste.

Aber: So interessant das Thema "Anmeldung mit 2FA" sein mag, es hat nichts mit dem hier diskutieren Anwendungsfall zu tun 😁

Es ist ein String, der als Value der Property übergeben wird, und dann in einer PasswortBox landet. Das Backing Field ist SecureString in dem Zusammenhang irrelevant ist, weil der String selbst bleibt. Es ist nicht möglich einen Wert hier ohne ein String als Übertragungsmedium zu setzen. PasswordBox hat zwar tatsächlich ein Property wo man das Passwort als "normlen" String abfragen oder sogar auch aus einem solchen setzen könnte (wie ich ja auch bereits mehrfach erwähnt habe), aber das heißt ja noch lange nicht, dass man diese Methode benutzen muss oder sollte! Der Standard-Anwendungsfall ist ja der, dass der Benutzer sein Passwort Zeichen für Zeichen in die PasswordBox eintippt - wobei das Passwort Zeichen für Zeichen (bei jedem KeyEvent) in den internen SecureString einfügt wird, ohne das jemals das komplette Passwort in einem einzigen zusammenhängen String vorliegt - und am Ende ruft man dann das vollständige Passwort über das Property "SecurePasswort" als SecureString ab.

(Und nein: Die haben es nicht maximal doof so programmiert, dass bei jedem Zeichen, das man eintippt, das Passwort als String ausgelesen, dann das Zeichen an den String angehängt und schließlich das Passwort aus dem String neu gesetzt wird 😘 )

15.03.2023 - 17:38 Uhr

Dein Problem basiert weiterhin auf reiner theoretischen Annahme, dass das Passwort kürzer im Speicher wäre, wenn Du ein SecureString verwendest. Dem ist weiterhin nicht so. Und ändert sich auch durch Deine Beiträge nicht.
Du betrachtest hier nämlich nur einen minimalen Zeitraum in einem System - weswegen das ganze hier ohne nennenswerten Effekt, schon gar nicht durch erhöhte Sicherheit, enden wird.
Dein Passwort wird in einer Textbox eingegeben, was ein String ist. Dieser String wird im Betriebssystem im Speicher hinterlegt und bleibt da erst mal.

Nö. Ich habe mir die Implementierung von PasswordBox angesehen, mit dem ILSpy. Das Passwort wird intern so oder so in einem SecureString gehalten.

Die Getter-Methode für das Property "Passwort" wandelt den intern verwendeten SecureString in einen "normalen" String um.

Die Getter-Methode für das Property "SecurePasswort" hingegen liefert direkt einen Clone des intern verwendeten SecureString zurück, ohne Zwischenschritt.

Das Betriebssystem hat damit nur in sofern etwas damit zu tun, dass es natürlich die einzelnen KeyPress-Events an die Anwendung schickt.

Die Implementierung von PasswordBox benutzt soweit ich sehe SecureString.InsertAt() um jedes eingegebene Zeichen direkt in den internen SecureString einzufügen.

Man kann das auch leicht nachprüfen:
*Passwort in die PasswordBox eingeben und anschließen ein Memory-Dump der Anwendung erzeugen ⇒ Volltextsuche findet nichts verdächtiges *Nun das Passwort aus der PasswordBox einmal per Property "Passwort" als String abrufen, dann neues Memory-Dump erzeugen ⇒ Volltextsuche beweist, dass das Passwort jetzt als Klartext im Speicher liegt

Hinweis: Man muss mit einem Tool den Dump durchsuchen, das auch UTF-16 Strings findet. Die meisten Tools finden nur ASCII, Latin-1 oder evtl. noch UTF-8 kodierte Strings.

15.03.2023 - 16:20 Uhr

Die Obscurity ist in dem Fall, dass das Passwort nur kurz im Speicher gehalten wird. Wenn man das weiss, dann kann man seinen Angriff entsprechende danach ausrichten.

"Security through obscurity" bedeutet ja, dass sich die Sicherheit in quasi Luft auflöst, wenn man weiß, wie das Verfahren funktioniert. Die Sicherheit basiert auf Geheimhaltung des Verfahrens selbst.

Sensible Daten nur so kurz wie möglich im Speicher zu halten (anstatt längerfristig) ist aber generell sinnvoll und macht es einem potentiellen Angreifer viel schwerer diese Daten abzugreifen, egal ob dieser davon weiß, dass die Daten nur kurzfristig da sein werden oder ob er das nicht weiß. Von daher hinkt der Vergleich meiner Meinung 😉

Mein Kritikpunkt geht eher dahin, den Passwort-Dialog selber zu erstellen, ergo Security selber zu machen. Das mag für deinen Anwendungsfall okay sein. In der Praxis sehe ich aber immer wieder, dass Firmen versuchen auf diese Weise Kundendaten zu schützen. Adressdaten, Bank-Verbindungen, Patientenakten. So ein Security-Vorfall, kann auch locker einer grösseren Firma den Kopf kosten, von den geschädigten Personen ganz zu schweigen.

Auch hier wieder die Frage, was ist konkret die bessere Alternative?

Wie schon mehrmals betont, geht es hier von Anfang an um den Anwendungsfall, dass ein "Passwort" an eine Bibliotheks-Funktion (konkret BouncyCastle) als char[] Array übergeben werdenmuss, um damit eine notwendige Berechnung auszuführen. Das Passwort muss irgendwie vom Benutzer entgegen genommen werden, um es an die Bibliothek übergeben zu können. Die Bibliothek kann bzw. soll nicht verändert werden. Die Frage war lediglich, wie man das Passwort, das von der PasswortBox als SecureString zurück geliefert wird, am besten in ein char[] bekommt, möglichst ohne String als Zwischenschritt. Es gibt sicher viele interessante Lösungen für andere Anwendungsfälle, aber das ist ein anderes Thema 🙂

15.03.2023 - 12:27 Uhr

Im Endeffekt ist das eine "security through obscurity"-Diskussion. Ich denke die Argumente für und wieder sind im Netzt zur genüge ausgetauscht.

Nö. Security through obscurity heißt, dass ein Sicherheitsmechanismus nur solange "sicher" ist, wie die genaue Funktionsweise geheim gehalten wird. Das Gegenteil von Kerckhoffs’ Prinzip

Dass man sensible Daten nur wenn unbedingt nötig und – wenn nicht anders möglich – immer nur so kurz wie unbedingt erforderlich (als Klartext) im Speicher hält, ist hingegen eine völlig "transparente" und allgemein bekannte Vorgehensweise; es ist ganz einfach "Best Practice" in der Softwareentwicklung und wird, wie schon erwähnt, in gängigen Security-Bibliotheken genau so umgesetzt.

The Secure­Zero­Memory() function doesn’t make things secure; it just makes them more secure. The issue is a matter of degree, not absolutes.
https://devblogs.microsoft.com/oldnewthing/20130529-00/?p=4223

...an der Stelle könntest Du anstatt Secure­Zero­Memory() auch SecureString oder andere einsetzen 😉

Die ganze Diskussion gegen die Verwendung von SecureString, die hier geführt wurde, basiert hauptsächlich auf Strohmann-Argumenten, d.h. es wird die ganze Zeit gegen eine vermeintliche "absolute Sicherheit" argumentiert, die so aber ja niemals behauptet wurde (und die es natürlich i.d.R. auch nicht geben kann). Wie Raymond Chen schon richtig gesagt hat: "the issue is a matter of degree, not absolutes".

Ich für meinen Teil kann nur empfehlen bei Security-Themen immer auf etablierte Systeme zu gehen und sowas nicht selber zu machen. 😉

Da stimme ich Dir vollkommen zu. "Don't roll your own crypto" 🙂

Deshalb vorhandene und etablierte Sicherheitsmechanismen wie, z.B. (aber nicht beschränkt auf) SecureString nutzen, wo verfügbar und zum Anwendungsfall passend, anstatt zu versuchen das Rad neu zu erfinden.

Wenn es keine perfekte Lösung gibt – und die wird es in der Realität selten geben – nimm halt wenigstens die beste verfügbare/praktikable Lösung 👍

Siehe auch:
https://stackoverflow.com/a/67048259

14.03.2023 - 22:14 Uhr

Wenn ihr gegen eine Lib arbeiten müsst, die schon die Klartextdaten verlangt, was genau bringt dann die Kapselung in SecureString?

Wenn Du sensible Informationen wie Passwörter in einen "normalen" String packst, dann liegen die Daten dauerhaft im Klartext im Speicher. Weil String immutable ist, können die Daten nicht explizit überschrieben werden. Wann der GC mal irgendwann den betreffenden String abräumt, und wann dann evtl. mal irgendwann dieser Speicherbereich wiederverwendet und mit neuen Daten überschrieben wird, das ist völlig undefiniert und wird i.d.R. nicht zeitnah passieren.

Mit einem SecureString liegen die sensible Informationen nahezu die gesamte Zeit über nur verschlüsselt im Speicher und tauchen dann z.B. in einem Memory-Dump nicht auf. Ja, richtig, um mit den Daten rechnen zu können, müssen sie notwendigerweise temporär entschlüsselt werden. Aber, mit der oben beschrieben Methode werden die Daten aus dem SecureString direkt in ein char[] Array entschlüsselt und nach der Berechnung auch sofort wieder überschrieben. Die Bibliotehk kopiert diese Daten auch nicht. Damit liegen die sensiblen Daten also nur noch für den Bruchteil einer Sekunde als Klartext im Speicher – anstatt dauerhaft. Das ist eindeutig erheblich sicherer als die Alternative, und definitiv nicht von Nachteil.

Wenn jemand in deinem Prozess die Daten abgrasen kann, dann spielt es auch keine Rolle wie lange die Daten im Speicher vorliegen.

Ein Zeitfenster spielt dabei dann auch keine Rolle, dass ist es auch was Abt damit ausdrücken will.

Doch, das tut es sehr wohl. Wenn die Daten nur für den Bruchteil einer Sekunde im Speicher liegen, ist es sehr viel unwahrscheinlicher, dass jemand sie genau in dem Moment erfolgreich abgreifen kann, als wenn sie dauerhaft dort rumliegen würde. Aber es geht auch darum, dass sensible Daten in Crash-Dumps oder dem Hibernation-File, etc. pp. auftauchen können. Ja, sogar nach einem Reboot können noch "alte" Daten aus dem RAM auslesbar sein (siehe Kaltstartattacke).

Praktisch jede Crypto-Bibliothek wie z.B. OpenSSL und Co. arbeiten deshalb genau so: Sensible Daten, wie Passwörter oder Schlüssel, werden stets nur so lange wie für die Berechnung unbedingt nötig als Klartext im Speicher gehalten, und sofort danach mit SecureZeroMemory() bzw. explicit_bzero() wieder ausradiert. Genau für diesen Zweck existieren solche Funktionen. Genau so wirst Du bei einem Blick in die JCE (Java Cryptography Extension) sehen, dass dort alle sensiblen Daten (Schlüssel, Passwörter, temporäre Zwischenergebnisse) immer sofort nach Benutzung sehr sorgfältig mit Arrays.fill(secret, (byte)0) wieder überschrieben werden.

Dass Du sensible Daten zumindest temporär als Klartext im Speicher halten musst, um damit rechnen zu können, ist oftmals unvermeidlich – außer vielleicht Du verlagerst die Berechnung komplett in einen separaten Sicherheitschip (TMP, SmartCard, etc.), aber das ist hier nicht Thema – deshalb muss man doch nicht gleich jegliche Sicherheit über Board werfen. Es geht letztlich immer darum, das Angriffsfenster so klein wie möglich zu halten.

Nochmal: Schwarz-Weiß-Denken und undifferenzierte Aussagen wie "XZY ist nicht perfekt sicher und muss deshalb tausend Mal verteufelt werden!" helfen uns hier nicht weiter. Es geht viel mehr darum, im konkreten Anwendungsfall mit den verfügbaren Mitteln eine möglichst gute und praktikable Lösung zu finden. Wenn es konkrete Vorschläge gibt, wie man in dem konkreten Anwendungsfall die Sicherheit noch weiter verbessern kann, dann bin ich dafür dankbar. Aber, darauf zu beharren, dass bereits genutzte Sicherheitsmechanismen über Board geworfen werden soll, ohne gleichzeitig eine bessere Alternative anzubieten, ist und bleibt komplett kontraproduktiv... Und nein, einfach mal irgendwelche Lösungen als Buzzword in den Raum zu werfen, obwohl diese gar nicht für den betrachteten Anwendungsfall zutreffend sind, zählt nicht 😉

Die Frage die man sich auch stellen muss ist ob eure Anwendung zukünftig auch unter neueren .NET Versionen laufen soll und ggf. auf anderen Platformen wie Linux.
Ab dem Punkt würdet ihr mit SecureString auf die Nase fallen, was ihr auch vermeiden solltet.

Nach Quellenlage ist SecureString bei .NET Core in "bestimmten Umgebungen wo keine Verschlüsselung zur Verfügung steht" nicht verschlüsselt und damit dann genau so unsicher wie ein "normaler" String. So what? Damit wäre SecureString im schlimmsten Fall genau so unsicher wie String, in den meisten Umgebungen (und bei einem .NET Framework immer) aber viel sicherer. Etwas verlieren tust Du damit also auf keine Fall, außer evtl. ein klein wenig Performance. Du kannst also nur etwas gewinnen!

14.03.2023 - 00:28 Uhr

Einzig sichere Lösung: externe Passwortverwaltung und Flows wie OAuth nehmen. Damit besteht man auch moderne Security Audits.
Das sagt Dir auch Microsoft, Google, Amazon, Okta...

Alles wunderbare Lösungen... für andere Probleme 🙄

Nachmal: Es geht hier ganz konkret darum, dass eine Bibliotheks-Funktion (konkret eine Funktion aus BouncyCastle) aufgerufen werden muss, die ein Passwort als char[] Array entgegen nimmt. Das Passwort muss per GUI entgegen genommen werden, naheliegender Weise per PasswortBox, wobei hier theoretisch ein anderes Control herhalten könnte.

Bis jetzt habe ich noch von Dir noch nichts konkretes gehört, wie man das besser (sicherer) als mit einem SecureString lösen kann 😉

SecureString ist nicht "perfekt" sicher. Das haben wir verstanden. So what? Die Welt ist nicht schwarz/weiß. Eine "perfekte" Lösung gibt es in den seltensten Fällen. Deshalb muss man nicht gleich hinwerfen oder die schlechteste aller Lösungen nehmen. Es geht immer darum, mit den verfügbaren Möglichkeiten eine möglichst gute Lösung zu finden. Wenn ich also zwischen String (⇒ gar keine Sicherheit) und SecureString (⇒ weitestgehend sicher, mit bekannten Einschränkungen) wählen kann, um das Passwort zu sichern, dann werde ich doch immer SecureString nehmen! Für Alternativvorschläge bin ich jederzeit offen, aber sie müssen halt schon zum konkreten Anwendungsfall passen...

Aber Du kannst auch einen eigenen Standpunkt haben, und es besser wissen als der .NET Security PM oder die Identity Profis 😉

Aussagen wie "SecureString is not suitable for anything... Stop using SecureString" sind ganz klar undifferenziertes Clickbait.

Es gibt auch differenzierte Betrachtungen:

There is no alternative to the SecureString class. So, if you really need the credentials [...] use SecureString:
https://stackoverflow.com/a/55590953

SecureString is "discouraged by Microsoft" - that's an oversimplification:
https://stackoverflow.com/a/55590962

SecureString is a good and right idea. The reason Microsoft no longer recommends it is because .NET Core can't have it:
https://stackoverflow.com/a/67048259

13.03.2023 - 21:26 Uhr

Les den Artikel und die angehängten Design Issues vielleicht ganz. Deswegen hab ichs Dir geschickt. Das TLDR hast bekommen.

Ja, hab ich. Die einzigen "Argumente", die gegen eine Verwendung von SecureString sprechen, die dort genannt werden, sind:* SecureString könnte beim Entwickler falsche Erwartungen wecken.
⇒ Das ist natürlich komplett subjektiv 🙄

  • SecureString ist nicht 100% sicher, weil die Daten bei Verwendung doch wieder temporär entschlüsselt werden müssen und dann kurzzeitig als Klartext vorliegen.
    ⇒ Das wäre ein Argument, wenn es denn eine bessere Alternativ gäbe. Die wurde bisher aber noch nicht aufgezeigt. Ein "normaler" ungeschützter String bietet jedenfalls überhaupt keine Sicherheit, weil die Daten dann einfach die ganze Zeit als Klartext im Speicher rumliegen (anstatt wie bei SecureString nur hin und wieder für ein paar Millisekunden), und man sie am Schluss noch nicht mal explizit abräumen kann 😉

  • In manchen Umgebungen, wo keine Verschlüsselung unterstützt wird, ist SecureString komplett unverschlüsselt (betrifft anscheinend nur .NET Core)
    ⇒ Damit ist SecureString also im schlimmsten Fall genau so unsicher wie ein "normaler" String, im Normalfall (und bei .NET Framework immer) biete SecureString aber eine zusätzliche Sicherheit gegenüber String. Eine noch bessere Alternative zu SecureString ist auch hier wieder nicht genannt worden 😉

Kurz gesagt: Die Argumentation ist letzten Endes immer die, dass SecureString nicht "perfekt" sicher ist, und man deshalb mangels besserer Alternative (jedenfalls wird ja keine genannt) einfach keine(?) Sicherheit haben sollte...

Überzeugt mich noch nicht so ganz 😜

Recommendation
The general approach of dealing with credentials is to avoid them and instead rely on other means to authenticate, such as certificates or Windows authentication.

Dieser Tipp nützt bloß herzlich wenig, wenn man in einer realen Anwendung ein Passwort per PasswortBox entgegennehmen und an eine Bibliothek (nicht mein Code!) übergeben muss, die nun einmal ein char[] Array haben will 😲

Das Problem ist, dass die SecureString Implementierung suboptimal ist, was dazu führt, dass Reihenweise Firmen durch Security Audits fliegen.

Ein Audit, das dogmatisch bestimmte Dinge beanstandet ohne den Kontext zu berücksichtigen und ohne ein bessere+praktikable Alternative vorzuschlagen, ist reichlich sinnfrei.

Werden wir doch mal konkret:
Was ist denn der "empfohlene" Weg, um ein Passwort aus einer PasswortBox entgegen zu nehmen und weiter zu verarbeiten? Soweit ich sehe, kann man das Passwort entweder als String abrufen, was offensichtlich komplett unsicher ist, da das Passwort dann die ganze Zeit als Klartext im Speicher liegt, oder man kann das Passwort als SecureString abrufen, was zwar nicht "perfekt" sicher aber auf jeden Fall sehr viel sicherer als ein "normaler" ungeschützter String ist. Eine weitere Option sehe ich nicht...

So, oder so. Dein Kollege hat ja bereits die ursprüngliche Frage beantwortet. Nochmal danke dafür!

13.03.2023 - 19:15 Uhr

Hallo,

vielleicht hilft dir die Klasse SecureStringWrapper sowie dessen Methode ToByteArray aus der Antwort von
>
(als Verbesserung des Codes, welche sehr deinem bisherigen Code ähnelt, aber Marshal.SecureStringToBSTR nun verwendet - inklusive Überschreiben der Daten in der Dispose-Methode)?

Danke für den Hinweis auf Marshal.SecureStringToBSTR()!

Die Konvertierung in Bytes benötige nicht, aber man kann mit Marshall.Copy() direkt die char's raus kopieren 👍


    class SecureStringReader : IDisposable
    {
        public char[] Buffer { get; private set; } = Array.Empty<char>();

        public SecureStringReader(SecureString secureStr)
        {
            if ((secureStr != null) && (secureStr.Length > 0))
            {
                IntPtr temp = IntPtr.Zero;
                try
                {
                    char[] buffer = new char[secureStr.Length];
                    if ((temp = Marshal.SecureStringToBSTR(secureStr)) != IntPtr.Zero)
                    {
                        Marshal.Copy(temp, buffer, 0, secureStr.Length);
                        Buffer = buffer;
                    }
                }
                finally
                {
                    Marshal.ZeroFreeBSTR(temp);
                }
            }
        }

        public void Dispose()
        {
            Array.Clear(Buffer, 0, Buffer.Length);
        }
    }

Das ist leider ein Irrtum - und man soll den SecureString schon ewig nicht mehr benutzen. Er "verspricht" Sicherheit, die es nicht gibt.
Obsolete the SecureString type #30612

Naja, wenn man das mal im Detail liest:

DE0001: SecureString shouldn't be used
Motivation
The purpose of SecureString is to avoid having secrets stored in the process memory as plain text.

However, even on Windows, SecureString doesn't exist as an OS concept.

The contents of the array is unencrypted except on .NET Framework.

In .NET Framework, the contents of the internal char array is encrypted. .NET doesn't support encryption in all environments, either due to missing APIs or key management issues.

Recommendation
Don't use SecureString for new code. When porting code to .NET Core, consider that the contents of the array are not encrypted in memory.

...dann ist das "Problem" doch lediglich, dass SecureString keine 100% Sicherheit bietet, sondern "nur" die Dauer das Angriffs-Fenster verkürzt. Was eben daran liegt, dass der String, wenn er benutzt wird, zumindest temporär wieder als Klartext entschlüsselt werden muss.

Naja, kann man so sehen. Ich finde aber, die Zeitdauer, die das Passwort als Klartext im Speicher liegt, auf das nötige Minimum zu begrenzen, ist immer noch ein Vorteil – gegenüber der Alternative, dass das Passwort als "normaler" String für undefinierte Zeit als Klartext im Speicher rum liegt 😉

(Und .NET Core ist für mich im Moment nicht relevant)

13.03.2023 - 17:02 Uhr

Hallo.

Ich verwende eine WPF PasswortBox um das Passwort vom Benutzer entgegenzunehmen. Das Passwort wird wird als SecureString abgerufen, damit es nicht als Klartext im Speicher liegt.

So weit, so gut. Leider unterstützen allerdings nicht alle Funktionen SecureString. Eine Funktion, die ich benutzen muss (nicht mein Code!), will das Passwort als char[] Array.

Wie also kann ich ein SecureString in ein char[] Array umwandeln?

Ich habe bereits folgenden Code bei CodeProject gefunden, der eine SecureString in ein "normales" String umwandelt:


public string convertToUNSecureString(SecureString secstrPassword)
{
    IntPtr unmanagedString = IntPtr.Zero;
    try
    {
        unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secstrPassword);
        return Marshal.PtrToStringUni(unmanagedString);
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
    }
}

Ab dem Punkt könnte man natürlich einfach String.ToCharArray() benutzen. Allerdings möchte man die Umwandlung von SecureString nach String natürlich vermeiden!

Denn String ist bekanntlich immutable und kann man nicht überschreiben! Das Passwort bleibt auf unbestimmte Zeit im Speicher 😡

Bei einem char[] Array hat man das Problem nicht. Da kann man die sensiblen Daten z.B. mit Array.Clear() sofort überschreiben, wenn sie nicht mehr gebraucht würden.

Daher die Frage: Kann man SecureString irgendwie ohne den Umweg über ein String-Instanz direkt als char[] Array bekommen ???

Danke!

(Hinweis: Mir ist klar, dass wenn eine Funktion SecureString nicht direkt benutzen kann, die Daten so oder so zumindest "temporär" als Klartext im Speicher liegen)

10.03.2023 - 17:09 Uhr

Die Ursache, warum deine Methode bei mir nicht funktionierte lag daran, dass das Windows-Handle was durch die GetProcessesByName-Methode zurückgegeben wird, nicht das Windows-Handle des Fensters dieses Processes ist. Process.GetProcessByName() gibt ja ein Array von Process Objekten.

Ich denke process.Handle ist das Handle für den Prozess selbst, kein Fenster-Handle. Wenn Du das Handle für das Hauptfenster eines Prozesses haben willst, müsste process.MainWindowHandle funktionieren.

10.03.2023 - 00:46 Uhr

Mit deiner Methode: GetPopupWindow erhalte ich das Handle eines PopUp-Fenster eines (Parent-) Fenster's. Richtig?

Ja, genau. Als hWndParent übergebe ich den HWND meines Hauptfensters. Es funktioniert bei meinem Test wie folgt:


namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        private IntPtr m_hwnd = IntPtr.Zero;

        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
            if (source != null)
            {
                m_hwnd = source.Handle;
                DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Background);
                timer.Tick += OnTimerTick;
                timer.Interval = TimeSpan.FromMilliseconds(250);
                timer.Start();
            }
        }

        private void OnTimerTick(object sender, EventArgs e)
        {
            try
            {
                Title = $"hWndPopup=0x{GetPopupWindow(m_hwnd).ToString("X")}";
            }
            catch { }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.ShowDialog(this);
        }

        private static IntPtr GetPopupWindow(IntPtr hWndParent)
        {
            if (hWndParent != IntPtr.Zero)
            {
                IntPtr hWndPopup = GetWindow(hWndParent, GW_ENABLEDPOPUP);
                return (hWndPopup != hWndParent) ? hWndPopup : IntPtr.Zero;
            }
            return IntPtr.Zero;
        }

        public const uint GW_ENABLEDPOPUP = 6;

        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, uint command);
    }
}


<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Margin="25">
        <Button Content="Click!" Click="Button_Click"></Button>
    </Grid>
</Window>

Achtung: Wenn man das Programm im Visual Studio-Debugger laufen lässt, dann hängt Visual Studio automatisch eine zusätzliche Symbolleiste an der Titlebar des Hauptfensters an. Und diese wird ebenfalls als Popup-Fenster erkannt. Ich vermute es ist so, weil diese Symbolleiste technisch gesehen ein eigenes Fenster ist. Wenn man das Programm ohne Debugger laufen lässt, dann funktioniert aber alles wie erwartet, d.h hWndPopup ist 0, solange kein Popup-Fenster offen ist.

05.03.2023 - 20:19 Uhr

Zur Info, hier die Lösung:

GetWindow() mit Parameter GW_ENABLEDPOPUP liefert genau des gesucht Ergebnis 😉


private static IntPtr GetPopupWindow(IntPtr hWndParent)
{
    if (hWndParent != IntPtr.Zero)
    {
        IntPtr hWndPopup = GetWindow(hWndParent, GW_ENABLEDPOPUP);
        return (hWndPopup != hWndParent) ? hWndPopup : IntPtr.Zero;
    }
    return IntPtr.Zero;
}


public const uint GW_ENABLEDPOPUP = 6;

[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint command);

🥳🥳🥳

05.03.2023 - 17:50 Uhr

Wenn Du mir nicht glaubst, was Dein gutes Recht ist; schau Dir doch einfach Open Source Win32 / C++ Applikationen an, wie die mit den Fenster umgehen.
Vielleicht verstehst dann das Verhalten unter Windows einfach besser. Mehr kann ich da nun auch nicht mehr sagen...

Eigentlich ist es ja nicht so kompliziert: Jedes Fenster, ausgenommen das "Top-Level" Fenster (Hauptfenster), hat ein Parent-Window (Owner) wo es sozusagen "angedockt" ist.

Außerdem "blockiert" ein Popup-Fenster (modaler Dialog) sein Parent-Window so lange, bis es wieder geschlossen wird.

Diese Beziehung zwischen den Fenstern hat erstmal nichts damit zu tun, welches Fenster oder welche Anwendung gerade auf dem Bildschirm (systemweit) im Vordergrund ist.

Ich wüsste nur gerne, wie ich in einer WPF-Anwendung feststellen kann, ob mein Hauptfenster gerade so ein aktives Popup-Fenster als Child besitzt, oder eben nicht.

Zur Erklärung: Ich benötige diese Information, um in meinem Hauptfensters bestimmte Events zu ignorieren, so lange ein Popup-Fenster aktiv ist.

...denn auch wenn das Hauptfenster gerade durch ein Popup-Dialog für den Benutzer "blockiert" ist, werden die Events in diesem Fenster trotzdem weiter verarbeitet!

Wie schon erwähnt, eigentlich würde this.OwnedWindows genau diese Information bereitstellen, aber das klappt anscheinend nur mit Fenstern, die tatsächlich von WPF verwaltet werden (nicht mit OpenFileDialog u.s.w.).

GetActiveWindow() funktioniert nicht, weil es NULL zurück liefert, wenn gerade eine andere Anwendung als meine im Vordergrund ist.

EnumChildWindows() liefert bei meinem Test nur eine leere Ergebnismenge zurück 😠

EnumWindows() funktioniert im Prinzip. Aber es ist, wie schon weiter oben beschrieben, schwierig die "richtigen" HWND's aus den unzähligen HWND's, die man bekommt, raus zu filtern.

05.03.2023 - 16:51 Uhr

Das hat nichts mit "innerhalb" der Anwendung zutun, sondern ist nur eine Prozesszordnung.
So kann zB auch das Betriebssystem das Popup abschießen, wenn Du Du hart den Prozess beendest.

Kann sein, dass Du es über die Prozesszuordnung bekommst; aber zumindest funktioniert so nicht das Window Handle Prinzip.

Es geht nicht (nur) um die Process-ID. Wenn Du versuchst das Hauptfenster anzuklicken, während gerade ein Popup-Dialog aktiv ist, dann geht das nicht.

Standessen wird sofort der aktive Popup-Dialog in den Vordergrund geholt, sobald Du das Hauptfenster anklickst.

Also, es ist somit doch offensichtlich, dass der Popup-Dialog als ein (modales) Child-Window an meinem Hauptfenster "angedockt" ist, in der Fenster-Hierarchie.

Die Frage ist jetzt, wie kann man aus dem Programmcode den HWND des aktiven Popup-Dialog bekommen, bzw. feststellen ob überhaupt so ein Popup-Dialog aktiv ist ???

Wie schon gesagt, Application.Current.Windows ist leider unvollständig. Das selbe gilt übrigens auch für this.OwnedWindows 😉


Man kann mit EnumWindows() alle vorhandenen Fenster bekommen. Leider liefert es tatsächlich die Fenster von allen Anwendungen auf dem System!

Okay, kann man mit GetWindowThreadProcessId() ausfiltern, um nur die Fenster zu bekommen, die zum eigenen Prozess gehören.

Es bleiben immer noch ziemlich viele Fenster übrig, selbst wenn nur das Hauptfenster da ist. Keine Ahnung wieso ?!

Okay, ich kann mit GetParent() noch weiter ausfiltern, um nur die die Fenster zu bekommen, deren Parent-Window mit meinem Hauptfenster identisch ist...

Das klappt! Mehr oder weniger. Es bleiben noch zwei HWND's übrig, solange nur das Hauptfenster da ist. Keine Ahnung was die sind.

Aber: Wenn ich den OpenFileDialog öffne, kommen etliche HWND's dazu, die alle wieder verschwinden, sobald der OpenFileDialog geschlossen wird 👍

Trotzdem ist es noch unklar, welche HWND's jetzt für ein "aktives" Popup-Fenster stehen...


Nur Hauptfenster:
EnumWindows()
--> hwnd: 265076
--> hwnd: 3016478

OpenFileDialog geöffnet:
EnumWindows()
--> hwnd: 723696
--> hwnd: 920094
--> hwnd: 920308
--> hwnd: 789242
--> hwnd: 723808
--> hwnd: 854842
--> hwnd: 789190
--> hwnd: 854762
--> hwnd: 2230372
--> hwnd: 265076
--> hwnd: 3016478

OpenFileDialog wieder geschlossen:
EnumWindows()
--> hwnd: 265076
--> hwnd: 3016478

05.03.2023 - 15:35 Uhr

Das Model weiß auch so nichts über den Filter, kennt nur das Interface. Messdatenerzeugung, Filter usw. habe ich als Interfaces und Contructor Injection injiziert. Meine Frage ist eher, wie verheirate ich das mit dem Viewmodel.

Als ich würde sagen, entweder sind aus Sicht des Views dann das "Messdaten-Model" und das "Filter" zwei separate Models, die jeweils ihre eigenen Properties haben.

Das "Messdaten-Model" liefert Daten (die evlt. gefiltert wurden, was das View an der Stelle aber nicht zu wissen braucht) und "Filter" liefert Filter-Parameter.

...oder, wenn es unbedingt eine Model-Klasse sein soll, man schaltet da nochmal eine Façade-Klasse als "allumfassendes" Model bzw. Abstraktionsschicht davor, welches die Unterscheidung zwischen "Messdaten-Model" und "Filter" vor dem View versteckt.

Siehe:
Fassade (Entwurfsmuster)

05.03.2023 - 15:27 Uhr

Ein Popup gehört technisch nicht _innerhalb _ Deiner Anwendung, sondern zum Betriebssystem (zumindest wenn wir von einem Win32 API ausgelösten Popup sprechen, wie Du es hier wohl meinst). Das System Popup wirst Du also niemals in Deiner Anwendung so auswerten können. Das würde nur gehen, wenn Du das Popup komplett selbst implementierst / steuerst.

Nicht ganz. Das Popup-Fenster wird vllt. in manchen Fällen mit System-Funktionen wie GetOpenFileName() oder GetSaveFileName() erstellt, aber das Fenster "gehört" trotzdem meiner Anwendung.

Wenn ich z.B. ein OpenFileDialog erzeuge und beim Aufruf von ShowDialog() mein Hauptfenster als "Owner" festlege, dann wird das "OpenFileDialog" Popup-Fenster in der Fenster-Hierarchie ganz eindeutig ein untergeordnetes (Child) Fenster von meinem Hauptfenster. So lange das "OpenFileDialog" Popup-Fenster noch aktiv ist, kann man das Hauptfenster nicht mehr aktivieren – beim Anklicken des Hauptfensters wird automatisch das offene "OpenFileDialog" Popup-Fenster aktiviert bzw. in den Vordergrund gebracht.

Man kann auch mit Spy++ sich die Details von dem "OpenFileDialog" Popup-Fenster anschauen. Da ist eindeutig die Prozess-ID diejenige von meiner Anwendung und das zugehörige "Parent Window" ist das Hauptfenster meiner Anwendung.

Irgendwie müsste meine Anwendung doch also feststellen können, dass bzw. ob an ihrem Hauptfenster aktuell noch so ein Popup-Fenster dran hängt... 😉

Auch hilft Dir Foreground nicht, weil Foreground im Gegensatz zu Active das ganze System betrifft.

Leider betreffen GetActiveWindow() und GetFocus() eben auch das "ganze" System.

...nur mit dem Unterschied, dass diese Funktionen einfach NULL zurück geben, wenn das Fenster, dass sich gerade (systemweit) im Vordergrund befindet, nicht zu meiner Anwendung gehört. Tatsächlich gibt es aber ja, zu jedem Zeitpunkt, auch innerhalb meiner Anwendung ein "aktives" (am weitesten vorne angeordnetes) Fenster. Das ist das Fenster, welches dann (wieder) im Vordergrund sein wird, sobald der User wieder zu meiner Anwendung zurück wechselt.

05.03.2023 - 14:42 Uhr

Also die Idee wäre, dass man die Filter-Klasse so baut, dass sie im Konstruktor das konkrete Modell übergeben bekommt, aus dem sich das Filter dann "intern" seine Eingabe-Daten holt.

Nach "außen" hin stell die Filter-Klasse die gefilterten Properties/Daten bereit, plus zusätzlich Poperties um die Filter-Parameter einstellen zu können.

Entscheidend ist, dass die Model-Klasse somit nichts über die konkrete Filter-Klasse wissen muss. Insbesondere muss das Modell keine Filter-Parameter durchreichen. Filter können leicht ausgetauscht werden, ohne Modell anzupassen.

(Die Filter-Parameter könnten ja auch bei jeder konkreten Filter-Implementierung andere sein!)


Alternativ kannst Du dem Modell über eine SetFilter() Methode das konkrete Filter zuweisen. Das Modell verwendet dann "intern" den jeweiligen Filter, um die Daten zu filtern.

Es müsste dafür eine geeignete Filter-Schnittstelle (z.B. IFilter) geben, die von allen konkreten Filtern implementiert wird.

Entscheidend ist wiederum, dass die Model-Klasse nichts über die konkrete Filter-Klasse oder deren Parameter wissen muss. Die Modell-Klasse muss nur das Filter-Interface kennen/nutzen.

05.03.2023 - 14:18 Uhr

Könntest Du nicht den Filter als "Wrapper" (Adapter) um Deine Modell-Klasse herum bauen?
Adapter (Entwurfsmuster)

Somit könnte der Filter die relevanten Modell-Properties einfach durchreichen (mit entsprechender Filterung) und zugleich seine "eigene" Properties (zum Einstellen der Filter-Parameter) ergänzen.

Ungefähr so:


new Filter(new Model());


Ansonsten wäre noch ein "Strategy" Pattern denkbar, so dass Du direkt auf das Filter-Objekt zugreifen kannst ohne dessen Properties durchreichen zu müssen:
Strategie_(Entwurfsmuster)


filter = new Filter();
model = new Model();
model.setFilter(filter);

05.03.2023 - 14:08 Uhr

Gibt es eine Möglichkeit, um zuverlässig das "aktive" (am weitesten vorne angeordnete) Fenster innerhalb meiner Anwendung festzustellen?

Kurz gesagt, wenn meine Anwendung gerade ein Popup-Fenster anzeigt, dann soll dieses Popup-Fenster zurück geliefert werden, andernfalls soll das Hauptfenster zurückgeliefert werden.

Insbesondere darf das Ergebnis nicht davon abhängen, ob meine Anwendung (als Ganzes) gerade "im Vordergrund" ist.

Folgende "Lösung" klappt leider nicht:


Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);

Es klappt aus zwei Gründen nicht:* Manche Popup-Fenster, wie z.B. ein OpenFileDialog, sind überhaupt nicht in Application.Current.Windows enthalten 😡

  • IsActive ist nur true, wenn das Fenster "aktive" ist und meine Anwendung gerade "im Vordergrund" ist. Wenn hingegen gerade eine andere Anwendung "im Vordergrund" ist, dann ist IsActive einfach mal für alle meine Fenster false 🙁

Was die Win32 API angeht, hab eich folgendes schon probiert:* GetActiveWindow() und GetFocus() funktionieren aus dem selben Grund nicht. Sie liefern NULL zurück, wenn meine Anwendung nicht gerade "im Vordergrund" ist.

  • GetForegroundWindow() hilft leider auch nicht weiter, da es ggf. Fenster zurück liefert, die zu anderen Anwendungen gehören...
28.02.2023 - 11:01 Uhr

Eigentlich steht doch in deiner Antwort ein brauchbarer Ansatz.
Am besten wäre es, wenn du einfach erst auf die letzte empfange Nachricht wartest und die anderen erstmal ignorierst.

Genau! Das ist ja der Ansatz mit dem Timer, den ich schlussendlich umgesetzt habe. Denn Du weißt ja nie, wenn eine so eine Nachricht rein kommt, ob da gleich hinterher noch eine weitere Nachricht kommen wird oder ob das jetzt (erstmal) die letzte war. Also "einfach erst auf die letzte empfange Nachricht warten" geht wohl nur mit dem Umweg über einen Timer.

Der Hash ist quasi nur eine zweite Sicherheit. Wenn man eh den Clipboard-Inhalt holt (beim Timer-Event), dann ist es kaum Overhead, noch kurz den Hash-Wert abzugleichen.

Es ist nur seltsam, dass solche "Workarounds" überhaupt nötig sind. Über das Problem stolpert schließlich jeder früher oder später, der sich mit WM_CLIPBOARDUPDATE auseinander setzt. Würde Windows von vorne herein nur eine Nachricht pro Änderung schicken – das Timeout könnte man von mir aus bei AddClipboardFormatListener() als Parameter festlegen – oder würde wenigstens Clipboard.IsCurrent() so funktionieren, wie man es erwartet, dann könnte man sich das alles ja sparen...

Egal. Hätte, wäre und wenn. Für mich ist der Fall jetzt erstmal gelöst, denke ich 😉

[Ein Thema als "gelöst" markieren gibt's hier anscheinend nicht, oder übersehe ich schon wieder was?]

27.02.2023 - 21:46 Uhr

Wenn man als einziger nen Workaround erfindet, dann riecht das immer etwas fischig...
Auf GitHub gibts ja hunderte Clipboard Projekte mit Win32/.NET/C#. Dutzende Extensions, Viewer, Watchers... paar Stück überflogen und nirgends hab ich nen Timer gesehen.
Sind die alle viel simpler als Dein Vorhaben, oder wieso brauchen die das nicht - aber Du diesen Timer Workaround?

Wenn Du eine einfachere/bessere Lösung hast, dann gerne her damit 🙂

Fakt ist, man findet im Netz diverse Diskussionen dazu, dass Windows gerne mal etliche WM_CLIPBOARDUPDATE Messages für jede einzelne Clipboard-Änderung liefert, siehe z.B.:
WM_CLIPBOARDUPDATE event may be triggered multiple times for a single cut action on Windows · Issue #2 · sudhakar3697/node-clipboard-event
clipboard-listener-event-is-being-called-twice
https://www.autohotkey.com/boards/viewtopic.php?style=1&t=96544

I've seen such madness before. Excel used to perform 24 separate operations when copying a chart.
[...]The usual mitigation strategy is to avoid reacting to every update, and react to the LAST update after a reasonable "settle time" has elapsed with no further clipboard notifications. 500ms will usually be more than adequate.

Bin also nicht der erste, der über dieses Phänomen gestolpert ist!

Eine Lösung, wie man dieses Verhalten abstellen kann, hab ich aber leider nicht gesehen. Anscheinend gilt hier: „It's not a bug, it's a feature“ 🙄

Klar, man kann das einfach ignorieren und bei jeder WM_CLIPBOARDUPDATE Message einfach (erneut) die komplette Verarbeitung durchführen. Das "funktioniert" schon.

Nur ist es eben nicht sonderlich performant und irgendwie auch "unschön", sinnlos die selbe Berechnung mehrmals hintereinander durchzuführen...

versuchst Du Rekorde bei den Beitragsbearbeitungen aufzustellen?

Du bringst mich ja auf Ideen 👍

Aber im Ernst: Ich ergänze eben oftmals noch weitere Informationen, wenn es (aus meiner Sicht) Sinn macht.

Schick doch den Beitrag erst ab, wenn er fertig ist.

Gegenvorschlag: Lass doch mal die Leute machen 😉

27.02.2023 - 18:31 Uhr

dann bleibt wohl wirklich nur die Timer-gesteuerte Umsetzung.

Es sieht wohl so aus. Ich habe es nun so gelöst, dass der OnClipboardChanged()-Handler indirekt per Timer aufgerufen wird. Der Timer wird gestartet wenn ein WM_CLIPBOARDUPDATE eingeht. Außerdem wird in OnClipboardChanged() immer zuerst der Hash berechnet und mit dem alten Hash vergleichen (der letzte Hash ist in einer statischen Variable gesichert). Die eigentliche Verarbeitung und das Update des UI findet nur statt, wenn sich der Hash seit dem letzten Mal geändert hat.

Warum einfach, wenn's auch kompliziert geht? 😁

26.02.2023 - 14:55 Uhr

Hast du auch explizit GetFormats(false) aufgerufen (denn GetFormats() liefert auch alle konvertierbaren Formate), s.
>
?

Nein, hatte ich nicht. Mit GetFormats(false) bekomme ich nur noch "UnicodeText", aber trotzdem mehrere WM_CLIPBOARDUPDATE Nachrichten, z.B.:

[004420BC] WM_CLIPBOARDUPDATE
[004420BC] GetFormats() = [UnicodeText]
[004420CB] WM_CLIPBOARDUPDATE
[004420CB] GetFormats() = [UnicodeText]
[004420EB] OnClipboardChanged()
25.02.2023 - 17:22 Uhr

Es wird wohl so sein, daß für jedes Format, das zum Clipboard hinzugefügt wird, ein WM_CLIPBOARDUPDATE ausgelöst wird (je nach Anwendung können ja z.B. TEXT, HTML und RTF als Formate erzeugt werden). Daher könntest du testweise mal die Formate während eines WM_CLIPBOARDUPDATE loggen.

Es sind immer die selben drei Formate (Text, Unicode und String), bei jeder WM_CLIPBOARDUPDATE Message.

Kann schon sein, dass für jedes Format eine separate WM_CLIPBOARDUPDATE Nachricht verschickt wird, aber es sind schon alle frei Format da, bevor die erste Nachricht ankommt.

Geht es dir bei deinem Programm denn nur um ein spezielles Format (also z.B TEXT) oder alle möglichen Formate (auch BITMAP etc.)?

Erstmal nur Text.

Also ich habe jetzt den Workaround mit dem Timer umgesetzt, mit 25 ms Tick-Intervall. Und es scheint so weit sinnvoll zu funktionieren.

24.02.2023 - 16:30 Uhr

Du könntest den Hashcode der Formate vergleichen:

  
int hash = data.GetData(format).GetHashCode();  
  

Also eine Liste für jedes Format (data.GetFormats()) erzeugen und dessen Hashcodes speichern.
Dann bei WM_CLIPBOARDUPDATE die Formate und deren Hashcodes vergleichen.

Also ein Hash nur über die verfügbaren Formate bringt nichts, denke ich. Es kann ja sein, dass vor und nach einer Änderung die genau selben Formate verfügbar sind. Ob sich der eigentliche Inhalt geändert hat, kann man nicht sagen.

Man kann sich natürlich für jedes der verfügbaren Formate (oder zumindest für die relevanten) die eigentlichen Daten holen und darüber dann jeweils einen Hash berechnen. Daran könnte man Änderungen am Inhalt erkennen, das ist klar. Aber bei jeder eingehenden WM_CLIPBOARDUPDATE-Nachricht sämtliche Daten neu aus dem Clipboard zu holen und zu hashen, das erscheint mir ein sehr großer Overhead zu sein, den man ja gerade an der Stelle vermeiden will... 😭

Ich habe noch im Netz eine andere "Lösung" gefunden:

Man soll bei einer eingehenden WM_CLIPBOARDUPDATE-Nachricht einen Timer starten und die Verarbeitung erst dann machen, wenn der Timer auslöst. Weitere WM_CLIPBOARDUPDATE-Nachrichten werden ignoriert, wenn sie eingehen, während der Timer bereits gestartet wurde aber noch nicht ausgelöst hat. Denke das würde funktionieren, aber es erscheint mir ein ziemlich "komplexer" Workaround für ein Problem, das gar nicht existieren sollte...

PS: Statt Fettdruck entweder "Code (Inline)" oder "vorformatierter Text" (die beiden rechten Buttons) hier im Forum benutzen.

👍

24.02.2023 - 15:28 Uhr

Nicht nötig so viel in Fettschrift zu schreiben. Wir können lesen 😉

Und wenn ich die Code-Ausdrücke nicht so vorbildlich hervor gehoben hätte, dann wäre als erstes der Kommentar gekommen, man soll doch gefälligst den Text gescheit formatieren. Schon klar 😁

Ich glaube das hier hilft Dir
>

Leider nein.

Das ist ja im Prinzip genau das, was ich bereits umgesetzt habe, bloß dass die noch die veraltete SetClipboardViewer() API mit WM_DRAWCLIPBOARD und WM_CHANGECBCHAIN benutzten, anstatt AddClipboardFormatListener() mit WM_CLIPBOARDUPDATE.
Using the Clipboard - Win32 apps

Ansonsten ist die Idee genau gleich: Anwendungs-Fenster für Update-Nachrichten registrieren, und anschließend die eingehenden Update-Nachrichten in der WndProc() verarbeiten.

Er hat das hübsch als Klasse verpackt, aber ich sehe nicht, dass er irgendwo "mehrfach" Events ausfiltert.

Das Problem ist nämlich immer das selbe: Man bekommt pro Änderung des Clipboard-Inhalts oftmals mehrere solcher Benachrichtigungen vom System!

Übrigens geben sich in dieser Beziehung WM_CLIPBOARDUPDATE und WM_DRAWCLIPBOARD nichts. Die kommen beide gleichermaßen mehrfach rein. Das hab ich schon ausprobiert 😉

Das Problem ist anscheinend bekannt:
https://github.com/sudhakar3697/node-clipboard-event/issues/2

Wie also findet man heraus, ob sich bei einer eingehenden WM_CLIPBOARDUPDATE Message wirklich etwas geändert hat, seit der letzten? Wenn sich nichts geändert hat, soll keine erneute Verarbeitung stattfinden!


Und nein, GetClipboardSequenceNumber() hilft da ebenfalls nicht:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboardsequencenumber

Es zählt offensichtlich pro WM_CLIPBOARDUPDATE Nachricht hoch, nicht pro tatsächlicher Änderung des Clipboard-Inhalt. Wäre ja auch zu einfach 😜

24.02.2023 - 14:46 Uhr

Danke! Du hast recht, die Win32 API Dokumentation von OleIsCurrentClipboard() sagt es:

Hinweise
OleIsCurrentClipboard() funktioniert nur für das Datenobjekt, das in der OleSetClipboard()-Funktion verwendet wird. Es kann vom Verbraucher des Datenobjekts nicht aufgerufen werden, um festzustellen, ob das Objekt, das sich in der Zwischenablage im vorherigen OleGetClipboard()-Aufruf befindet, weiterhin in der Zwischenablage vorhanden ist.

Da ist die Dokumentation von Clipboard.IsCurrent() leider sehr unvollständig!

😠

FRAGE: Wie kann man sonst feststellen, ob sich der Clipboard-Inhalt seit dem letzten WM_CLIPBOARDUPDATE geändert hat oder nicht ???

24.02.2023 - 14:25 Uhr

Hallo.

Ich muss für eine Anwendung die Zwischenablage (Clipboard) auf Änderungen überwachen. Dazu wird mit AddClipboardFormatListener() das Hauptfenster der WPF-Anwendung registriert, mit HwndSource.AddHook() ein Hook auf der WndProc() installiert, und anschließend können dann die WM_CLIPBOARDUPDATE Nachrichten verarbeitet werden:

private const int WM_CLIPBOARDUPDATE = 0x031D;

protected override void OnSourceInitialized(EventArgs e)
{
	base.OnSourceInitialized(e);
	HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
	if (source != null)
	{
		source.AddHook(WndProc);
		AddClipboardFormatListener(m_hWndSelf = source.Handle);
	}
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
	switch (msg)
	{
		case WM_CLIPBOARDUPDATE:
			[...]
			handled = true;
			break;
	}
	return IntPtr.Zero;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AddClipboardFormatListener(IntPtr hWnd);

Das klappt so weit auch. Das Problem ist jetzt, dass Windows bei jeder Clipboard-Änderung häufig ++:::

Laut Google ist das anscheinend "normal" so und man muss damit umgehen 🙄

Natürlich soll aber bei einer Änderung des Clipboard-Inhalt der neue Inhalt nur ++:::

Meine Idee war daher, den Clipboard-Inhalt mit Clipboard.GetDataObject() zu lesen, wenn ein WM_CLIPBOARDUPDATE rein kommt. Außerdem wird der alte Inhalt als IDataObject in einer statischen Variable gespeichert. Daher kann bei jeder eingehenden WM_CLIPBOARDUPDATE Message mittels Clipboard.IsCurrent() geprüft werden, ob sich der Inhalt seit dem vorhergehenden WM_CLIPBOARDUPDATE tatsächlich geändert hat (oder eben nicht).

Leider funktioniert Clipboard.IsCurrent() anscheinend gar nicht ?!?! 😡

Die Funktion liefert immer false zurück, selbst wenn sich der Clipboard-Inhalt definitiv nicht geändert hat, seit der vorhergehenden WM_CLIPBOARDUPDATE Message.

Sogar folgender Code, der den Clipboard-Inhalt holt und sofort prüft, ob der Inhalt noch aktuell ist, liefert bei meinem Test immer false zurück:

IDateObject data = Clipboard.GetDataObject();
Clipboard.IsCurrent(data); // <-- false !!!

Wie kann das denn sein? 🤔

Returns
true if the specified data object matches what is on the system Clipboard; otherwise, false. Clipboard.IsCurrent(IDataObject) Method (System.Windows)

Danke!