2017-08-31 11 views
1

Voici donc mes objectifs:CryptoStream me force à fuir les données sensibles dans la RAM

  1. décryptez un byte[] dans un épinglébyte[] tampon.
  2. Je ne veux pas que le texte brut existe ailleurs en mémoire, en dehors de ce byte[] épinglé, que je contrôle.

Comment puis-je faire cela en C#? J'ai utilisé naïvement la classe CryptoStream. Mais cela exige moi pour lui donner un flux de sortie. Je dois. Alors je suis allé de l'avant et lui ai donné un MemoryStream. J'ai fait un peu de reniflage en mémoire, en utilisant la fenêtre de débogage mémoire. Je crois que j'ai trouvé que MemoryStream a une copie (pour le tampon?) De tout ce qui sort du CryptoStream. Alors maintenant, le texte brut est à la fois dans mon byte[] épinglé, et dans cette autre partie de la mémoire qui peut être copiée au hasard par le CLR, et sur laquelle je n'ai aucun contrôle.

Voici le code que j'ai utilisé. Je suppose pas ici pour la simplicité concurrency:

public class ExampleCode 
{ 
    private SymmetricAlgorithm algorithm; 
    private ICryptoTransform decryptor; 

    public ExampleCode(byte[] key, byte[] iv) // c'tor 
    { 
     algorithm = new RijndaelManaged(); 
     algorithm.Key = key; 
     algorithm.IV = iv; 
     decryptor = algorithm.CreateDecryptor(); 
    } 

    private long DecryptToPinnedArray(byte[] src, byte[] pinnedDst) 
    { 
     long bytesWritten = 0; 

     using (var ms = new MemoryStream(pinnedDst, writable: true)) 
     using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write)) 
     { 
      cs.Write(src, 0, src.Length); 
      cs.FlushFinalBlock(); 
      ms.Flush(); 

      bytesWritten = ms.Position; 
      return bytesWritten; 
     } 
    } 
} 

Comment puis-je éviter cette deuxième copie des données sensibles d'être jamais créé? Devrais-je oublier CryptoStream et utiliser quelque chose de plus bas niveau? Existe-t-il une meilleure pratique pour des problèmes comme celui-ci?

EDIT: Peeking avec un réflecteur, ce que je pense qui se passe:

CryptoStream.Write():

  1. cipher_data - = copier - =>_InputBuffer (un CryptoStream interne non épinglé byte[]).
  2. _InputBuffer - = transformer - =>_OutputBuffer (a désépinglé interne CryptoStreambyte[]).
  3. _OutputBuffer - = écriture - =>MemoryStream

Si elle doit (et peut) transformer plus d'un bloc à la fois, il utilisera un désépinglé temporaire de plus grande envergure locale byte[] (multiBlockArray) pour essayer de transformer tous les blocs en une seule fois (au lieu de _OutputBuffer). Il écrit multiBlockArray dans le flux. Il perd ensuite la référence à ce tableau, et ne tente même pas de le désinfecter. Bien sûr, ce n'est pas épinglé de toute façon.

CryptoStream.FlushFinalBlock() & CryptoStream.Dispose():

Tous deux Array.Clear le _OutputBuffer. C'est mieux que rien, bien que _OutputBuffer ne soit pas épinglé, donc il peut encore potentiellement fuir les données en texte brut.

+0

Je ne vois rien dans la classe [MemoryStream] (https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/io/memorystream.cs) qui ressemble à une * copie * de le tampon que vous fournissez dans le constructeur. –

+0

Si ce n'est pas le 'MemoryStream' alors c'est peut-être le' decryptor', ou le 'CryptoStream' lui-même? J'ai ajouté des informations sur le 'decryptor' que j'ai utilisé pour la question. –

Répondre

1

Cela peut être une réponse opiniâtre, prenez donc pour ce qui vaut le coup:

Le constructeur MemoryStream vous utilisez est déjà celui qui fonctionne sur le passé dans le tableau pinnedDst. Il ne s'agrandira pas, ne se réallouera pas, etc. Ce tableau va simplement le lire et l'écrire. Comme il est déjà épinglé, vous pouvez être sûr qu'il ne sera pas déplacé par le GC.

Cependant, toutes les opérations de flux en C# fonctionnent à l'aide de tampons byte[] pour lire ou écrire dans le flux. Lorsque vous écrivez à CryptoStream, il va créer une sorte de tampon de texte brut à utiliser pour copier des données sur votre MemoryStream - il ne peut être quelques octets, ou il peut être aussi grande que l'ensemble de votre tableau src. C'est (probablement) jusqu'à la longueur de bloc du chiffrement qu'il utilise et/ou comment il a été écrit.

Si vous voulez vraiment éviter que votre texte brut ne soit déplacé pendant une opération GC et recyclé dans le tas géré sans être effacé, vous devrez probablement appeler l'API crypto Windows. Cependant, P/invoquant l'API Crypto de Windows nécessiterait des blocs de code non sécurisés pour obtenir des pointeurs sur votre tableau, ce qui signifie que vous activez ce qui vous rend vulnérable aux attaques de mémoire, permettant le déréférencement de pointeurs non contrôlé dans votre processus. Je ne voudrais pas perdre le sommeil sur le code que vous avez écrit ci-dessus. Je n'autoriserais pas non plus les blocs de code non sécurisés dans un assembly .net qui doivent gérer le décryptage étendu PCI-DSS. Comme vous le faites déjà, utilisez des tableaux d'octets au lieu de chaînes, optez pour MemoryStream en utilisant des tampons que vous allouez et effacez lorsque vous avez terminé - et vous devriez être prêt à partir.

0
ICryptoTransform decryptor = symm.CreateDecryptor(); 

if (!decryptor.CanTransformMultipleBlocks) 
    throw new InvalidOperationException(); 

// Since we're decrypting src.Length is block aligned. 
int written = decryptor.TransformBlock(src, 0, src.Length, pinnedDst, 0); 
byte[] lastBlock = decryptor.TransformFinalBlock(Array.Empty<byte>(), 0, 0); 
Buffer.BlockCopy(lastBlock, 0, pinnedDst, written, lastBlock.Length); 

Cela fait tout, sauf pour le dernier bloc n'écrire à votre mémoire épinglé - à moins que la mise en oeuvre de l'algorithme nécessaire pour utiliser un tampon interne géré. Si vous êtes dans PaddingMode.None (ou Zeros) alors je crois que TransformFinalBlock retournera le tableau vide, puisqu'il n'a pas besoin de faire un holdback depad.

+0

Je suppose que c'est un peu comme l'implémentation manuelle de 'CryptoStream.Write()', que j'espérais éviter, mais comme j'ai tout centralisé dans un endroit, je peux l'essayer. Merci! –