segunda-feira, 19 de maio de 2014

O problema das Cifras... E a dor de cabeça que levou uma noite a resolver!

Recentemente num projecto em que estive a trabalhar, tive de cifrar uma quantidade considerável de dados. A primeira coisa que me surgiu na mente foi usar hash, mas a necessidade de reverter a string cifrada para plain-text invalidou essa ideia.

Foi por essa altura que pensei "bem isto era porreiro encriptar em AES!", mas aí surgiu-me uma outra questão: Como o fazer ? Como gerar key-rings para cada registo ? Depois de indagar umas horas, acabei adoptando uma forma de encriptar registos e desencripar os mesmos de forma simples e quase instantânea, usando key-rings diferentes para cada registo.  

Problemas: 
  • Não repetir key-rings
  • Evitar paterns
  • Encriptar quantidades massivas de dados 
  • Fazê-lo de forma minimamente robusta 
A solução:

Umas pesquisas na web, não resultaram em grande coisa. Ou encontrava implementações que não funcionávam, ou tinham memory leaks, e nenhuma delas fazia tudo o que eu precisava.

Chegado a esta conclusão "volta-se ao quadro e desenha-se"! Depois de muitos rabiscos, lá começou a tomar forma aquilo que eu pretendia.

Uma classe que gera-se strings aleatórias de tamanho definido no construtor, e uma classe que suporta-se a encriptação, AES usando algoritmo Rijndael. Nesta faze decidi colocar a private key hardcoded na app, uma vez que o meu objectivo não se concentrava em proteger a app, mas proteger os dados, e o elo mais fraco seria o servidor de base de dados.


Neste caso a classe que encriptaria os dados é a seguinte:

Class: EncryptStringDino 
 


 using System;  
 using System.Text;  
 using System.Security.Cryptography;  
 using System.IO;  
   
 namespace EncryptStringDino  
 {  
   public static class StringCipher  
   {  
     private const string initVector = "EfK7yhF3HKywfvXp";  
   
     private const int keysize = 256;  
   
     //sumary  
     //metodo Encrypt  
     //exemplo: string cifrado = Encrypt(texto_a_cifrar, key)  
     //key é uma contra-senha que tanto pode ser uma constante como um valor de uma veriável  
     //devolve uma string correspondente ao texto a cifrar, cifrado recorrendo ao standard AES metodologia Rijndael  
     public static string Encrypt(string plainText, string passPhrase)  
     {  
       byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);  
       byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);  
       PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);  
       byte[] keyBytes = password.GetBytes(keysize / 8);  
       RijndaelManaged symmetricKey = new RijndaelManaged();  
       symmetricKey.Mode = CipherMode.CBC;  
       ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);  
       MemoryStream memoryStream = new MemoryStream();  
       CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);  
       cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);  
       cryptoStream.FlushFinalBlock();  
       byte[] cipherTextBytes = memoryStream.ToArray();  
       memoryStream.Close();  
       cryptoStream.Close();  
       return Convert.ToBase64String(cipherTextBytes);  
     }  
   
     //sumary  
     //metodo Dncrypt  
     //exemplo: string cifrado = Encrypt(texto_cifrado, key)  
     //key é uma contra-senha que tanto pode ser uma constante como um valor de uma veriável  
     //devolve uma string correspondente ao texto a cifrar, cifrado recorrendo ao standard AES metodologia Rijndael  
     public static string Decrypt(string cipherText, string passPhrase)  
     {  
       byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);  
       byte[] cipherTextBytes = Convert.FromBase64String(cipherText);  
       PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);  
       byte[] keyBytes = password.GetBytes(keysize / 8);  
       RijndaelManaged symmetricKey = new RijndaelManaged();  
       symmetricKey.Mode = CipherMode.CBC;  
       ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);  
       MemoryStream memoryStream = new MemoryStream(cipherTextBytes);  
       CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);  
       byte[] plainTextBytes = new byte[cipherTextBytes.Length];  
       int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);  
       memoryStream.Close();  
       cryptoStream.Close();  
       return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);  
     }  
   }  
 }  


 Class: RamdomStringGeneratorDino , que vai gerar as strings aleatórias que vão fazer parte do processo de encriptação.

Este código foi alterado diversas vezes porque causava constantemente erros e problemas com a CLR, ao ponto de ter de ser alterado de novo, quando começou a gerar exceptions quando pedido que gera-se um volume de cerca de 2000 strings. 

Class: RamdomStringGeneratorDino
  
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Security.Cryptography;  
   
 namespace RamdomStringGeneratorDino  
 {  
   /// <summary>  
   /// Classe geradora de strings aleatórias de acordo com as opções abaixo listadas  
   /// 1) 4 caracteres (maiusculo, minusculo, numerico e caracteres especiais)  
   /// 2) numero variável de caracteres em uso  
   /// 3) numero minimo de caracteres de cada tipo a serem usados na string  
   /// 4) Geração orientada a patterns  
   /// 5) geração de strings unicas  
   /// 6) usar cada caracter apenas uma vez  
   /// feito para gerar "keyt" para senhas Rjindael  
   /// </summary>  
   public class RandomStringGenerator  
   {  
     public RandomStringGenerator(bool UseUpperCaseCharacters = true,  
                    bool UseLowerCaseCharacters = true,  
                    bool UseNumericCharacters = true,  
                    bool UseSpecialCharacters = true)  
     {  
       m_UseUpperCaseCharacters = UseUpperCaseCharacters;  
       m_UseLowerCaseCharacters = UseLowerCaseCharacters;  
       m_UseNumericCharacters = UseNumericCharacters;  
       m_UseSpecialCharacters = UseSpecialCharacters;  
       CurrentGeneralCharacters = new char[0]; // evita excepções de null  
       UpperCaseCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();  
       LowerCaseCharacters = "abcdefghijklmnopqrstuvwxyz".ToCharArray();  
       NumericCharacters = "0123456789".ToCharArray();  
       SpecialCharacters = ",.;:?!/@#$%^&()=+*-_{}[]<>|~".ToCharArray();  
       MinUpperCaseCharacters = MinLowerCaseCharacters = MinNumericCharacters = MinSpecialCharacters = 0;  
       RepeatCharacters = true;  
       PatternDriven = false;  
       Pattern = "";  
       Random = new RNGCryptoServiceProvider();  
       ExistingStrings = new List<string>();  
     }  
   
     #region character sets managers  
     /// <summary>  
     /// True se precisar-mos de um numero de caracteres fixo  
     /// </summary>  
     public bool UseUpperCaseCharacters  
     {  
       get  
       {  
         return m_UseUpperCaseCharacters;  
       }  
       set  
       {  
         if (CurrentUpperCaseCharacters != null)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentUpperCaseCharacters).ToArray();  
         if (value)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(CurrentUpperCaseCharacters).ToArray();  
         m_UseUpperCaseCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// define ou obter a definição de caracteres maiusculos.  
     /// </summary>  
     public char[] UpperCaseCharacters  
     {  
       get  
       {  
         return CurrentUpperCaseCharacters;  
       }  
       set  
       {  
         if (UseUpperCaseCharacters)  
         {  
           if (CurrentUpperCaseCharacters != null)  
             CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentUpperCaseCharacters).ToArray();  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(value).ToArray();  
         }  
         CurrentUpperCaseCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// True se é obritatório o uso de minusculas  
     /// </summary>  
     public bool UseLowerCaseCharacters  
     {  
       get  
       {  
         return m_UseLowerCaseCharacters;  
       }  
       set  
       {  
         if (CurrentLowerCaseCharacters != null)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentLowerCaseCharacters).ToArray();  
         if (value)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(CurrentLowerCaseCharacters).ToArray();  
         m_UseLowerCaseCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// define ou obtem a definição de uso de caracteres minusculos  
     /// </summary>  
     public char[] LowerCaseCharacters  
     {  
       get  
       {  
         return CurrentLowerCaseCharacters;  
       }  
       set  
       {  
         if (UseLowerCaseCharacters)  
         {  
           if (CurrentLowerCaseCharacters != null)  
             CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentLowerCaseCharacters).ToArray();  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(value).ToArray();  
         }  
         CurrentLowerCaseCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// True se é necessário o uso de caracteres numéricos  
     /// </summary>  
     public bool UseNumericCharacters  
     {  
       get  
       {  
         return m_UseNumericCharacters;  
       }  
       set  
       {  
         if (CurrentNumericCharacters != null)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentNumericCharacters).ToArray();  
         if (value)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(CurrentNumericCharacters).ToArray();  
         m_UseNumericCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// define ou obtem a definição de uso de caracteres numéricos  
     /// </summary>  
     public char[] NumericCharacters  
     {  
       get  
       {  
         return CurrentNumericCharacters;  
       }  
       set  
       {  
         if (UseNumericCharacters)  
         {  
           if (CurrentNumericCharacters != null)  
             CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentNumericCharacters).ToArray();  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(value).ToArray();  
         }  
         CurrentNumericCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// True se é para usar caracteres especiais  
     /// </summary>  
     public bool UseSpecialCharacters  
     {  
       get  
       {  
         return m_UseSpecialCharacters;  
       }  
       set  
       {  
         if (CurrentSpecialCharacters != null)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentSpecialCharacters).ToArray();  
         if (value)  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(CurrentSpecialCharacters).ToArray();  
         m_UseSpecialCharacters = value;  
       }  
     }  
   
     /// <summary>  
     /// define ou obtem a definição de uso de caracteres especiais  
     /// </summary>  
     public char[] SpecialCharacters  
     {  
       get  
       {  
         return CurrentSpecialCharacters;  
       }  
       set  
       {  
         if (UseSpecialCharacters)  
         {  
           if (CurrentSpecialCharacters != null)  
             CurrentGeneralCharacters = CurrentGeneralCharacters.Except(CurrentSpecialCharacters).ToArray();  
           CurrentGeneralCharacters = CurrentGeneralCharacters.Concat(value).ToArray();  
         }  
         CurrentSpecialCharacters = value;  
       }  
     }  
     #endregion  
   
     #region character limits  
     /// <summary>  
     /// Define ou obtem o numero minumo de caracteres maiusculos a serem usados.  
     /// </summary>  
     public int MinUpperCaseCharacters  
     {  
       get { return m_MinUpperCaseCharacters; }  
       set { m_MinUpperCaseCharacters = value; }  
     }  
   
     /// <summary>  
     /// Define ou obtem o numero minimo de caracteres minusculos a serem usados.  
     /// </summary>  
     public int MinLowerCaseCharacters  
     {  
       get { return m_MinLowerCaseCharacters; }  
       set { m_MinLowerCaseCharacters = value; }  
     }  
   
     /// <summary>  
     /// define ou obtem o numero minimo de caracteres numéricos a sere utilizados.  
     /// </summary>  
     public int MinNumericCharacters  
     {  
       get { return m_MinNumericCharacters; }  
       set { m_MinNumericCharacters = value; }  
     }  
   
     /// <summary>  
     /// define ou obtem o numero minimo de caracteres especiais a serem utilizados.  
     /// </summary>  
     public int MinSpecialCharacters  
     {  
       get { return m_MinSpecialCharacters; }  
       set { m_MinSpecialCharacters = value; }  
     }  
     #endregion  
   
     #region pattern  
     private string m_pattern;  
   
     /// <summary>  
     /// Define o padrão a ser seguido para gerar uma string.   
     /// Este valor é ignorado se for igual string vazia.   
     /// Os padrões são:   
     /// L - para letra maiúscula   
     /// L - para letra minúscula   
     /// N - de número   
     /// S - para caractere especial   
     /// * - Para qualquer caractere  
     /// </summary>  
     private string Pattern  
     {  
       get  
       {  
         return m_pattern;  
       }  
       set  
       {  
         if (!value.Equals(String.Empty))  
           PatternDriven = true;  
         else  
           PatternDriven = false;  
         m_pattern = value;  
       }  
     }  
     #endregion  
   
     #region generators  
     /// <summary>  
     /// Gerar uma string que segue o padrão.   
     /// Caracteres possíveis são:   
     /// L - para letra maiúscula   
     /// L - para letra minúscula   
     /// N - de número   
     /// S - para caractere especial   
     /// * - Para qualquer caracter  
     /// </summary>  
     /// <param name="Pattern">o pattern na ser seguido enquanto gera as strings</param>  
     /// <returns>um padrão aleatório que é retornado após a geração</returns>  
     public string Generate(string Pattern)  
     {  
       this.Pattern = Pattern;  
       string res = GenerateString(Pattern.Length);  
       this.Pattern = "";  
       return res;  
     }  
   
     /// <summary>  
     /// gera uma string de comprimento variável compreendido entre MinLength e MaxLength. Os caracteres   
     /// devem ser definidos antes de ser chamada esta função  
     /// </summary>  
     /// <param name="MinLength">cumprimento minimo da string string</param>  
     /// <param name="MaxLength">Cumprimento maximo da string</param>  
     /// <returns>uma string aleatória de um tamanho compreendido entre o minimo e o maximo</returns>  
     public string Generate(int MinLength, int MaxLength)  
     {  
       if (MaxLength < MinLength)  
         throw new ArgumentException("Maximal length should be grater than minumal");  
       int length = MinLength + (GetRandomInt() % (MaxLength - MinLength));  
       return GenerateString(length);  
     }  
   
     /// <summary>  
     /// Gera uma string de comprimento fixo  
     /// os conjuntos de caracteres utilizaveis devem ser definidos antes de chamar esta função  
     /// </summary>  
     /// <param name="FixedLength">cumprimento da string</param>  
     /// <returns>uma string aleatória do comprimento desejado</returns>  
     public string Generate(int FixedLength)  
     {  
       return GenerateString(FixedLength);  
     }  
   
     /// <summary>  
     /// Metodo de geração principal que escolhe o metodo de geração adequado.  
     /// procura situações excepcionais também.  
     /// </summary>  
     private string GenerateString(int length)  
     {  
       if (length == 0)  
         throw new ArgumentException("Não se pode gerar uma string com zero caracteres");  
       if (!UseUpperCaseCharacters && !UseLowerCaseCharacters && !UseNumericCharacters && !UseSpecialCharacters)  
         throw new ArgumentException("Tem de se usar pelo menos um conjunto de caracteres! É que é burro alvin! :D");  
       if (!RepeatCharacters && (CurrentGeneralCharacters.Length < length))  
         throw new ArgumentException("não existem caracteres suficientes para gerar a string sem repetir caracteres");  
       string result = ""; // Esta string contem o resultado  
       if (PatternDriven)  
       {  
         // usando a pattern para gerar algo  
         result = PatternDrivenAlgo(Pattern);  
       }  
       else if (MinUpperCaseCharacters == 0 && MinLowerCaseCharacters == 0 &&  
            MinNumericCharacters == 0 && MinSpecialCharacters == 0)  
       {  
         // usando o algoritmo mais simples, neste caso  
         result = SimpleGenerateAlgo(length);  
       }  
       else  
       {  
         // atenção ao limite  
         result = GenerateAlgoWithLimits(length);  
       }  
       // suporte para strings unicas  
       // recursão, a possibilidade de stack overflow é grande para strings maiores que 3 chars.  
       try  
       {  
         if (UniqueStrings && ExistingStrings.Contains(result))  
           return GenerateString(length);  
         AddExistingString(result); // guarda histórico  
       }  
       catch { throw; }// intercepta o overflow e manda-o de volta (pro raio que o parta)  
       return result;  
     }  
   
     /// <summary>  
     /// gera uma string aleatória baseada na pattern  
     /// </summary>  
     private string PatternDrivenAlgo(string Pattern)  
     {  
       string result = "";  
       List<char> Characters = new List<char>();  
       foreach (char character in Pattern.ToCharArray())  
       {  
         char newChar = ' ';  
         switch (character)  
         {  
           case 'L':  
             {  
               newChar = GetRandomCharFromArray(CurrentUpperCaseCharacters, Characters);  
               break;  
             }  
           case 'l':  
             {  
               newChar = GetRandomCharFromArray(CurrentLowerCaseCharacters, Characters);  
               break;  
             }  
           case 'n':  
             {  
               newChar = GetRandomCharFromArray(CurrentNumericCharacters, Characters);  
               break;  
             }  
           case 's':  
             {  
               newChar = GetRandomCharFromArray(CurrentSpecialCharacters, Characters);  
               break;  
             }  
           case '*':  
             {  
               newChar = GetRandomCharFromArray(CurrentGeneralCharacters, Characters);  
               break;  
             }  
           default:  
             {  
               throw new Exception("O caracter '" + character + "' não é suportado");  
             }  
         }  
         Characters.Add(newChar);  
         result += newChar;  
       }  
       return result;  
     }  
   
     /// <summary>  
     ///   
     ///   
     /// </summary>  
     private string SimpleGenerateAlgo(int length)  
     {  
       string result = "";  
   
       for (int i = 0; i < length; i++)  
       {  
         char newChar = CurrentGeneralCharacters[GetRandomInt() % CurrentGeneralCharacters.Length];  
         if (!RepeatCharacters && result.Contains(newChar))  
         {  
           do  
           {  
             newChar = CurrentGeneralCharacters[GetRandomInt() % CurrentGeneralCharacters.Length];  
           } while (result.Contains(newChar));  
         }  
         result += newChar;  
       }  
       return result;  
     }  
   
     /// <summary>  
     ///   
     /// </summary>  
     private string GenerateAlgoWithLimits(int length)  
     {  
   
       if (MinUpperCaseCharacters + MinLowerCaseCharacters +  
         MinNumericCharacters + MinSpecialCharacters > length)  
       {  
         throw new ArgumentException("Sum of MinUpperCaseCharacters, MinLowerCaseCharacters," +  
           " MinNumericCharacters and MinSpecialCharacters is greater than length");  
       }  
       if (!RepeatCharacters && (MinUpperCaseCharacters > CurrentUpperCaseCharacters.Length))  
         throw new ArgumentException("Can't generate a string with this number of MinUpperCaseCharacters");  
       if (!RepeatCharacters && (MinLowerCaseCharacters > CurrentLowerCaseCharacters.Length))  
         throw new ArgumentException("Can't generate a string with this number of MinLowerCaseCharacters");  
       if (!RepeatCharacters && (MinNumericCharacters > CurrentNumericCharacters.Length))  
         throw new ArgumentException("Can't generate a string with this number of MinNumericCharacters");  
       if (!RepeatCharacters && (MinSpecialCharacters > CurrentSpecialCharacters.Length))  
         throw new ArgumentException("Can't generate a string with this number of MinSpecialCharacters");  
       int AllowedNumberOfGeneralChatacters = length - MinUpperCaseCharacters - MinLowerCaseCharacters  
         - MinNumericCharacters - MinSpecialCharacters;  
   
       string result = "";  
   
       List<char> Characters = new List<char>();  
   
   
       for (int i = 0; i < MinUpperCaseCharacters; i++)  
         Characters.Add(GetRandomCharFromArray(UpperCaseCharacters, Characters));  
       for (int i = 0; i < MinLowerCaseCharacters; i++)  
         Characters.Add(GetRandomCharFromArray(LowerCaseCharacters, Characters));  
       for (int i = 0; i < MinNumericCharacters; i++)  
         Characters.Add(GetRandomCharFromArray(NumericCharacters, Characters));  
       for (int i = 0; i < MinSpecialCharacters; i++)  
         Characters.Add(GetRandomCharFromArray(SpecialCharacters, Characters));  
       for (int i = 0; i < AllowedNumberOfGeneralChatacters; i++)  
         Characters.Add(GetRandomCharFromArray(CurrentGeneralCharacters, Characters));  
   
   
       for (int i = 0; i < length; i++)  
       {  
         int position = GetRandomInt() % Characters.Count;  
         char CurrentChar = Characters[position];  
         Characters.RemoveAt(position);  
         result += CurrentChar;  
       }  
       return result;  
     }  
   
     #endregion  
   
   
     public bool RepeatCharacters;  
   
   
     public bool UniqueStrings;  
   
   
     public void AddExistingString(string s)  
     {  
       ExistingStrings.Add(s);  
     }  
   
     #region misc tools  
   
     private int GetRandomInt()  
     {  
       byte[] buffer = new byte[2]; // 16 bit = 2^16 = 65576   
       Random.GetNonZeroBytes(buffer);  
       int index = BitConverter.ToInt16(buffer, 0);  
       if (index < 0)  
         index = -index; //handle de numeros negativos  
       return index;  
     }  
   
     private char GetRandomCharFromArray(char[] array, List<char> existentItems)  
     {  
       char Character = ' ';  
       do  
       {  
         Character = array[GetRandomInt() % array.Length];  
       } while (!RepeatCharacters && existentItems.Contains(Character));  
       return Character;  
     }  
     #endregion  
   
     #region internal state  
     private bool m_UseUpperCaseCharacters, m_UseLowerCaseCharacters, m_UseNumericCharacters, m_UseSpecialCharacters;  
     private int m_MinUpperCaseCharacters, m_MinLowerCaseCharacters, m_MinNumericCharacters, m_MinSpecialCharacters;  
     private bool PatternDriven;  
     private char[] CurrentUpperCaseCharacters;  
     private char[] CurrentLowerCaseCharacters;  
     private char[] CurrentNumericCharacters;  
     private char[] CurrentSpecialCharacters;  
     private char[] CurrentGeneralCharacters;  
     private RNGCryptoServiceProvider Random;  
     private List<string> ExistingStrings;  
     #endregion  
   }  
 }  
   

E por fim o código que utilizei para cifrar os dados, depois de os ter numa datagridview em windows forms:

   
  foreach (DataGridViewCell cell in row.Cells)  
           {  
             deviceid = row.Cells[0].Value.ToString();  
             plainText = row.Cells[3].Value.ToString(); //lê o código da celula 4 da grid  passPhrase = RSG.Generate(7); //gera a contrasenha  
             passcritped = EncryptStringDino.StringCipher.Encrypt(plainText, passPhrase); //encripta e armazena o valor na variável na passcripted  
             SqlCommand commands2 = new SqlCommand("UPDATE tabela SET codigo = '" + passcritped + "' , key = '" + passPhrase + "' WHERE campo1 = '" + arg1 + "' ;", conex); //conex é a conection string à base de dados 
             commands2.ExecuteNonQuery();  
               
             richTextBox1.AppendText(Environment.NewLine + arg1 + ";");  
           }  
 
E pronto, foi esta a solução que dei à questão.

Provávelmente existem soluções melhores que esta. De futuro pensarei noutras. Também estou a ponderar portar ambas as classes para .net 4.5 usando async e await para evitar alguma lentidão quando o volume de registos é grande.

E pronto, fica aqui um pedaço de código que pode dar jeito a alguém e foi implementado numa noite de insónias.

"Enquanto houver paixão pela programação e café... Haverá código!"

segunda-feira, 31 de março de 2014

Dias uteis (WorkDays) do Excell mas mais completo

Dias e horas úteis

Mais um desafio que recentemente tive de superar, num projecto. Engraçado foi o quão simples é a solução e o quão rebuscada a mente consegue ser para a encontrar.

Neste caso uma simples classe em C# permite achar as horas uteis decorridas entre duas datas e horas, sendo estas passadas como objectos Datetime.

Outras particularidades da classe é asseitar como parametro os dias feriados (excludeDays), que com pouco código até podem vir de uma tabela de uma BD.

Achei engraçada a facilidade com que se consegue instanciar, fazer os calculos e obter o tempo decorrido com precisão ao segundo.

using System;
using System.Linq;
namespace duteis
{
    public class uteis
    {
        private TimeSpan startingTime;
        private TimeSpan endingTime;
        private DayOfWeek[] excludeDays;
        public uteis(TimeSpan? startingTime, TimeSpan? endingTime, DayOfWeek[] excludeDays)
        {
            this.startingTime = startingTime ?? new TimeSpan(8, 30, 0);
            this.endingTime = endingTime ?? new TimeSpan(17, 30, 0);
            this.excludeDays = excludeDays ?? new DayOfWeek[]   
   {   
    DayOfWeek.Saturday ,   
    DayOfWeek.Sunday   
   };
        }
        public uteis()
            : this(null, null, null)
        {
        }
        public double Calculate(DateTime startDate, DateTime endDate)
        {
            var counter = startDate;
            double hours = 0;
            while (counter <= endDate)
            {
                var dayStart = counter.Date.Add(startingTime);
                var dayEnd = counter.Date.Add(endingTime);
                var nextDayStart = startDate.Date.Add(startingTime).AddDays(1);
                if (counter < dayStart)
                    counter = dayStart;
                if (excludeDays == null ||
                  excludeDays.Contains(counter.DayOfWeek) == false)
                {
                    if (endDate < nextDayStart)
                    {
                        var ticks = Math.Min(endDate.Ticks, dayEnd.Ticks) - counter.Ticks;
                        hours = TimeSpan.FromTicks(ticks).TotalHours;
                        break;
                    }
                    else if (counter.Date == startDate.Date)
                    {
                        if (counter >= dayStart && counter <= dayEnd)
                        {
                            hours += (dayEnd - counter).TotalHours;
                        }
                    }
                    else if (counter.Date == endDate.Date &&
                         startDate.Date != endDate.Date)
                    {
                        if (counter >= dayStart && counter <= dayEnd)
                        {
                            hours += (counter - dayStart).TotalHours;
                        }
                        else if (counter > dayEnd)
                        {
                            hours += (endingTime - startingTime).TotalHours;
                        }
                    }
                    else
                    {
                        hours += (endingTime - startingTime).TotalHours;
                    }
                }
                counter = counter.AddDays(1);
                if (counter.Date == endDate.Date)
                    counter = endDate;
            }
            return hours;
        }
    }
}


Atenção a estas linhas onde se define a duração do dia ùtil, e são tratados os "excludeDays" que podem ser os feriados como escrevi anteriormente.

this.startingTime = startingTime ?? new TimeSpan(8, 30, 0);  
this.endingTime = endingTime ?? new TimeSpan(17, 30, 0);  
this.excludeDays = excludeDays ?? new DayOfWeek[]  

Eliminar duplicados numa tabela SQL

Nos ultimos tempos tenho-me deparado com desafios cada vez mais interessantes e incomuns.

Desta feita, precisei de localizar e eliminar de forma eficiente registos duplicados numa tabela, onde não existia um campo identificador unico nem um autonumber, ou outro que me permitisse isolar duplicados e eliminar apenas os duplicados, deixando um registo e não as várias duplicações do mesmo.

Aqui fica o script feito para resolver esta questão, na esperança que seja útil para mais alguém! Porque cooperar é mais produtivo que competir.


 USE BD  
 GO  
 -- ADICIONA UM CAMPO ID AUTONUMER  
 ALTER TABLE dbo.tabela  
   ADD ID INT IDENTITY  
 GO  
 -- SELECT  
 SELECT *  
 FROM tabela  
 GO  
 -- DETECTA DUPLICADOS  
 SELECT campo, COUNT(*) TotalCount  
 FROM tabela  
 GROUP BY campo  
 HAVING COUNT(*) > 1  
 ORDER BY COUNT(*) DESC  
 GO  
 -- ELEMINA DUPS  
 DELETE  
 FROM tabela  
 WHERE ID NOT IN  
 (  
 SELECT MAX(ID)  
 FROM tabela  
 GROUP BY campo)  
 GO  
 -- SELECT DE CONFIRMAÇÃO  
 SELECT *  
 FROM tabela  
 GO  
 -- REMOVE INDEX CRIADO PARA REMOVER DUPLICADOS  
 ALTER TABLE tabela  
 DROP COLUMN ID  
 GO  

terça-feira, 18 de dezembro de 2012

Revista PROGRAMAR Edição 38 (Dezembro 2012)


Nesta edição continuaremos também a premiar os autores dos três melhores artigos, dado o sucesso nas edições anteriores. E os leitores devem dar a sua opinião para que possamos premiar correctamente. Para isso vote em http://tiny.cc/ProgramarED38_V 
Assim nesta edição trazemos até si, como artigo de capa, um artigo de Introdução à Programação em Compute Unified Device Architecture (CUDA) de Patricio Domingues. Nesta 38ª edição pode ainda encontrar os seguintes artigos:
  • Accionamento de Led e Arduino Através de Interface Gráfica em Processing(Nuno Santos)
  • PostgreSQL como alternativa de SGBD (Ricardo Trindade)
  • Custo Efetivo de uma Solução na Nuvem em Ambiente Windows Azure (Edgar Santos)
  • Rápido e bem? A programação web tem! (Sérgio Laranjeira)
  • Introdução à Programação em CUDA (Patrício Domingues)
  • Visual (NOT) Basic - Organismos! Do zero ao mercado (2/2) (Sérgio Ribeiro)
  • Enigmas de C#: Foreach (Paulo Morgado)
  • Core Dump: Core Dump [8] - Fora de Horas (Fernando Martins)
  • Review do livro Introdução ao Design de Interfaces (Sérgio Alves)
  • Review do livro Exercícios em Java – Algoritmia e Programação Estruturada(Carlos José Dias)
  • Viagem da Informação (Rita Peres)
  • “Camaleão! De que cor?” (Sara Santos)
  • Verifica regularmente os ficheiros Javascript do seu site? (David Sopas)
  • Entrevista a Tiago Andrade e Silva
  • Projecto em Destaque na Comunidade Portugal-a-Programar: Taggeo

segunda-feira, 1 de outubro de 2012

Revista Programar 37º Edição


Nesta edição continuam também a premiar os autores dos três melhores artigos, dado o sucesso nas edições anteriores. E os leitores devem dar a sua opinião para que possamos premiar corretamente. Para isso vote em http://tiny.cc/ProgramarED37_V
Assim nesta edição pode-se encontrar, um artigo sobre Makefiles  para conhecer melhor esta poderosa ferramenta e ainda
  • Interface Gráfica - Termometro Usando Arduino e LM335A (Nuno Santos)
  • Algoritmos de Path Find : Princípios e Teorias (João Ferreira)
  • SEO: Search Engine Optimization - Introdução Parte III (Miguel Lobato)
  • Profilers Usar ou não usar… Os 5 minutos que mudam a experiência! (António Cunha Santos)
  • CodeDump -Core Dump [8] - Fora de Horas  (Fernando Martins)
  • Kernel Panic A importância da formação no ensino superior numa carreira dentro da área de segurança informática (Tiago Henriques)
  • Enigmas do C#: Async/Await e Threads (Paulo Morgado)
  • Organismos! Do zero ao mercado (1 de 2) Sérgio Ribeiro)
  • Review do livro HTML5 2ª Edição (Marco Amado)
  • Review do livro Sistemas Operativos (Fábio Domingos)
  • Review do livro Desenvolvimento em iOS iPhone, iPad, iPod Touch (Sara Santos)
  • Análise: O que faz de nós um bom programador? (Rita Peres)
  • Falácias da Computação na Nuvem (Edgar Santos)
  • As reais ameaças de segurança não são os APT (David Sopas)
  • Entrevista - João Barreto
  • Projeto em Destaque na Comunidade Portugal-a-Programar: NotíciasPT
E em parceira com as comunidades  PtCoreSec e Comunidade netPonto:
  • Introducao-Auditoria-Passwords (PtCoreSec)
BizTalk360 uma ferramenta de suporte e monitorização para a plataforma BizTalk Server (NetPonto)

Mais uma excelente edição, com o excelente trabalho a que me habituou enquanto leitor. Aproveito para agradecer ao pessoal da PtCoreSec, por toda a sua ajuda na revista (vocês sabem quem são). À Ana Barbosa, que mostrou ser a "eficiência em pessoa" (Muito Obrigado), ao Filipe Reis, que tanto tem feito pela nova plataforma. Ao Dr. António Pedro Cunha, que não só escreveu um excelente artigo, como tem sido sempre um apoio presente. Ao Silva (É pá tu és o verdadeiro homem dos 7 oficios). e a toda a restante equipa da Revista PROGRAMAR, que a torna cada vez mais uma leitura mais presente e aprazível. 

Não deixem de ler!

sábado, 26 de maio de 2012

Nunca se sabe quando pode fazer jeito!

 include<stdio.h>  
 main()  
  {  
  int i = 0;  
  char array[33][4] = {"NUL\0", "SOH\0","ETX\0", "EOT\0", "ENQ\0","ACK\0","BEL\0","BS\0","TAB\0","TAB\0","LF\0","VT\0","FF\0","CR\0","SO\0","SI\0", "DLE\0","DC1\0","DC2\0","DC3\0","DC4\0","NAK\0","SYN\0","ETB\0","CAN\0","EM\0","SUB\0","ESC\0","FS\0","GS\0","RS\0","US\0","Spc\0"};  
   for (i = 0; i < 33; i++)  
   {  
   printf("%d %s\n", i, array[i]);  
   }  
   for (i=33;i<256;i++)  
   {  
   printf("%d %c\n", i, i);  
   }    
  }  

sexta-feira, 8 de julho de 2011

Snippet de Codigo VB.NET pra criar directorios

Aqui fica um exemplo de codigo de como criar directorios numerados sequencialmente em vb.net.

Fiz isto porque precisei de criar 140 directorios numerados de uma só vez, e não estava com pachorra de criar um a um.

 Imports System.IO  
 Public Class Form1  
 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click  
 Dim contador As Integer  
 Dim max As Integer  
 Dim dir As String  
 Dim dir2 As String dir = "c:\meudirectorio\" max = TextBox1.Text  
 Try  
 For contador = 1 To max dir2 = dir & contador Directory.CreateDirectory(dir2)  
 Next contador  
  Catch End  
 Try MsgBox("Pronto")  
 End Sub End Class   


Na textbox1 é definido o numero do ultimo directório a ser criado.

Até uma proxima