2010-11-23 18 views
1

J'ai un pool d'objets d'aide de cryptage immuables qui contiennent des instances de la java JCA Cipher et objets MessageDigest:Ce thread de code crypto est-il sécurisé?

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

Divers fils dans mon application, le cryptage ou le décryptage besoin, composer des objets AlgorithmInstance en accédant à une piscine. Chaque thread les utilise pour chiffrer ou déchiffrer, puis les renvoie dans le pool lorsqu'ils sont terminés. Les threads ne se synchronisent sur aucun objet JCA car il n'y a pas d'accès simultané. Decrypt fonctionne à peu près de la même manière.

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) 
} 
} 

Cela fonctionne 99.99% du temps; et 100% du temps dans les tests unitaires. Cependant, une fois dans une lune bleue, je reçois un message dont le résumé calculé ne sort pas correctement - normalement cela indique une falsification de message ou des erreurs de réseau; mais l'expéditeur et le destinataire sont sur la même machine (dans différents processus). Existe-t-il un état interne pour un chiffrement ou un chiffrement qui peut souffrir d'effets de consistance de la mémoire? Je suis dans une boîte Windows 2 Core donc je ne vois pas comment je pourrais même souffrir de la cohérence de la mémoire effets. Je réinitialise le chiffrement et digère chaque appel, cela ne devrait donc pas avoir d'importance.

Q: Y at-il un moyen que j'aurais pu finir avec un mode de remplissage qui échoue parfois en fonction de la longueur du message? Le décrypteur et le chiffreur utilisent exactement les mêmes algorithmes (AES/CBC/Pkcs5Padding + SHA-256, et une taille de clé de 128).

+1

Réponse 1: Pas que je puisse voir. Réponse 2: Non. –

+0

Mon sentiment est que c'est un effet secondaire de la classe GC ou un tampon partagé dans les Chiffreurs/Digests; ou comme @Rook suggère que je l'utilise mal. C'est arrivé encore, et les digestions étaient totalement différentes et pas seulement quelques bits différents. Absolument aucune idée. – Justin

+0

Y a-t-il une raison pour laquelle vous les mettez en commun? Cela donne-t-il vraiment un avantage de performance? J'aurais deviné que créer des objets Cyper ne serait pas si cher. – CodesInChaos

Répondre

0

J'ai changé le code afin que je synchronise sur l'AlgorithmInstance pendant que j'utilise l'un de ses objets contenus. Je n'ai pas vu ce problème depuis; Cependant, ce n'est pas clair pourquoi; étant donné que les queue.put() et queue.take() opérations devraient former exactement le même se passe-avant relation formée par le déverrouillage de l'écran:

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

La seule autre possibilité que je peux trouver est que le IVParamSpec est modifié au cours de la Cipher.init() calcul puis restauré à la fin. Puisque les ivParams sont supposés en lecture seule et partagés entre tous les objets du pool, si cela est vrai, cela pourrait conduire à un accès non-synchronisé et éventuellement à un MCE.

+0

Je suis aussi passé de JDK 1.6_21 -> 1.6_22 – Justin

+0

Salut Justin! Avez-vous creusé plus profondément dans cela? Je suis sur le point d'écrire du code presque identique à votre version originale, et j'aimerais éviter les pièges. Pour autant que je sache, le code que vous avez d'abord publié devrait fonctionner correctement. Même si 'ivParams' était mutable, synchroniser sur' inst' ne devrait pas faire de différence, non? – Martin

+0

Je ne peux pas distinguer entre la mise à niveau de la JVM et le bloc synchronisé. Après avoir fait les deux, pas de problèmes. Je ne pouvais pas trouver une cause profonde, malheureusement. – Justin

0

La fonction encryptMessage() est self-thread car elle n'utilise pas la mémoire partagée par 2 threads. Cependant, il est probable que le code qui utilise cette fonction ne soit pas thread-safe. Si vous souhaitez chiffrer/déchiffrer simultanément des données dans un pool ou que, il est probable que l'index de travail actuel n'augmente pas ou ne décrémente pas de manière sécurisée. Un index global partagé entre threads peut être créé en ayant un entier global protégé par un mutex. Avant que cette valeur soit lue puis modifiée, un thread doit demander un verrou sur cette variable.

+0

Il n'était pas clair à partir de mon message d'origine mais le pool est un ArrayBlockingQueue. Voulez-vous dire que la piscine est le problème? Tout BlockingQue est censé être threadsafe: à partir du javadoc 'Effets de cohérence mémoire: Comme avec d'autres collections concurrentes, les actions dans un thread avant de placer un objet dans un BlockingQueue arrivent avant les actions postérieures à l'accès ou au retrait de cet élément du BlockingQueue dans un autre fil. ' – Justin

+0

@Justin Crypto n'est pas magique, c'est juste quelques opérations binaires sur une chaîne. Cette question est comme si demander '.substring()' thread sûr? – rook

+0

Donc, vous dites que la piscine est le problème? BTW, la plupart des chiffrements allouer beaucoup de tables internes de sorte qu'il existe un potentiel pour MCE. – Justin

3

Votre application comporte-t-elle beaucoup de threads? Parce qu'au lieu d'avoir un pool, vous pouvez placer votre AlgorithmInstances dans des objets ThreadLocal, s'assurant ainsi que chaque AlgorithmInstance n'est utilisée que par le même thread, le problème devrait disparaître et vous n'aurez plus besoin de synchroniser sur AlgorithmInstance (donc vous aurez une meilleure performance).