Cifrar/descifrar usando SHA256

Una pregunta que se repite con cierta frecuencia en los foros de programación es como se puede cifrar y descifrar usando sha, md5, etc ... Suelen hacerlas gente que se esta iniciando en el mundo de la criptografía y no tiene muy claro todavía para que sirve cada cosa. La respuesta mas común es decirles que esos son algoritmos de reducción (resumen o simplemente "hash") y que no se pueden usar para cifrar / descifrar, pero eso no es del todo cierto.

El siguiente ejemplo muestra como se puede usar un algoritmo de reducción, como es SHA256, para cifrar y descifrar datos usando el método de cifrado por bloques conocido como Cipher feedback (CFB).

Las siguientes imágenes muestran brevemente como funciona este método:


Como se ve en la imágenes solamente necesitamos que el algoritmo de cifrado vaya en un sentido, por lo que se puede utilizar sin problemas una función de hash.

Un poco de código:

program Crypt;
 
{$APPTYPE CONSOLE}
 
uses
  Windows,
  SysUtils,
  Classes,
  SeSHA256 in 'SeSHA256.pas';
 
procedure Cifrar(Src, Dst: TStream; Key: WideString; Desc: Boolean);
var
  i,j: Integer;
  Temp: TMemoryStream;
  Buffer, Hash: TSHA256HASH;
begin
  Temp:= TMemoryStream.Create;
  try
    // Leemos el primer bloque (32 bytes)
    i:= Src.Read(Buffer,Sizeof(Buffer));
    while i > 0 do
    begin
      // Añadimos la clave al buffer anterior para generar el Hash
      Temp.WriteBuffer(PWideChar(Key)^,Length(Key)*Sizeof(WideChar));
      Temp.Position:= 0;
      // Generamos el hash de la clave y el buffer anterior
      Hash:= CalcSHA256(Temp);
      Temp.Clear;
      if Desc then
        // Si estamos descifrando usamos el buffer antes de descifrar
        Temp.WriteBuffer(Buffer,Sizeof(Buffer));
      for j:= 0 to 7 do
        // Ciframos el buffer con el hash generado
        Buffer[j]:= Buffer[j] xor Hash[j];
      // Escribimos el resultado
      Dst.WriteBuffer(Buffer,i);
      if not Desc then
        // Si estamos cifrando usamos el buffer despues de cifrar
        Temp.WriteBuffer(Buffer,Sizeof(Buffer));
      // Leemos el siguiente bloque  
      i:= Src.Read(Buffer,Sizeof(Buffer));
    end;
  finally
    Temp.Free;
  end;
end;
 
var
  Origen, Destino: TFileStream;
begin
  if ParamCount < 3 then
    Exit;
  try
    Origen:= TFileStream.Create(ParamStr(1),fmOpenRead or fmShareDenyWrite);
    try
      Destino:= TFileStream.Create(ParamStr(2),fmCreate);
      try
        // Ciframos o desciframos dependiendo del parametro /d
        Cifrar(Origen,Destino,ParamStr(3),FindCmdLineSwitch('d',TRUE));
      finally
        Destino.Free;
      end;
    finally
      Origen.Free;
    end;
  except
    On E: Exception do
      Writeln(E.Message);
  end;
end.

Para usarlo:

Crypt origen.txt cifrado.bin Clave
Crypt cifrado.bin descifrado.txt Clave /d

Una de las ventajas de este método es que los datos cifrados tienen el mismo tamaño que el original y no un múltiplo del tamaño del bloque, por lo que no es necesario guardar aparte el tamaño del original. En cuanto a la seguridad, debería ser similar a métodos mas tradicionales de cifrado como AES, aunque no esta tan revisado y comprobado como este último.

Enlaces de interés:
http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedb...
https://github.com/dxeoane/secrypt

Comentarios

Código claro y conciso, como siempre, Domingo.
Gracias.

Muy buen ejercicio amigo, como siempre muy bien documentado. :)

Saludos

Edito: Muy difícil el captcha amigo, a éstas horas ya mi cerebro no quiere hacer operaciones aritméticas :D :D :D