Classe pour crypter/décrypter simplement une chaîne de caractères

Soyez le premier à donner votre avis sur cette source.

Snippet vu 33 381 fois - Téléchargée 32 fois

Contenu du snippet

Cette classe permet de crypter SIMPLEMENT et de façon la plus sécurisée qui soit une chaine de caractères pour la stocker dans un fichier de config par exemple. Ce code utilise l'API Win32 DPAPI (Data Protection API) disponible depuis Windows 2000 SP3.

Exemple d'utilisation :

CryptoUtil c = new CryptoUtil(CryptoUtil.Store.USE_MACHINE_STORE);
string chaineCryptee = c.Encrypt("la chaine à crypter", "ma clé en plus pas obligatoire");
string chaineOrigine = c.Decrypt(chaineCryptee, "ma clé en plus pas obligatoire");

ou bien :
CryptoUtil c = new CryptoUtil(CryptoUtil.Store.USE_USER_STORE);
string chaineCryptee = c.Encrypt("la chaine à crypter");
string chaineOrigine = c.Decrypt(chaineCryptee);

Source / Exemple :


/// <summary>
/// Utilitaire de cryptage / décryptage de chaines
/// Adaptation d'un article de Microsoft Patterns & Practices
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secmod/html/secmod21.asp
/// </summary>
public class CryptoUtil
{
	#region Plomberie PInvokes

	// imports
	[DllImport("Crypt32.dll", SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
	private static extern bool CryptProtectData(
		ref DATA_BLOB pDataIn, 
		String szDataDescr, 
		ref DATA_BLOB pOptionalEntropy,
		IntPtr pvReserved, 
		ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, 
		int dwFlags, 
		ref DATA_BLOB pDataOut);
	[DllImport("Crypt32.dll", SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
	private static extern bool CryptUnprotectData(
		ref DATA_BLOB pDataIn, 
		String szDataDescr, 
		ref DATA_BLOB pOptionalEntropy, 
		IntPtr pvReserved, 
		ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, 
		int dwFlags, 
		ref DATA_BLOB pDataOut);
	[DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
	private unsafe static extern int FormatMessage(int dwFlags, 
		ref IntPtr lpSource, 
		int dwMessageId,
		int dwLanguageId, 
		ref String lpBuffer, int nSize, 
		IntPtr *Arguments);

	// structures
	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
	internal struct DATA_BLOB
	{
		public int cbData;
		public IntPtr pbData;
	}

	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
	internal struct CRYPTPROTECT_PROMPTSTRUCT
	{
		public int cbSize;
		public int dwPromptFlags;
		public IntPtr hwndApp;
		public String szPrompt;
	}

	// constantes
	private static IntPtr NullPtr = ((IntPtr)((int)(0)));
	private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
	private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;

	private void InitPromptstruct(ref CRYPTPROTECT_PROMPTSTRUCT ps) 
	{
		ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT));
		ps.dwPromptFlags = 0;
		ps.hwndApp = NullPtr;
		ps.szPrompt = null;
	}

	private unsafe static String GetErrorMessage(int errorCode)
	{
		int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
		int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
		int FORMAT_MESSAGE_FROM_SYSTEM  = 0x00001000;
		int messageSize = 255;
		String lpMsgBuf = "";
		int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | 
			FORMAT_MESSAGE_IGNORE_INSERTS;
		IntPtr ptrlpSource = new IntPtr();
		IntPtr prtArguments = new IntPtr();
		int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0, 
			ref lpMsgBuf, messageSize, &prtArguments);
		if(0 == retVal)
		{
			throw new Exception("Failed to format message for error code " + 
				errorCode + ". ");
		}
		return lpMsgBuf;
	}

	#endregion

	// enums
	public enum Store 
	{
		USE_MACHINE_STORE = 1, 
		USE_USER_STORE
	}

	// variables membres
	private Store m_store;

	/// <summary>
	/// Constructeur
	/// </summary>
	public CryptoUtil(Store store)
	{
		m_store = store;
	}

	/// <summary>
	/// Crypte un texte, renvoie le résultat sous forme d'une chaine base 64, facile à stocker dans un fichier de config par exemple
	/// </summary>
	public string Encrypt(string plainText)
	{
		return this.Encrypt(plainText, "");
	}

	/// <summary>
	/// Décrypte une chaine base 64 renvoie la chaine d'origine
	/// </summary>
	public string Decrypt(string base64String)
	{
		return this.Decrypt(base64String);
	}

	/// <summary>
	/// Crypte un texte, renvoie le résultat sous forme d'une chaine base 64, facile à stocker dans un fichier de config par exemple
	/// Note : le paramètre optionalEntropy n'a aucun effet en mode User
	/// </summary>
	public string Encrypt(string plainText, string optionalEntropy)
	{
		byte[] result = this.Encrypt(Encoding.Default.GetBytes(plainText), Encoding.Default.GetBytes(optionalEntropy));
		return Convert.ToBase64String(result);
	}

	/// <summary>
	/// Décrypte une chaine base 64 renvoie la chaine d'origine
	/// Note : le paramètre optionalEntropy n'a aucun effet en mode User
	/// </summary>
	public string Decrypt(string base64String, string optionalEntropy)
	{
		byte[] bytes = Convert.FromBase64String(base64String);
		byte[] result = this.Decrypt(bytes, Encoding.Default.GetBytes(optionalEntropy));
		return Encoding.Default.GetString(result);
	}

	public byte[] Encrypt(byte[] plainText, byte[] optionalEntropy)
	{
		bool retVal = false;

		DATA_BLOB plainTextBlob = new DATA_BLOB();
		DATA_BLOB cipherTextBlob = new DATA_BLOB();
		DATA_BLOB entropyBlob = new DATA_BLOB();

		CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT();
		InitPromptstruct(ref prompt);

		int dwFlags;
		try
		{
			try
			{
				int bytesSize = plainText.Length;
				plainTextBlob.pbData = Marshal.AllocHGlobal(bytesSize);
				if(IntPtr.Zero == plainTextBlob.pbData)
				{
					throw new Exception("Unable to allocate plaintext buffer");
				}
				plainTextBlob.cbData = bytesSize;
				Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize);
			}
			catch(Exception ex)
			{
				throw new Exception("Exception marshalling data. " + ex.Message);
			}
			if(Store.USE_MACHINE_STORE == m_store)
			{
				//Using the machine store, should be providing entropy.
				dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
				//Check to see if the entropy is null
				if(null == optionalEntropy)
				{
					//Allocate something
					optionalEntropy = new byte[0];
				}
				try
				{
					int bytesSize = optionalEntropy.Length;
					entropyBlob.pbData = Marshal.AllocHGlobal(optionalEntropy.Length);;
					if(IntPtr.Zero == entropyBlob.pbData)
					{
						throw new Exception("Unable to allocate entropy data buffer");
					}
					Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
					entropyBlob.cbData = bytesSize;
				}
				catch(Exception ex)
				{
					throw new Exception("Exception entropy marshalling data. " + 
						ex.Message);
				}
			}
			else
			{
				//Using the user store
				dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
			}
			retVal = CryptProtectData(ref plainTextBlob, "", ref entropyBlob, 
				IntPtr.Zero, ref prompt, dwFlags, 
				ref cipherTextBlob);
			if(false == retVal)
			{
				throw new Exception("Encryption failed. " + 
					GetErrorMessage(Marshal.GetLastWin32Error()));
			}
		}
		catch(Exception ex)
		{
			throw new Exception("Exception encrypting. " + ex.Message);
		}
		byte[] cipherText = new byte[cipherTextBlob.cbData];
		Marshal.Copy(cipherTextBlob.pbData, cipherText, 0, cipherTextBlob.cbData);
		return cipherText;
	}

	public byte[] Decrypt(byte[] cipherText, byte[] optionalEntropy)
	{
		bool retVal = false;
		DATA_BLOB plainTextBlob = new DATA_BLOB();
		DATA_BLOB cipherBlob = new DATA_BLOB();
		CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT();
		InitPromptstruct(ref prompt);
		try
		{
			try
			{
				int cipherTextSize = cipherText.Length;
				cipherBlob.pbData = Marshal.AllocHGlobal(cipherTextSize);
				if(IntPtr.Zero == cipherBlob.pbData)
				{
					throw new Exception("Unable to allocate cipherText buffer");
				}
				cipherBlob.cbData = cipherTextSize;  
				Marshal.Copy(cipherText, 0, cipherBlob.pbData, cipherBlob.cbData);
			}
			catch(Exception ex)
			{
				throw new Exception("Exception marshalling data. " + ex.Message);
			}
			DATA_BLOB entropyBlob = new DATA_BLOB();
			int dwFlags;
			if(Store.USE_MACHINE_STORE == m_store)
			{
				//Using the machine store, should be providing entropy.
				dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
				//Check to see if the entropy is null
				if(null == optionalEntropy)
				{
					//Allocate something
					optionalEntropy = new byte[0];
				}
				try
				{
					int bytesSize = optionalEntropy.Length;
					entropyBlob.pbData = Marshal.AllocHGlobal(bytesSize);
					if(IntPtr.Zero == entropyBlob.pbData)
					{
						throw new Exception("Unable to allocate entropy buffer");
					}
					entropyBlob.cbData = bytesSize;
					Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
				}
				catch(Exception ex)
				{
					throw new Exception("Exception entropy marshalling data. " + 
						ex.Message);
				}
			}
			else
			{
				//Using the user store
				dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
			}
			retVal = CryptUnprotectData(ref cipherBlob, null, ref entropyBlob, 
				IntPtr.Zero, ref prompt, dwFlags, 
				ref plainTextBlob);
			if(false == retVal)
			{
				throw new Exception("Decryption failed. " + 
					GetErrorMessage(Marshal.GetLastWin32Error()));
			}
			//Free the blob and entropy.
			if(IntPtr.Zero != cipherBlob.pbData)
			{
				Marshal.FreeHGlobal(cipherBlob.pbData);
			}
			if(IntPtr.Zero != entropyBlob.pbData)
			{
				Marshal.FreeHGlobal(entropyBlob.pbData);
			}
		}
		catch(Exception ex)
		{
			throw new Exception("Exception decrypting. " + ex.Message);
		}
		byte[] plainText = new byte[plainTextBlob.cbData];
		Marshal.Copy(plainTextBlob.pbData, plainText, 0, plainTextBlob.cbData);
		return plainText;
	}
}

Conclusion :


Il y a deux mode de fonctionnement :

- Un mode ou on crypte pour la machine. Seuls les utilisateurs de la machine en cours peuvent décrypter la chaine. Dans ce cas, il est conseillé de passer le paramètre nommé optionnalEntropy au cryptage et au décryptage. Ce paramètre permet de compliquer un peu plus de cryptage. Ce mode est conseillé pour l'ASP.NET.

- Un mode ou on crypte pour l'utilisateur en cours. Dans ce cas, les données sont propres à l'utilisateur. Le paramètre optionnalEntropy est purement ignoré.

ATTENTION : ce source utilise des blocs de code UNSAFE, pensez à autoriser le code unsafe à la compilation.

A voir également

Ajouter un commentaire

Commentaires

cs_Warny
Messages postés
478
Date d'inscription
mercredi 7 août 2002
Statut
Membre
Dernière intervention
10 juin 2015
-
Tient, voila quelqu'un de bien qui ne cherche pas à réinventer la poudre.
Les deux failles de ton système sont :
- DES est quelque peu dépassé par la puissance des machines. Utilise plutot DES-3 ou AES pour crypter
-Ta clef comme tu le remarques toi-même n'est pas protégée. Utilise un cryptage asymétriqe (RSA) pour ça (tu as aussi les classes dans le framework)

Pour l'envoi d'un message :
- tu généres une clef symétrique (et totalement aléatoire)
- tu cryptes symétriquement (DES) ton message avec
- tu cryptes la clef symétrique avec la clef publique de ton correspondant (RSA)
- tu agrèges dans le message la clef symétrique crypté et le message crypté
- tu envoie le message crypté

Pour la réception
- tu extrais la clef symétrique crypté
- tu la décryptes avec la clef privée
- tu décryptes le message avec la clef symétrique décryptée
- tu peux lire ton message en clair...

Ce protocole de communication est celui qui s'applique dans PGP. On utilise ce protocole parce que c'est beaucoup trop long d'utiliser RSA sur tout le message, mais ça provoque deux points d'attaques possible. Ce qui ne veut pas dire que c'est facile à attaquer.

DES-3 ou triple DES c'est DES trois fois de suite avec 3 clefs différentes, comme tu n'utilise que 56bits de ton aglomération SHA pour faire un DES, utilise les bits suivants comme clefs pour les deuxième et troisième passages

Bonne programmation
nseveno
Messages postés
21
Date d'inscription
mardi 2 avril 2002
Statut
Membre
Dernière intervention
12 juin 2008
-
Merci pour tes précieux conseils Warny. C'était autant pour le FeedBack que pour partager que j'ai posté ce source. Et là je suis comblé pour la partie FeedBack :-)

Un collègue m'a déjà expliqué ce système de crypter la clé. Je posterais une nouvelle version de ma classe dès que j'aurais mis tout ça au propre...
cs_Warny
Messages postés
478
Date d'inscription
mercredi 7 août 2002
Statut
Membre
Dernière intervention
10 juin 2015
-
Bien joué pour la protection de la clef. Pour la petite histoire, dans le cas du cryptage utilisateur, si vous changez le mot de passe de l'utilisateur depuis la console d'administration, la clef est irrécupérable. Ceci garantie l'impossibilité du vol des données par un administrateur. c'est pour cette raison que l'option d'entropie est ignorée : elle est inutile.
Je crois que la classe protectedstorage du framework permet d'écrire dans le fichier crypté utilisateur sans passer explicitement par les API windows.
nseveno
Messages postés
21
Date d'inscription
mardi 2 avril 2002
Statut
Membre
Dernière intervention
12 juin 2008
-
Merci pour ces compléments sur le mot de passe, je comprends mieux l'histoire de l'entropie maintenant.

Pour ce qui est de la classe ProtectedStorage, je ne la trouve pas. Peut être sera-t elle incluse dans le FW 2.0 ? De toutes façons ça je ne pense pas qu'il y ait une classe pour faire ça dans le FW 1.1, sinon ils ne se seraient pas fatigués à écrire un article complet sur le sujet sur MSDN Patterns & Practices...
cs_RJMS
Messages postés
6
Date d'inscription
mardi 15 mars 2005
Statut
Membre
Dernière intervention
6 novembre 2007
-
Bonjour !

J'obtiens enu StackOverFlow Exception quand je tente de décrypter une chaîne cryptée avec l'autre méthode (encrypt)

L'exception se passe au niveau de cette méthode (base64String)

public string Decrypt(string base64String)
{
return this.Decrypt(base64String);
}
Y aurait-il une explication et un moyen de contourner ceci ?
Merci !

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.