Press "Enter" to skip to content

Common Libraries – Due metodi helper per la crittografia AES

In risposta a Questo thread postato oggi sul forum Microsoft C# in cui un utente vuole implementare una chat crittografata utilizzando il metodo AES e ha trovato delle difficoltà.

Rispondo pubblicando un esempio di codice con i metodi che utilizzo io per questo tipo di crittografia.

L’Esempio

Per l’esempio ho creato una nuova applicazione WPF e nella MainWindow ho implementato quanto segue:

Lo XAML

<Window x:Class="ClientEncryptDecrypt.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"
        xmlns:local="clr-namespace:ClientEncryptDecrypt"
        mc:Ignorable="d"
        Title="Encrypt And Decrypt" Height="350" Width="525">

prima di tutto Diamo un titolo alla finestra.

<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="Auto"/>
		<RowDefinition Height="Auto"/>
		<RowDefinition Height="50*"/>
		<RowDefinition Height="50*"/>
		<RowDefinition Height="Auto"/>
	</Grid.RowDefinitions>
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="Auto"/>
		<ColumnDefinition Width="*"/>
	</Grid.ColumnDefinitions>

Inseriamo una struttura ove mettere i nostri controlli.

<TextBlock 
	Margin="4,2,4,2"
	Grid.Column="0"
	Grid.Row="0"
	Text="Password"/>
<PasswordBox
	Margin="4,2,4,2"
	Grid.Column="1"
	Grid.Row="0"
	HorizontalAlignment="Stretch"
	VerticalAlignment="Center"
	Name="pwd"/>

Prima Riga: etichetta più PasswordBox per contenere la password.

<TextBlock 
	Margin="4,2,4,2"
	Grid.Column="0"
	Grid.Row="1"
	Text="Clear Text"/>
<TextBox
	Margin="4,2,4,2"
	Grid.Column="1"
	Grid.Row="1"
	HorizontalAlignment="Stretch"
	VerticalAlignment="Center"
	Text="{Binding ClearText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>

Seconda riga: Una textbox per il testo in chiaro.

<TextBlock 
	Margin="4,2,4,2"
	Grid.Column="0"
	Grid.Row="2"
	Text="Encrypted Text"/>
<TextBox
	Margin="4,2,4,2"
	Grid.Column="1"
	Grid.Row="2"
	HorizontalAlignment="Stretch"
	VerticalAlignment="Stretch"
	TextWrapping="Wrap"
	VerticalScrollBarVisibility="Auto"
	Text="{Binding Encrypted, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>

Terza riga: Una textbox per il testo crittografato.

<TextBlock 
	Margin="4,2,4,2"
	Grid.Column="0"
	Grid.Row="3"
	Text="Decrypted Text"/>
<TextBox
	Margin="4,2,4,2"
	Grid.Column="1"
	Grid.Row="3"
	HorizontalAlignment="Stretch"
	VerticalAlignment="Stretch"
	TextWrapping="Wrap"
	VerticalScrollBarVisibility="Auto"
	Text="{Binding Decrypted, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>

Quarta riga: Una textbox per mostrare il testo decrittografato.

<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft" Grid.Row="4" Grid.ColumnSpan="2">
	<Button Content="Send" Click="Send_click" Margin="4,2,4,2" Padding="10,4,10,4"/>
</StackPanel>

Ultima riga, un pannello che contiene l’unico button che ci serve.

Da notare solo come, ho effettuato il Binding alle property utilizzando UpdateSourceTrigger=PropertyChanged se dovete aggiornare dati in tempo reale, fate sempre in modo di usare questa opzione per l’aggiornamento.

La window risultante è la seguente:

mainwindow01

Il codice

public MainWindow()
{
	InitializeComponent();
	this.DataContext = this;
}

Nel costruttore ci limitiamo ad agganciare la finestra facendola divenire view model di se stessa.

public string ClearText
{
	get
	{
		return (string)this.GetValue(ClearTextProperty);
	}
	set
	{
		this.SetValue(ClearTextProperty, value);
	}
}
 
public string Decrypted
{
	get
	{
		return (string)this.GetValue(DecryptedProperty);
	}
	set
	{
		this.SetValue(DecryptedProperty, value);
	}
}
 
public string Encrypted
{
	get
	{
		return (string)this.GetValue(EncryptedProperty);
	}
	set
	{
		this.SetValue(EncryptedProperty, value);
	}
}

Le tre Dependency property utilizzate per fornire i dati alle textbox.

private void Send_click(object sender, RoutedEventArgs e)
{
	Encrypted = Encrypt(ClearText, pwd.Password);
	Decrypted = Decrypt(Encrypted, pwd.Password);
}

L’event handler del click del bottone che crittografa e poi decrittografa la stringa utilizzando la nostra password.

Ed arriviamo alla parte succosa, i metodi di crittografia e decrittografia:

private string Encrypt(string visibleData, string password)
{
	try
	{
		byte[] data = Encoding.Unicode.GetBytes(visibleData);
		PasswordDeriveBytes hiddenNumbers = new PasswordDeriveBytes(password, new byte[] { 73, 118, 97, 110, 32, 77, 101, 100, 118, 101, 100, 101, 118 });
		byte[] dataBytes = Encrypt(data, hiddenNumbers.GetBytes(32), hiddenNumbers.GetBytes(16));
		if (dataBytes != null)
		{
			return Convert.ToBase64String(dataBytes);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
	return null;
}

Per crittografare una stringa è innanzi tutto necessario trasformarla in un array di byte cosa che si effettua utilizzando Getbytes di Encoding, come vedete ho usato l’Unicode in modo che funzioni con qualsiasi tipo di stringa.

Dopo di ciò, ci serve una chiave che viene creata componendo la password e una serie di byte chiamati “Salt” sale nel gergo della crittografia, possono essere una qualsiasi serie di numeri, nel mio caso si tratta dei valori ascii di una stringa la cosa importante da ricordare è che devono essere identici nella funzione di crittografia e in quella di decrittografia.

Ottenuti i due dati precedenti, li passiamo alla funzione di crittografia specifica (è stata separata perché volendo crittografare un file, la preparazione è diversa ma la parte di crittografia è identica. Ottenuto il byte array di dati crittografati, li convertiamo in stringa utilizzando il Convert.ToBase64String per restituirla alla nostra funzione chiamante.

private byte[] Encrypt(byte[] visibleData, byte[] key, byte[] iv)
{
	using (MemoryStream memoryStream = new MemoryStream())
	{
		try
		{
			Rijndael dataKey = Rijndael.Create();
			dataKey.Key = key;
			dataKey.IV = iv;
			dataKey.Padding = PaddingMode.PKCS7;
 
			CryptoStream cryptoStream = new CryptoStream(memoryStream, dataKey.CreateEncryptor(), CryptoStreamMode.Write);
			cryptoStream.Write(visibleData, 0, (int)visibleData.Length);
			cryptoStream.Close();
			byte[] array = memoryStream.ToArray();
			return array;
		}
		catch (Exception ex)
		{
			MessageBox.Show(ex.Message);
			return null;
		}
	}
}

Questa è la funzione che effettua la crittografia reale utilizzando la classe CryptoStream e i dati che gli abbiamo passato per produrre un array di byte crittografato.

private string Decrypt(string hiddenData, string password)
{
	try
	{
		byte[] dataOne = Convert.FromBase64String(hiddenData);
		PasswordDeriveBytes hiddenNumbers = new PasswordDeriveBytes(password, new byte[] { 73, 118, 97, 110, 32, 77, 101, 100, 118, 101, 100, 101, 118 });
		byte[] dataTwo = Decrypt(dataOne, hiddenNumbers.GetBytes(32), hiddenNumbers.GetBytes(16));
		if (dataTwo != null)
		{
			return Encoding.Unicode.GetString(dataTwo);
		}
		return null;
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		return null;
	}
}

Per decrittografare quanto prodotto in precedenza, lavoriamo a rovescio, quindi creiamo un byte array dalla stringa in base64.

Rigeneriamo la chiave per la decrittografia partendo dalla stessa password e dalla stessa serie di numeri, attenzione se cambiate qualcosa qui, non decrittografate nulla.

Usiamo la funzione base di decrittografia e rigeneriamo la stringa unicode a partire dai dati decrittografati.

public static byte[] Decrypt(byte[] hiddenData, byte[] key, byte[] iv)
{
 
	using (MemoryStream memData = new MemoryStream())
	{
		try
		{
			Rijndael keyData = Rijndael.Create();
			keyData.Key = key;
			keyData.IV = iv;
			keyData.Padding = PaddingMode.PKCS7;
			CryptoStream hstream = new CryptoStream(memData, keyData.CreateDecryptor(), CryptoStreamMode.Write);
			hstream.Write(hiddenData, 0, (int)hiddenData.Length);
			hstream.Close();
			byte[] array = memData.ToArray();
			return array;
		}
		catch (Exception ex)
		{
			MessageBox.Show(ex.Message);
			return null;
		}
 
	}
}

La funzione di decrittografia, parte dal byte array crittografato e utilizza l’array di byte crittografato, e la chiave generata dalla password e dal “Salt” per effettuare l’operazione contraria restituendo i dati decrittografati.

Cosa ricordare di tutto questo

  1. Se crittografate dei dati con una password sbagliata non li recuperate più.
  2. La password e i byte utilizzati per il SALT devono essere identici sia in crittografia che in decrittografia oppure non recuperate i dati.
  3. Come illustrato dall’immagine qui sotto, una stringa crittografata è molto più grande della stringa originale.

mainwindow02

Potete scaricare il progetto esempio dal link qui indicato:

Per qualsiasi domanda, approfondimento, commento, o per segnalare un errore potete usare il link al modulo di contatto in cima alla pagina.