2010-11-23 6 views
1

ich einen Pool von unveränderlichen Verschlüsselungshilfsobjekte haben, die Instanzen der Java JCA Cipher und Message Objekte enthalten:Ist dieser Crypto Code Thread sicher?

AlgorithmInstance(Cipher encCipher, Cipher decCipher, MessageDigest digest) { ... } 
private BlockingQueue<AlgorithmInstance> pool = new ArrayBlockingQueue<AlgorithmInstance>(poolSize); 

Verschiedene Themen in meiner Anwendung, um die Verschlüsselung oder Entschlüsselung, kämpfen für AlgorithmInstance Objekte durch einen Pool zugreifen. Jeder Thread verwendet sie zum Verschlüsseln oder Entschlüsseln und gibt sie anschließend an den Pool zurück, wenn sie fertig sind. Threads werden auf keinem der JCA-Objekte synchronisiert, da kein gleichzeitiger Zugriff möglich ist. Decrypt funktioniert ungefähr auf die gleiche Weise.

public byte[] encryptMessage(byte data[]) { ... 
try { 
    AlgorithmInstance inst = pool.take(); 

    inst.digest.reset(); 
    byte[] digest = inst.digest.digest(message); 

    inst.encryptCipher.init(Cipher.ENCRYPT_MODE, m_currentKey, ivParams); 
    inst.encryptCipher.doFinal(messageBuffer); 
} 
finally { 
    pool.put(inst) 
} 
} 

Dies funktioniert 99,99% der Zeit; und 100% der Zeit in Einzeltests. Aber sobald ich in einem blauen Mond bin, bekomme ich eine Nachricht, deren berechneter Digest nicht richtig herauskommt - normalerweise deutet dies auf eine Manipulation der Nachricht oder Netzwerkfehler hin; Sender und Empfänger befinden sich jedoch auf derselben Maschine (in verschiedenen Prozessen).

F: Gibt es einen internen Status für eine Chiffre oder einen Digest, der unter Speicherkonsistenzeffekte leiden kann - ich bin auf einer 2-Kern-Windows-Box, so sehe ich nicht, wie ich unter Speicherkonsistenz leiden könnte Auswirkungen. Ich initialisiere die Chiffre und verdaue jeden Anruf, so dass es keine Rolle spielt.

F: Gibt es eine Möglichkeit, dass ich mit einem Auffüllmodus gelandet wäre, der manchmal aufgrund der Nachrichtenlänge fehlschlägt? Der Entschlüsseler und der Verschlüsseler verwenden genau dieselben Algorithmen (AES/CBC/Pkcs5Padding + SHA-256 und eine Schlüsselgröße von 128).

+1

Antwort 1: Nicht, dass ich sehen kann. Antwort 2: Nr. –

+0

Mein Gefühl ist, dass es einige Nebeneffekte der Klasse GC oder einige gemeinsame Puffer in den Ciphers/Digests; oder als @Rook schlägt vor, dass ich es falsch benutze. Es passierte wieder und die Digests waren total verschieden, nicht nur ein paar Bits anders. Absolut keine Ahnung. – Justin

+0

Gibt es Gründe, warum Sie sie überhaupt zusammenlegen? Gibt es wirklich einen Leistungsvorteil? Ich hätte gedacht, dass das Erstellen der Cyper-Objekte nicht so teuer wäre. – CodesInChaos

Antwort

0

Ich habe den Code so geändert, dass ich auf der AlgorithmInstance synchronisieren, während ich eines seiner enthaltenen Objekte verwende. Ich habe dieses Problem seitdem nicht gesehen; aber es ist nicht klar warum; da die queue.put() und queue.take() Operationen sollten GENAU die gleiche Form geschieht zuvor Beziehung durch den Monitor Unlock gebildet:

synchronized (inst) { 
... 
// do crypto opperations 
} 

Die einzige andere Möglichkeit, ich ist kommen kann, dass die IVParamSpec während des Cipher.init modifiziert() Berechnung und dann am Ende wiederhergestellt. Da die ivParams nur read-on und für alle Objekte im Pool freigegeben sind, könnte dies zu einem unsynchronisierten Zugriff und möglicherweise zu MCE führen.

+0

Ich ging auch von JDK 1.6_21 -> 1.6_22 – Justin

+0

Hallo Justin! Hast du tiefer gegriffen? Ich bin dabei, Code fast identisch mit Ihrer ursprünglichen Version zu schreiben, und möchte alle Fallstricke vermeiden. Soweit ich das beurteilen kann, sollte der Code, den Sie zuerst gepostet haben, gut funktionieren. Selbst wenn 'ivParams' veränderbar wären, sollte die Synchronisation auf' inst' keinen Unterschied machen, richtig? – Martin

+0

Ich kann nicht zwischen dem JVM-Upgrade und dem synchronisierten Block unterscheiden. Nach beiden, keine Probleme. Ich konnte leider keine Ursache finden. – Justin

0

Die Funktion encryptMessage() ist selbstsicher threadsicher, weil sie keinen gemeinsamen Speicher für 2 Threads verwendet. Es ist jedoch wahrscheinlich, dass der Code, der diese Funktion verwendet, nicht Thread-sicher ist. Wenn Sie gleichzeitig Daten innerhalb eines Pools oder einer Warteschlange verschlüsseln/entschlüsseln wollen, ist es wahrscheinlich, dass der "aktuelle Arbeitsindex" nicht in einer Thread-sicheren Weise inkrementiert oder dekrementiert wird. Ein globaler Index, der zwischen Threads geteilt wird, kann erstellt werden, indem eine globale Ganzzahl durch einen Mutex geschützt wird. Bevor dieser Wert gelesen und dann geändert wird, muss ein Thread eine Sperre für diese Variable anfordern.

+0

Es war nicht klar aus meinem ursprünglichen Beitrag, aber der Pool ist eine ArrayBlockingQueue. Sagst du, dass der Pool das Problem ist? Jede BlockingQue soll threadsicher sein: aus dem javadoc 'Memory consistency effects: Wie bei anderen gleichzeitigen Collections, Aktionen in einem Thread vor dem Platzieren eines Objekts in eine BlockingQueue geschehen vor Aktionen nach dem Zugriff oder Entfernen dieses Elements aus der BlockingQueue in einem anderen Thread. ' – Justin

+0

@Justin Crypto ist keine Magie, es sind nur einige binäre Operationen auf einer Zeichenfolge. Diese Frage ist wie fragen ist '.substring()' thread sicher? – rook

+0

Also sagst du, dass der Pool das Problem ist? BTW, die meisten Chiffren zuweisen viele interne Tabellen, so dass es das Potenzial für MCE gibt. – Justin

3

Hat Ihre App viele Threads? Anstatt einen Pool zu verwenden, können Sie Ihre AlgorithmInstances in ThreadLocal-Objekte einfügen und so sicherstellen, dass jede AlgorithmInstance immer nur von demselben Thread verwendet wird. Daher sollte das Problem verschwinden und Sie müssen weder die AlgorithmInstance noch damit du eine bessere Leistung bekommst).