Desenvolupant l'aplicació que verifica el bitrotting dels fitxers m'he trobat en la necessitat d'iniciar diferents threads, un thread per cada disc dur
En total al servidor hi ha 8 discos, 4+4, cal verificar un fitxer i la seva còpia contra el seu hash guardat a la BBDD. Això vol dir 8 threads que fan el mateix, calcular hashs de fitxers. Enlloc d'escriure el mateix 8 cops vaig pensar de fer un bucle per iniciar-los.
Bé? Malament. Els threads són molt tisquimiquis amb les variables que els inicien i el seu valor, ja que si són d'un context comú aleshores es trepitgen. Però millor un exemple, primer un codi que no funciona:
int i = 0;
clsVerificador Verificadors = null;
foreach (DataRow DR in TBL.Rows)
{
Verificadors = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["Path"].ToString(), i);
threadsVerificadors[i] = new Thread(delegate() { Verificadors.VerificaHashes(); });
threadsVerificadors[i].Start();
i++;
Verificadors = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["PathBackup"].ToString(), i);
threadsVerificadors[i] = new Thread(delegate() { Verificadors.VerificaHashBackup(); });
threadsVerificadors[i].Start();
i++;
}
Sembla correcte, i de fet si s'executa només amb un thread funciona bé. Però no. Resulta que quan el thread comença a executar-se han passat uns nanosegons des de que va deixar el codi principal, i agafa els valors de les variables en aquell moment. Com el bucle va més ràpid que la creació dels threads aleshores tots els threads tenen el valor de les variables de la última volta del bucle. Quina és la solució? Obligar al compilador a crear noves variables a cada volta del bucle. Així:
Aquest seria el codi que funciona:
clsVerificador[] Verificadors = newclsVerificador[8];
foreach(DataRow DR in TBL.Rows)
{
int j = i;
Verificadors[j] = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["Path"].ToString(), j);
threadsVerificadors[j] = new Thread(delegate() { Verificadors[j].VerificaHashes(); });
threadsVerificadors[j].Start();
i++;
int k = i;
Verificadors[k] = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["PathBackup"].ToString(), k);
threadsVerificadors[k] = new Thread(delegate() { Verificadors[k].VerificaHashBackup(); });
threadsVerificadors[k].Start();
i++;
}
Es declaren dins del bucle variables, i així són noves. També cal fer que la variable que el "new" sigui nova cada cop. Es pot fer declarant-la dins del bucle o fent-la un array. Funciona!
#08/01/2018 18:58 Programació C# Autor: Alex Canalda
He actualitzat el Firefox, fins aquí tot bé. He passat uns dies sense NoScript, ara ja ha tornat. I també he vist una coseta sorprenent.
Resulta que el Firefox fa servir un magatzem de certificats separat del sistema operatiu, Explorer i Chrome fan servir el del sistema. I el Firefox ha començat a queixar-se del certificat de Gmail. WTF?!! El certificat que fa servir Google per Gmail no està al magatzem del Firefox? Però a Explorer i Chrome funciona!??? MMmmm... suspicious!
Resulta que si es mira el certificat que es servir des de l'empresa on treballo surt això:
Certificat de Gmail si navego des de l'empresa on treballo.
En canvi si es mira el certificat navegant en un ordinador "normal":
Certificat bo, emès per Google
Es pot observar que la cadena de validació dels certificats és diferent, l'emissor també.
Per tant si vull fer servir Gmail en Firefox haig de posar al magatzem de certificats del FFox els certificats arrel de l'empresa que permeten validar el certificat de "mentida" que fa servir l'empresa per Gmail. I com es fa un canvi de certificats així? L'explicació és sencilla, ho fa el Proxy de l'empresa, el Proxy es comunica amb els servidors de Google amb el certificat "bo", i amb el navegador de l'usuari amb un certificat de "mentida", que es valida perque el departament de TI posa al magatzem de la màquina el certificat arrel de l'empresa. Es a dir, el Proxy actua com si fos un atac man-in-the-middle (MITM). En altres paraules l'empresa està vigilant tots els correus de Gmail que envien els seus treballadors.
Curiosament, això només passa amb Gmail, amb altres connexions SSL el proxy no fa la substitució del certificat SSL. Cal deduir que l'empresa no farà servir la informació en clar que captura el Proxy per res beneficiós pel treballador, ergo deixaré de fer servir Gmail al PC de l'empresa i el faré servir al mòbil. També ara al veure el cadenat verd SSL davant la URL no és garantia que el trafic estigui xifrat end-to-end (de principi a fi).
Sempre cal llegir la lletra petita de les especificacions i els disc durs tenen una molt curiosa, el "error rate".
Aquest errors que es produeixen cada N bits (on N és un número força gran) amb el temps acaben generant degradació de dades. Depenent d'on caigui l'error pot ser més o menys greu, un píxel en un fotograma que no es nota o si cau en un lloc sensible potser el fitxer queda mal parat. I això sense arribar a tenir sectors defectuosos.
Aleshores suposem que tenim molts i molts fitxers, que ocupen gigues i gigues i els volem guardar durant molt temps, uns quants anys. Per exemple les fotos i vídeos de les vacances.
El primer que cal garantir és que si falla el disc mecànic no ens quedem sense dades, això s'aconsegueix tenint les dades duplicades en més d'un disc. Ok, fins aquí anem bé, però dupliquem els bits que es poden corrompre.
Però això no garanteix que el disc no s'equivoqui llegint les dades, i com hi ha més d'una còpia quina és la bona? I si al disc dur li surt un sector defectuós com saps el fitxer afectat i que necessites fer servir la còpia bona?
He estat pensant com enfocar aquest problema i penso que les funcions de hash són una bona solució. Una funció de hash donada una entrada sempre dóna el mateix resultat. Si hi ha la més mínima variació en l'entrada, per exemple un bit que està corromput aleshores el resultat canvia. Cal fer una base de dades amb els fitxers i el seu corresponent hash calculat. Cada X temps es torna a recalcular i es compara amb el que hi ha a la BBDD, si és diferent vol dir que el fitxer està corromput.
Ja seria mala pata que totes les còpies del fitxer estiguessin corrompudes al mateix moment. Es pot saber quina és la bona i tornar-la a copiar, eliminant la corrompuda.
Un exemple d'uns bits rebels
#05/05/2017 11:18 Programació Software Autor: Alex Canalda
Amb el canvi de servidor he posat Windows Server 2016, IIS 10, SQL Server 2016 per veure que hi ha de nou. La migració del software d'un a l'altra ha anat prou bé. Copiar fitxers, permisos NTFS, moure BBDDs, canviar cadenes de connexió... fins que al cap d'un temps m'he fixat que en una de les meves webs, un control jQuery, el Grid, no es veia bé.
Resulta que els texts del control mostraven caràcters estranys en els accents. Típics errors de joc de caràcters. Si obria el fitxer JS en l'Editplus veia el text correctament, l'editor em deia que el fitxer està codificat amb ANSI, encara que en realitat per filar prim és Windows-1252. En HTML5 la codificació Windows 1252 es pot servir com ISO-8859-1 ja que les considera la mateixa.
També és curiós veure com el Firefox, en el Network monitor veu correctament el fitxer, en canvi en el Javascript Debugger el mostra amb els caràcters estranys. Suposo que ara han adoptat el UTF-8 com codificació per defecte si no s'indica res.
Hi ha varies solucions, poso les que he trobat més "assequibles".
Passar el fitxer a UTF-8, s'ha de fer fent un "Guardar como...", no un canvi a sac de codificació ja que el canvi directe perd els caràcters, mentre que el "Guardar como..." fa la transformació.
No modificar el fitxer JS i indicar en tag HTML que importa l'script la codificació que fa servir:
Quan hem diuen si sóc programador, o analista o el que... a vegades penso que treballo amb ferralla. Arriba una modificació, es retorça una rutina per que faci les coses noves que es vol, falla per una altra banda... I els sistemes s'han d'adaptar, tallafocs, disc plens, BBDD que cauen... Estabilitat? :D Ni està ni se la espera. Però al final acaba funcionant, durant un temps.
Don't worry if it doesn't work right. If everything did, you'd be out of a job.
#27/10/2016 11:44 Programació Cites Autor: Alex Canalda
Últimament estic aficionat a fer aplicacions amb Threads, a menys que tingui molta pressa evito fer servir el Aplication.DoEvents() i faig un nou Thread que fa la funció.
Alehores he estat buscant com passar paràmetres a la funció que ha d'executar el Thread. I això es fa amb un "delegate", ja que el Thread espera un delegate d'aquests.
I un "delegate" que és? Segons la definició és un tipus de dada. Aquest tipus de dada és un punter a una funció, però més segur que un punter ja que verifica els tipus dels paràmetres. Un exemple:
//Es defineix el tipus de dadapublicdelegatevoid Del(string message);
// Aquesta és la funció dummy per al delegate.publicstaticvoid DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
// Definim una variable "handler" que és del tipus Del
(el tipus Del està definit prèviament com un delegat a una funció que rep un string)
Del handler = DelegateMethod;
// Call the delegate.
handler("Hello World");
Aleshores, per invocar a un Thread hi ha 2 formes de fer-ho:
staticvoid ProcessPerson(Person p)
{
Console.WriteLine("Id :{0},Name :{1}", p.Id, p.Name);
}
// C# 2staticvoid Main()
{
Person p = new Person();
p.Id = "cs0001";
p.Name = "William";
Thread th = new Thread(delegate() { ProcessPerson(p); });
th.Start();
}
// C# 3staticvoid Main()
{
Person p = new Person();
p.Id = "cs0001";
p.Name = "William";
Thread th = new Thread(() => ProcessPerson(p));
th.Start();
}
A mi m'agrada més l'opció 2 de C# sense "lambda expressión" ja que queda més clar que està fent.
#12/07/2016 11:34 Programació C# Autor: Alex Canalda
Recentment he tingut que fer un mini-webserver, multithread per tal que faci d'sparring d'una altra aplicació. Algo fàcil de fer servir, ràpid de configurar, i que respongui a les peticions un contingut fàcil de modificar. Així que he fet al Listener. No té suport per SSL, encara que no és difícil afegir-li.
Té un formulari que li fa d'interface d'usuari i dues classes principals.
HttpServer: Aquesta classes es dedica a tenir un Socket que escolta peticions entrants. Per cada petició entrant instancia una HttpProcessor que s'encarrega de servir la petició en el seu propi thread.
HttpProcessor: Aquesta classes s'encarrega d'atendre una petició. La parseja i li serveix el que demana, ja sigui un GET o un POST.
El codi més interessant del HttpServer és:
publicvoid Listen() {
IPAddress localhost = IPAddress.Any;
listener = new TcpListener(localhost, port);
listener.Start();
ThreadPool.SetMaxThreads(NumThreads, NumThreads);
while (is_active) {
if (listener.Pending())
{
TcpClient client = listener.AcceptTcpClient();
HttpProcessor processor = new HttpProcessor(client, this);
ThreadPool.QueueUserWorkItem(processor.process);
}
else
{
Thread.Sleep(100);
}
}
}
El HttpProcessor per la seva banda:
class HttpProcessor : IDisposable
{
public TcpClient socket { get; set; }
public HttpServer server { get; set; }
private BufferedStream inputStream;
private StreamWriter outputStream;
privatebool isDisposing = false;
privatestring http_contenttype { get; set; }
privatestring http_method { get; set; }
privatestring http_url { get; set; }
privatestring http_protocol_versionstring { get; set; }
privateDictionary<string, string> httpHeaders = newDictionary<string, string>();
privateDictionary<string, string> urlParameters = newDictionary<string, string>();
privatestaticint MAX_POST_SIZE = 10 * 1024 * 1024; // 10MBpublicvoid Dispose()
{
if (!isDisposing)
{
isDisposing = true;
if (inputStream != null)
{
inputStream.Dispose();
inputStream = null;
}
if (outputStream != null)
{
if (outputStream.BaseStream != null)
{
try
{
outputStream.Dispose();
}
catch
{
}
}
outputStream = null;
}
if (socket != null)
{
socket.Close();
socket = null;
}
}
}
public HttpProcessor(TcpClient s, HttpServer srv)
{
this.socket = s;
this.server = srv;
}
privatestring streamReadLine(BufferedStream PinputStream)
{
int next_char = 0;
string data = "";
int i = 0;
while (i < 10000 && next_char != -1)
{
next_char = PinputStream.ReadByte();
if (next_char != -1)
{
if (next_char == '\n')
{
break;
}
else
{
if (next_char != '\r')
{
data += Convert.ToChar(next_char);
}
}
}
i++;
}
return data;
}
publicvoid process(Object threadContext)
{
try
{
inputStream = new BufferedStream(socket.GetStream());
outputStream = new StreamWriter(socket.GetStream(), Encoding.Default);
//HttpGetProcessor httpGetProcessor;//HttpPostProcessor httpPostProcessor;
parseRequest();
readHeaders();
server.IFace.SetResultsValue("URL: " + http_url);
server.IFace.SetResultsValue("Method: " + http_method);
server.IFace.SetResultsValue("Headers ---------");
foreach(KeyValuePair<string, string> Item in httpHeaders)
{
server.IFace.SetResultsValue(Item.Key + ": " + Item.Value);
}
switch (http_method)
{
case"GET":
//httpGetProcessor = new HttpGetProcessor
(http_url, httpHeaders, urlParameters, outputStream, server);//httpGetProcessor.ProcessGETRequest();
EscriureResposta();
break;
case"POST":
//Carrega totes les dades del POST//Dictionary<string, string> PostValues = ReadPostContents();//httpPostProcessor = new HttpPostProcessor
(http_url, httpHeaders, urlParameters, outputStream, server, PostValues);//httpPostProcessor.ProcessPOSTRequest();
server.IFace.SetResultsValue("--------------- POST -------------");
server.IFace.SetResultsValue(ReadPostContents());
EscriureResposta();
break;
case"HEAD":
case"PUT":
case"DELETE":
case"TRACE":
case"CONNECT":
server.IFace.SetResultsValue("Verb HTTP no suportat: " + http_method);
break;
case"UNKNOWN":
server.IFace.SetResultsValue("Verb HTTP desconegut: " + http_method);
break;
}
server.IFace.SetResultsValue("CLOSING------");
Dispose();
}
catch (Exception e)
{
server.IFace.SetResultsValue("Exception: " + e.ToString());
Thread.CurrentThread.Join(10000);
}
}
privatevoid EscriureResposta()
{
HttpCodes Codes = new HttpCodes();
string Capcalera = Codes.Success(http_contenttype, "");
string Contingut = server.IFace.GetResponseValue();
Capcalera += Environment.NewLine + "Content-Length: " + Contingut.ToCharArray().Length;
outputStream.Write(Capcalera);
//Aquests writelines separa els headers de la resposta del contigut de la resposta
outputStream.WriteLine("");
outputStream.WriteLine("");
//Envia el contingut
outputStream.Write(Contingut);
outputStream.Flush();
outputStream.Close();
}
privatevoid parseRequest()
{
string request = streamReadLine(inputStream);
request = request.Trim();
if (!string.IsNullOrWhiteSpace(request))
{
string[] tokens = request.Split(' ');
if (tokens.Length != 3)
{
thrownewException("Invalid HTTP request line: ->" + request + "<-");
}
http_method = tokens[0].ToUpper();
http_url = tokens[1];
if (http_url.Contains("?")) parseURLParameters();
http_protocol_versionstring = tokens[2];
}
else
{
http_method = "UNKNOWN";
}
}
privatevoid parseURLParameters()
{
string[] URL = http_url.Split('?');
if(URL.Length != 2)
{
server.IFace.SetResultsValue("ERROR: URL no valida: " + http_url);
}
string[] Parameters = URL[1].Split('&');
string[] Parameter;
http_url = URL[0];
foreach (string item in Parameters)
{
Parameter = item.Split('=');
if(Parameter.Length != 2)
{
server.IFace.SetResultsValue("ERROR: Parametre no valid: " + Parameter);
}
urlParameters.Add(Parameter[0], Parameter[1]);
}
}
privatevoid readHeaders()
{
String line;
while ((line = streamReadLine(inputStream)) != null)
{
line = line.Trim();
if (line.Equals(""))
{
return;
}
int separator = line.IndexOf(':');
if (separator == -1)
{
thrownewException("Invalid HTTP header line: ->" + line + "<-");
}
String name = line.Substring(0, separator);
int pos = separator + 1;
while ((pos < line.Length) && (line[pos] == ' '))
{
pos++; // strip any spaces
}
string value = line.Substring(pos, line.Length - pos);
//Console.WriteLine("header: {0}:{1}",name,value);
httpHeaders.Add(name, value);
}
}
privateconstint BUF_SIZE = 4096;
privatestring ReadPostContents()
{
// this post data processing just reads everything into a memory stream.// this is fine for smallish things, but for large stuff we should really// hand an input stream to the request processor. However, the input stream // we hand him needs to let him see the "end of the stream" at this content // length, because otherwise he won't know when he's seen it all! //Console.WriteLine("get post data start");int content_len = 0;
MemoryStream ms = new MemoryStream();
StreamReader SR = null;
string PostData = "";
//Dictionary<string, string> PostValues;if (httpHeaders.ContainsKey("Content-Length"))
{
content_len = Convert.ToInt32(httpHeaders["Content-Length"]);
if (content_len > MAX_POST_SIZE)
{
thrownewException(String.Format("POST Content-Length({0}) too big for this simple server", content_len));
}
byte[] buf = newbyte[BUF_SIZE];
int to_read = content_len;
while (to_read > 0)
{
//Console.WriteLine("starting Read, to_read={0}",to_read);int numread = this.inputStream.Read(buf, 0, Math.Min(BUF_SIZE, to_read));
//Console.WriteLine("read finished, numread={0}", numread);if (numread == 0)
{
if (to_read == 0)
{
break;
}
else
{
thrownewException("POST Client disconnected during post");
}
}
to_read -= numread;
ms.Write(buf, 0, numread);
}
ms.Seek(0, SeekOrigin.Begin);
}
SR = new StreamReader(ms);
PostData = SR.ReadToEnd();
return PostData;
//PostValues = ProcessPostValues(PostData);//return PostValues;
}
privateDictionary<string, string> ProcessPostValues(string PostData)
{
Dictionary<string, string> PostValues = newDictionary<string, string>();
string EncodedHTTP;
string DecodedString;
string[] Splitter;
string[] ItemSplitter;
Splitter = PostData.Split('&');
for (int i = 0; i < Splitter.Length; i++)
{
ItemSplitter = Splitter[i].Split('=');
if (ItemSplitter.Length == 2)
{
EncodedHTTP = ItemSplitter[1];
DecodedString = WebUtility.UrlDecode(EncodedHTTP);
PostValues.Add(ItemSplitter[0], DecodedString);
}
}
return PostValues;
}
}
També hi ha una classe de support que encapsula els codis HTTP, i el formulari principal fa servir la classe de configuració.
class HttpCodes
{
publicstring SuccessJSON()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: application/json; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessHTML()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: text/html; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
/// <summary>/// Resposta amb 200 OK/// </summary>/// <param name="ContentType">application/xml, text/xml, text/html</param>/// <param name="Charset">Valors posibles "iso-8859-1" o "" per no enviar cap</param>/// <returns></returns>publicstring Success(string ContentType, string Charset)
{
string Result = "";
Result = "HTTP/1.0 200 OK";
if(string.IsNullOrWhiteSpace(Charset))
{
Result += Environment.NewLine + "Content-Type: " + ContentType;
}
else
{
Result += Environment.NewLine + "Content-Type: " + ContentType + "; charset=" + Charset;
}
//Result += Environment.NewLine + "Content-Type: " + ContentType + "; charset=iso-8859-1";//Result += Environment.NewLine + "Content-Type: application/xml";//Result += Environment.NewLine + "Content-Type: text/xml";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessText()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: text/plain; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring Error500()
{
string Result = "";
Result = "HTTP/1.0 500 Internal Server Error";
Result += Environment.NewLine + "Content-Type: text/plain; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
/// <summary>/// Escriu un 200 OK amb el content type que toca/// </summary>/// <param name="IMG">jpg, gif, png</param>publicstring SuccessIMG(string IMG)
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: image/" + IMG;
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessCSS()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: text/css";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessJS()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: application/x-javascript";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring FileNotFound404()
{
string Result = "";
Result = "HTTP/1.0 404 File not found";
Result += Environment.NewLine + "Content-Type: text/plain";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring Redirect(string URL)
{
string Result = "";
Result = "HTTP/1.1 302 Found";
Result += Environment.NewLine + "Location: " + URL;
Result += Environment.NewLine + "Connection: close";
return Result;
}
}
El Listener té un germà gran que fa més coses, si algun dia l'acabo ja el publicaré
#31/05/2016 12:30 Programació C# Autor: Alex Canalda
Recentment m'ha tocat barallar-me amb aquest tipus de criptografia. I realment és interessant com encaixen les diferents peces del puzle.
La motivació del PKCS#7 és que els algoritmes d'encriptació asimètrics (els que fan servir clau pública i clau privada) no poden xifrar quantitats grans de dades. Al contrari els algoritmes simètrics (la mateix clau es fa servir per encriptar que per desencriptar) són "bons" xifrant quantitats grans de dades.
Per això el que es fa és el següent: les dades es xifren amb un algoritme simètric fent servir una clau generada aleatòriament que només es fa servir en un missatge. Aquesta clau simètrica és petita i es xifra amb la clau pública d'una algoritme asimètric.
Com es xifra un missatge
La informació arriba en Base64, que és un tipus de codificació, per la que C# ja té una forma fàcil de gestionar. Es fa amb el Convert, que retorna un array de bytes.
Aquests bytes no són ASCII, són ASN.1 (Abstract Syntax Notation 1) que és un format binari d'encapsular informació. De fet és molt similar al XML, però amb binari. Té TAGs, longitud del TAG i valor del TAG. Apart del tema del BER i el DER (però no vull explicar aquí tot l'estàndard ASN.1). El ASN.1 és antic i mig internet el fa servir (i jo sense saber-ho).
Per donar estructura a un missatge ASN.1 hi ha un llenguatge que defineix les estructures el formen. És el Encoding Control Notation (ECN), seria l'equivalent a un XSD/DTD del XML. Aleshores per rebre un missatge ASN.1 que segueixi un estàndard ha de complir una estructura ECN determinada, concretament el PKCS#7 fa servir el Cryptographic Message Syntax (RFC 5652).
Aleshores, el que defineix aquest RFC 5652 és el format en que han de posar-se les parts del missatge, es a dir:
Les dades xifrades amb l'algoritme simètric.
La clau simètrica feta servir per xifrar les dades.
El vector d'inicialització del xifrat simètric (aquest està en clar, sense xifrar).
Dades de la clau pública emprada per xifrar la clau simètrica, normalment aquesta clau està guardada en un certificat X509, aleshores en les dades s'inclou la informació típica sobre el certificat, email, dades de l'emissor del certificat (país, adreça,...), etc...
ASN.1 + ECN RFC 5652 (CMS) = PKCS#7
Igual que el XML posa noms a les marques, el ASN.1 té OID (Object Identifier), que identifiquen el contingut d'un tag. Aleshores cada un dels punts anteriors queda identificat dins del missatge. En format ASCII i PrettyPrint un missatge PKCS#7 pot quedar:
Per implementar tot això en C# he fet servir la llibreria BouncyCastle en la versió C#, et simplifica la vida el no tenir que batallar amb el ASN.1 ja que la llibreria m'ha permès transformar el ASN.1 a un string normal. I un cop ja tinc l'string m'he buscat la vida per desencriptar-lo amb llibreries .NET normals. He trobat altres llibreries que permeten processar el ASN.1 però cap m'ha permès obtenir el string normal i que posteriorment no hagi de parsejar-lo. Per exemple hi ha una implementació del projecte Mono que et fa un PrettyPrint del text ASN.1. També cal comentar que durant l'encriptació depenent del format en la que tenim la clau pública el ASN.1 generat té un format o un altra, no és el mateix disposar del certificat X509 o de la clau pública en format PEM.
El codi d'encriptació és força senzill. Únicament cal veure si encriptem fent servir un certificat X509 amb totes les dades o bé un fitxer PEM que conté la clau pública.
CmsEnvelopedDataGenerator gen = new CmsEnvelopedDataGenerator();
X509CertificateParser Parser = new X509CertificateParser();
StreamReader sr = new StreamReader(FitxerCertificat);
X509Certificate Cert = Parser.ReadCertificate(sr.BaseStream);
gen.AddKeyTransRecipient(Cert);
/*
byte[] KeyId = new byte[] { 1, 2, 3, 4, 5 };
TextReader TxtRead = File.OpenText(@"c:\Ruta_al_fitxer\Public.txt");
Org.BouncyCastle.OpenSsl.PemReader PubCert =
new Org.BouncyCastle.OpenSsl.PemReader(TxtRead);
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters RSAParams =
(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)PubCert.ReadObject();
gen.AddKeyTransRecipient(RSAParams, KeyId);
*/
CmsProcessableByteArray ByteArr = new CmsProcessableByteArray(OrigenBytes);
CmsEnvelopedData Data2 = gen.Generate(ByteArr, AES256_OID_STR);
txtEncrypted.Text = Convert.ToBase64String(Data2.GetEncoded());
Per desencriptar el codi és el següent:
byte[] Base64 = Convert.FromBase64String(Origen);
/*
Codi pel Pretty Parser
ASN1Element ASN = new ASN1Element(Base64, 0);
StringBuilder SB = new StringBuilder();
ASN.BuildString(SB, 3);
string Resultat2 = SB.ToString();
PrettyPrinter P = new PrettyPrinter(ASN);
string Resultat = P.ToString();
*/
CmsEnvelopedData Data = new CmsEnvelopedData(Base64);
Content = Data.ContentInfo.Content.ToString();
string[] Splitted = Content.Split('#');
int Posicio = 0;
string ClauAESEncriptadaRSA = "";
string strIV = "";
string DadesEncriptadesAES = "";
switch(Splitted.Length)
{
//Si s'encripta amb una clau PEM sense les dades d'un certificat aleshores hi 5 parts, per que s'especifica el keyID//Queda algo com://[2, [[2, [0]#0102030405, [1.2.840.113549.1.1.1, NULL],
#88ed9a2dc3f6f520a955dda3b3460c2e72b232f0cebb20aeeb5e9a7d27e7df3d1a026b017414c61483484fb7877
d466fc8bf47b20a1a6a9b4caf314f7862ea9cdcd4c7fdea168b07748d6bd13866df1c2319c07650a4a14c697b215
982007804b49a6c7f673b928f0417d44900bedb3f97bd5c4f6a08efce136e88f7b2fa714b2c03baad24ce51a3ad0
96649393ac71af2d6bcba3d42b108b5ec9b106028fd782b29162144c01f1fbca81200006b6c1c1513929b1d8b82d
ae2ec4bd0b37d2aaa08a4f1c8901ec23ad7e4d4eda522137fec9a00d34ef25c8f5f00fbe9a3cdce83bcdede83c9d
dff7b91c0c46d6394f4150b98782c12380cccd06d06858548c872]], [1.2.840.113549.1.7.1, [2.16.840.1.101.3.4.1.42,
#dd65197a345a16990dba5e3b60653a23],
[0]#1af48e832434616b6d16e235a28690fb8b506753525e91eb0674e20cd6224e1ebe13346b6cc01cc10ecf8442
12291077750569e09986dea001beec448fb66b69c878eebae45e1303f05518705c0a0626]]case 5:
{
Posicio = Splitted[2].IndexOf(']');
ClauAESEncriptadaRSA = Splitted[2].Substring(0, Posicio);
Posicio = Splitted[3].IndexOf(']');
strIV = Splitted[3].Substring(0, Posicio);
Posicio = Splitted[4].IndexOf(']');
DadesEncriptadesAES = Splitted[4].Substring(0, Posicio);
break;
}
//Si s'encripta amb un fitxer CRT que només té la clau publica hi ha menys trossos binaris//Queda algo com://[0, [[0, [[[[1.2.840.113549.1.9.1, email@email.es]],
[[2.5.4.6, ES]], [[2.5.4.8, Barcelona]], [[2.5.4.7, Carrer A 33]],
[[2.5.4.10, La meva empresa]], [[2.5.4.11, El meu departament]], [[2.5.4.3, Projecte]]], 1444401454],
[1.2.840.113549.1.1.1, NULL],
#9a5aff081265f8a5ec74a5ec10173758c8520a74c6373455f74ce5fa35699bd036028b6999b9a5303486e95fa39da
87215e14e273639c8ad76854e9d9b3b1ef6c4d00a8f0c9b6be1c0d0f92a5298c7fcd0c4ae782ce08545d3693810655
bc7fcbfea2dd2093b8de1c7af7b930e27d72b2b4e0bb45b3624923c791bc429160b0717cc7867698f4ee991f175812
58dfd8a53b6207e482f8840ff832a721188043f849d561d4115195a664f8baacdab2d75ccb486a4107e94c8d4d3c3f
f542cb997a8c9d444a53ceb21434b48c85bbf18d45131b9d5998cbb06911537bc1f8413cf3e669d3289a42fd699cc8
8e1b0d2d5b399bf40c2b4c2aefa5967c66516cfba0c]], [1.2.840.113549.1.7.1, [2.16.840.1.101.3.4.1.42,
#6ab63f859a84a8422708ceed3a4d3244],
[0]#242d87bbf418ab8773945fedc0e38761e111827e298a72998a5e52e241b723df86e9804ef373118d4ba951b2e0
bea96f41bc6da80a5131dc8fc6d98d97d378cc25d5b6f8c2b0baf42506dd1a7bf9f9de]]case 4:
{
Posicio = Splitted[1].IndexOf(']');
ClauAESEncriptadaRSA = Splitted[1].Substring(0, Posicio);
Posicio = Splitted[2].IndexOf(']');
strIV = Splitted[2].Substring(0, Posicio);
Posicio = Splitted[3].IndexOf(']');
DadesEncriptadesAES = Splitted[3].Substring(0, Posicio);
break;
}
}
byte[] ClauAESEncriptadaRSA_bytes = ConvertHexStringToByteArray(ClauAESEncriptadaRSA);
OrigenBytes = ConvertHexStringToByteArray(DadesEncriptadesAES);
//Desencriptador de la Clau AES
RSACryptoServiceProvider KeyImporter = new RSACryptoServiceProvider();
//Carreguem la clau
KeyImporter.LoadPrivateKeyPEM(PrivateKeyPEM);
//Obtenim la clau AESbyte[] PrivateKey = KeyImporter.Decrypt(ClauAESEncriptadaRSA_bytes, false);
AesCryptoServiceProvider Decrypter = new AesCryptoServiceProvider();
byte[] IV = ConvertHexStringToByteArray(strIV);
ICryptoTransform EncDec = null;
Decrypter.Key = PrivateKey;
Decrypter.IV = IV;
Decrypter.Mode = CipherMode.CBC;
Decrypter.Padding = PaddingMode.None;
EncDec = Decrypter.CreateDecryptor();
DecryptedBytes = newbyte[OrigenBytes.Length];
for (int i = 0; i < OrigenBytes.Length; i = i + 16) EncDec.TransformBlock(OrigenBytes, i, 16, DecryptedBytes, i);
if (chkCopyToClipboard.Checked) Clipboard.SetText(txtDecrypt.Text);
txtBase64.Text = Content;
txtDecrypt.Text = ConvertByteArrayASCIIToString(DecryptedBytes);
txtDecrypt.Text = txtDecrypt.Text.Replace("[03]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[04]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[05]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[06]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[07]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[08]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[09]", "");
#02/11/2015 10:22 Programació C# Autor: Alex Canalda
Fa uns dies m'han demanat que llisti els usuaris que s'han loginat al servidor d'SQL Server, de forma correcta o no.
El primer que cal fer és activar el log d'usuaris per casos correctes (per defecte només es registren els incorrectes).
Es fa així
Després cal obtenir la informació del log d'SQL, això es fa mitjançant la stored procedure xp_readerrorlog. Aquesta stored té els següents paràmetres:
El fitxer de log que es vol llegir: 0 = actual, 1 = arxiu #1, 2 = arxiu #2, etc...
El tipus de fitxer de log: 1 o NULL = error log, 2 = SQL Agent log
String que vols buscar 1
String que vols buscar 2, per refinar més la cerca
Data d'inici
Data de fi
Ordenació dels resultats: N'asc' = ascending, N'desc' = descending
Aleshores es pot fer algo similar a:
DECLARE @tLog TABLE
(
LogDate datetime,
ProcessInfo nvarchar(50),
Value nvarchar(max)
)
DECLARE @FechaActual datetime
SELECT @FechaActual=GETDATE()
INSERTINTO @tLog
EXEC master.dbo.xp_readerrorlog 0, 1, N'Login', N'succeeded', '2015-03-26T00:00:00', @FechaActual, N'desc'SELECT MIN(LogDate) AS FechaLogin, [VALUE] FROM @tLog GROUP BY [VALUE]
És molt important posar una "N" davant dels strings de cerca per que els converteix a Unicode, si no es posa no troba resultats. Quan recuperem la taula els valors tenen el nom de l'usuari entre cometes simples, es pot fer algo com:
Usuario = DR("VALUE").ToString()
//Treiem el text que hi ha abans i despres del nom de l'usuari. El nom de l'usuari va entre cometes simples
Inicio = Usuario.IndexOf("'") + 1
Fin = Usuario.IndexOf("'", Inicio)
Usuario = Usuario.Substring(Inicio, Fin - Inicio)
I llestos!
#14/05/2015 15:00 Programació SQLServer Autor: Alex Canalda