domenica 24 marzo 2024

Crittografia e WCF per passare una password ( od una qualsiasi altra stringa (xml,json, etc.etc.) ) da un applicazione ad un' altra in relativa sicurezza

 Il codice che segue è da considerarsi in alpha e da non utilizzare in un ambiente di produzione , qui potete trovare il  "progetto" https://sourceforge.net/projects/sourcecodesafer/

L'ipotetico scenario è quello in cui vogliamo lasciar testare la nostra applicazione .Net Windows Forms ma vogliamo evitare che sia possibile decompilarla in fase di test ,

per ottenere questo risultato crittografiamo sul nostro pc l'eseguibile da proteggere con una password (chiamiamola P , si tratta di una password simmetrica(https://it.wikipedia.org/wiki/Crittografia_simmetrica) ) ,quando ci spostiamo sulla macchina di test decrittografiamo l'eseguibile con la stessa password e lo carichiamo ed eseguiamo grazie alla reflection ,

sulla macchina di test portiamo quindi l'eseguibile crittografato , se  ci venisse carpita la password P sulla macchina di test si potrebbe decrittografare l'eseguibile e risalire al codice sorgente ,per evitare che ciò accada dobbiamo evitare che possibili keylogger intercettino lo stream della tastiera o/e che uno screen recorder registri la nostra attività sulla tastiera virtuale dell'applicazione , per risolvere questa situazione potremmo utilizzare una seconda applicazione che in remoto fornisca la password P tramite un servizio WCF ( questa è ovviamente solo una delle soluzioni possibili ). Chiamando questo servizio forniamo una password asimmetrica pubblica ( https://it.wikipedia.org/wiki/Crittografia_asimmetrica ) , quest'ultima password viene utilizzata per crittografare la password P , l'array della password così crittografata viene restituito al chiamante il quale ha la chiave privata e può risalire alla password P in chiaro .

Questo "progetto" è costituito da 3 sotto-progetti :

1) un eseguibile Ethical_Hacking.exe che è l'applicazione che vogliamo non sia decompilata in fase di test

2) un applicazione WindowsFormsProtector che si occupa di :

   a) crittografare Ethical_Hacking.exe

   b) decrittografare Ethical_Hacking.exe

   c) creare una coppia di chiavi asimmetriche e chiamare il servizio WCF di  PasswordSupplier passando la chiave pubblica ed ottenendo da PasswordSupplier  la password P crittografata e decrittografarla con la chiave privata quando è ritornata dal metodo GetPassword della classe Service di PasswordSupplier      

   d) caricare in memoria ed eseguire Ethical_Hacking.exe partendo da un array di bytes ( ovvero Ethical_Hacking.exe decrittografato )

 3)  l'applicazione PasswordSupplier che si occupa di :

   a) scegliere l'indirizzo a cui sarà reso disponibile il servizio WCF

   b) consentire di inserire la password ,dovrà essere la stessa inserita al punto 2)a)

   c) rendere disponibile (Start) il servizio all'indirizzo scelto in 3)a)

   d) fermare (Stop) il servizio avviato in 3)c)

   e) fornire l'implementazione di GetPassword  prevista dall'interfaccia IService


Segue il dettaglio dei singoli punti :

1) Ethical_Hacking.exe è un eseguibile Windows Forms si tratta di un form che ne chiama un altro,la sua funzione è solo quella di essere un eseguibile .net chiamabile tramite reflection 

2) vediamo il codice relativo al punto 2)a)

    private void button_encrypt_Click(object sender, EventArgs e)
        {
            try { 
            byte[] encrypted = null;
            string file = this.textBox1.Text;
            string pwd = this.textBox2.Text;
            if (!file.Equals(""))
            {
                    if (pwd.Length > 7)
                    {
                        FileStream fs = new FileStream(this.textBox1.Text, FileMode.Open);
                        BinaryReader br = new BinaryReader(fs);
                        byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
                        fs.Close();
                        br.Close();
                        string EncryptionKey = pwd;
                        byte[] clearBytes = bin;
                        Aes encryptor = Aes.Create();
                        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[]
                        { 0x20,0x6e, 0x20, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x20, 0x61, 0x64, 0x76,0x65});
                        encryptor.Key = pdb.GetBytes(32);
                        encryptor.IV = pdb.GetBytes(16);
                        MemoryStream ms = new MemoryStream();
                        CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(),
                              CryptoStreamMode.Write);
                        cs.Write(clearBytes, 0, clearBytes.Length);
                        cs.Close();
                        encrypted = ms.ToArray();
                        FileStream fs1 = new FileStream(this.folderName + "\\encryptedExe.encrypt", FileMode.Create);
                        BinaryWriter br1 = new BinaryWriter(fs1);
                        br1.Write(encrypted);
                        fs1.Close();
                        br1.Close();
                    } else
                    {
                        MessageBox.Show("inserire una password di almeno 8 caratteri");
                    }       
            }          
            else {
                    MessageBox.Show("inserire il nome di un file");      
                }
            } catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

       }        


-segue il codice relativo al punto 2)b) 

                string EncryptionKey = this.textBox2.Text;
                string dir = Directory.GetCurrentDirectory();
                FileStream fs = new FileStream(this.EncryptedFile, FileMode.Open);               
                BinaryReader br = new BinaryReader(fs);
                byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
                byte[] exe = null;
                fs.Close();
                br.Close();
                using (Aes encryptor = Aes.Create())
                {
                    Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[]
                         {0x20, 0x6e, 0x20, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x20, 0x61, 0x64, 0x76,0x65 });
                    encryptor.Key = pdb.GetBytes(32);
                    encryptor.IV = pdb.GetBytes(16);
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(),
                                                   CryptoStreamMode.Write))
                        {
                            cs.Write(bin, 0, bin.Length);
                            cs.Close();
                        }
                        exe = ms.ToArray();
                    }
                }
    

l'array exe consiste dell'eseguibile decrittografato 

-passiamo al punto 2)c)

    private void GetPasswordFromRemoteAddress(object sender, EventArgs e)
        {
            try
            {
                /*https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider?view=net-7.0*/
                RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                string publicKey = rsa.ToXmlString(false); // false to get the public key   
                string address = this.textBox5.Text;                
                ChannelFactory<IService> cf = new ChannelFactory<IService>(new WebHttpBinding(), address);
                //WebHttpBehavior abilita il modello di programmazione Web per un servizio Windows Communication Foundation (WCF).
                cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
                //crea il canale di comunicazione
                IService channel = cf.CreateChannel();
                //viene invocato il metodo GetPassword sul servizio il cui indirizzo è address                
                byte[] encryptedData = channel.GetPassword(publicKey);
                //viene valorizzato decryptedData con l'array di bytes ritornati dal metodo RSADecrypt
                byte[] decryptedData = RSADecrypt(encryptedData, rsa.ExportParameters(true), true);
                UnicodeEncoding convertToString = new UnicodeEncoding();
                string str = "";
                str = convertToString.GetString(decryptedData);
                this.textBox2.Text = str;                              
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
        }

      segue il metodo RSADecrypt che utilizza la chiave privata:

      static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
        {
            try
            {
                byte[] decryptedData;
                //Create a new instance of RSACryptoServiceProvider.
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    //Import the RSA Key information. This needs
                    //to include the private key information.
                    RSA.ImportParameters(RSAKeyInfo);
                    //Decrypt the passed byte array and specify OAEP padding.  
                    //OAEP padding is only available on Microsoft Windows XP or
                    //later.  
                    decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
                }
                return decryptedData;
            }
            //Catch and display a CryptographicException  
            //using a message box
            catch (CryptographicException e)
            {                
                MessageBox.Show(e.Message.ToString());
                return null;
            }
        }

-punto 2)d)

private void button_decrypt_and_run_Click(object sender, EventArgs e)
        {
            try
            {
                string EncryptionKey = this.textBox2.Text;
                string dir = Directory.GetCurrentDirectory();
                FileStream fs = new FileStream(this.EncryptedFile, FileMode.Open);               
                BinaryReader br = new BinaryReader(fs);
                byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
                byte[] exe = null;
                fs.Close();
                br.Close();
                using (Aes encryptor = Aes.Create())
                {
                    Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[]
                         {0x20, 0x6e, 0x20, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x20, 0x61, 0x64, 0x76,0x65 });
                    encryptor.Key = pdb.GetBytes(32);
                    encryptor.IV = pdb.GetBytes(16);
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(),
                                                   CryptoStreamMode.Write))
                        {
                            cs.Write(bin, 0, bin.Length);
                            cs.Close();
                        }
                        exe = ms.ToArray();
                    }
                }
               //https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load?view=net-8.0
                Assembly assembly1 = Assembly.Load(exe);
                var programType1 = assembly1.GetTypes().FirstOrDefault(c => c.Name == "Program");
                MethodInfo method1=programType1.GetMethod("Start", BindingFlags.Public | BindingFlags.Static);
                method1.Invoke(null, new object[] { });                
            }
            catch(Exception ex)
            {
                string msg= " controllare anche che la password per la decriptazione sia corretta";
                MessageBox.Show(ex.Message.ToString() + " " + msg);                
            }
        }

per utilizzare il codice soprastante bisogna modificare il codice di Ethical_Hacking.exe aggiungendo il metodo Start :     
    
static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Start();
        }
                
        public static void Start()  
        {
            Form1 f = new Form1();
            f.ShowDialog();           
        }
    }
    
il metodo chiama il primo Form dell'applicazione (Form1) .

3)  
  a) si tratta di un textbox dove possiamo inserire un indirizzo http a cui sarà possibile trovare il servizio WCF 
 b)  come sopra si tratta di un textbox in cui inserire la password che abbiamo utilizzato al punto 2)a) 
 c) al click del button1 (Start) eseguiamo il gestore dell'evento click
         private void button1_Click(object sender, EventArgs e)
        {
                try
                {
                  string uri = this.textBox1.Text;                    
                  string s = "";
                  s = this.textBox2.Text;                    
                  this.password = s;
                  //WebServiceHost è una classe derivata da ServiceHost che integra il modello di programmazione REST
                  //di Windows Communication Foundation (WCF).
                  //https://learn.microsoft.com/it-it/dotnet/api/system.servicemodel.web.webservicehost?view=netframework-4.8
                  host = new WebServiceHost(typeof(Service), new Uri(uri));                       
                  host.Open();               
                } catch(Exception ex)
                {
                   MessageBox.Show(ex.Message.ToString());
                }
        } 

d) al click del button2 (Stop) 
         private void button2_Click(object sender, EventArgs e)
        {
            try { 
                host.Close();
            }catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

e) segue la classe service che implementa IService 
public class Service : IService
    {        
        public byte[] GetPassword(string publicKey)
        {
             try {                
                byte[] encryptedData = null;
                Form1 f = (Form1)Application.OpenForms["Form1"];
                string text = f.password;
                UnicodeEncoding byteConverter = new UnicodeEncoding();
                byte[] dataToEncrypt = byteConverter.GetBytes(text);

                //load the encryptedData variable with the return of the RSACryptoServiceProvider encrypt method
                using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
                {
                    // load public key from publicKey parameter ,publicKey is a valid xml    
                    rsa.FromXmlString(publicKey);
                    // Encrypt the data and store it in the encyptedData bytes array                       
                    encryptedData = rsa.Encrypt(dataToEncrypt, true);
                }
                return encryptedData;
            } catch(Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            } 
            return null;
        }       
    }
 
sia WindowsFormsProtector che PasswordSupplier possiedono ed utilizzano la seguente interfaccia :

//https://learn.microsoft.com/en-us/dotnet/framework/wcf/designing-service-contracts
    //https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.servicecontractattribute?view=dotnet-plat-ext-7.0
    [ServiceContract]
    public interface IService
    {
        //https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.operationcontractattribute?view=dotnet-plat-ext-7.0
        [OperationContract]
        //https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.web.webgetattribute?view=netframework-4.8.1
        [WebGet]
        byte[] GetPassword(string s);       

    }

Per suggerimenti ,critiche o segnalazione errori potete inviare un email a gianmarco.castagna@gmail.com

Crittografia e WCF per passare una password ( od una qualsiasi altra stringa (xml,json, etc.etc.) ) da un applicazione ad un' altra in relativa sicurezza

 Il codice che segue è da considerarsi in alpha e da non utilizzare in un ambiente di produzione , qui potete trovare il  "progetto&quo...