viernes, 9 de marzo de 2018

Cifrar y guardar en archivo binario.

Mucho se dice que no hay que guardar datos cifrados porque de una u otra forma pueden llegar a descifrarlos, máxime si dejamos el valor de la key o llave en nuestro programa. Y es cierto, pero no menos cierto es que es mucho mejor que dejar los datos sin encriptar en un archivo de texto o ini (que también es texto plano) o si se quiere en una tabla en SQLite, o también en un binario sin cifrar. Resumiendo es mejor cifrar. Y si además no guardamos la llave en el programa, sino que la requerimos al usuario (tampoco guardamos su hash), la única manera de obtener los datos es mediante el método de fuerza bruta, que tardará bastante con una llave de 30 caracteres con mayúsculas, minúsculas, números, espacios y símbolos, actualmente tardaría años.

En este ejemplo, para que se entienda bien, porque de eso tratan los ejemplos, cargaremos la llave en una variable. También cabe destacar que los nombres de variables y funciones que se ven en este ejemplo, son para aprender, en la práctica hay que esconder los datos, no usar para cifrar una función llamada cifrar, etc. También hay que validar datos y utilizar try al leer y escribir archivos.

Veamos el código:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
    Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
    Dialogs, StdCtrls, BlowFish;

type

  { TForm1 }

    TForm1 = class(TForm)
        BGuardar: TButton;
 BLeer: TButton;
 Edit1: TEdit;
 Edit2: TEdit;
 Edit3: TEdit;
 Edit4: TEdit;
 Edit5: TEdit;
 Edit6: TEdit;
 Label1: TLabel;
 Label2: TLabel;
 Label3: TLabel;
 Label4: TLabel;
 Label5: TLabel;
 Label6: TLabel;
 procedure BGuardarClick(Sender: TObject);
 procedure BLeerClick(Sender: TObject);
        procedure FormCreate(Sender: TObject);
    private
       function Cifrar (const texto:String):RawByteString;
       function DesCifrar (const texto:String):RawByteString;
        { private declarations }
    public
        { public declarations }
    end;

type
    TRegistro=Record
      Servidor:String[100];
      Usuario:String[100];
      Clave:String[100];
    end;

var
    Form1: TForm1;
    archivo:String;
    llave:String;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  archivo:=GetCurrentDir+PathDelim+'prueba.dat';
  llave:='La llave';
end;

function TForm1.Cifrar(const texto: String): RawByteString;
var
  str_Cifrar:TBlowFishEncryptStream;
  streamTexto:TStringStream;
begin
  streamTexto:=TStringStream.Create('');
  str_Cifrar:=TBlowFishEncryptStream.Create(llave,streamTexto);
  str_Cifrar.WriteAnsiString(texto);
  str_Cifrar.Free;
  Result:=streamTexto.DataString;
  streamTexto.Free;
end;

function TForm1.DesCifrar(const texto: String): RawByteString;
var
  str_DesCifrar:TBlowFishDeCryptStream;
  unstream:TStringStream;
  temp:RawByteString;
begin
  unstream:=TStringStream.Create(texto);
  unstream.Position:=0;
  str_DesCifrar:=TBlowFishDeCryptStream.Create(llave,unstream);
  temp:=str_DesCifrar.ReadAnsiString;
  str_DesCifrar.Free;
  unstream.Free;
  Result:=temp;
end;

procedure TForm1.BGuardarClick(Sender: TObject);
var
  Registro:TRegistro;
  FReg:File of TRegistro;
begin
  Registro.Servidor:=Cifrar(Edit1.text);
  Registro.Usuario:=Cifrar(Edit2.text);
  Registro.Clave:=Cifrar(Edit3.text);
  AssignFile(FReg,archivo);
  Rewrite(FReg);
  Write(FReg,Registro);
  CloseFile(FReg);
end;

procedure TForm1.BLeerClick(Sender: TObject);
var
  Registro:TRegistro;
  FReg:File of TRegistro;
begin
  AssignFile(FReg,archivo);
  Reset(FReg);
  Read(FReg,Registro);
  CloseFile(FReg);
  edit4.Text:=DesCifrar(Registro.Servidor);
  edit5.Text:=DesCifrar(Registro.Usuario);
  Edit6.Text:=DesCifrar(Registro.Clave);
end;

end.

Lo primero que debemos hacer es incluir la unidad BlowFish en uses.
Si trabajamos con archivos binarios, definimos un registro para guardar y leer los datos.
En el método Create simplemente definimos el archivo y la llave, que como ven, puede contener espacios.
Luego 2 funciones para encriptar y desencriptar y 2 procedimientos para guardar y leer, para un ejemplo, alcanza y sobra.

Veamos la función Cifrar:

function TForm1.Cifrar(const texto: String): RawByteString;
var
  str_Cifrar:TBlowFishEncryptStream;
  streamTexto:TStringStream;
begin
  streamTexto:=TStringStream.Create('');
  str_Cifrar:=TBlowFishEncryptStream.Create(llave,streamTexto);
  str_Cifrar.WriteAnsiString(texto);
  str_Cifrar.Free;
  Result:=streamTexto.DataString;
  streamTexto.Free;
end;

Nótese que no devuelve un string, sino un RawByteString que es una cadena de caracteres (string) sin ningún CodePage asociado, ideal e indispensable para que esto funcione.
Necesitamos dos variables, una para el stream de cifrado de Blow Fish y otro un stream común, donde volcaremos el texto cifrado.
Creamos el stream común (streamTexto) vacío.
Creamos el stream de cifrado de BF y le pasamos como parámetros la llave (establecida en FormCreate) y el stream de texto vacío. Es importante hacer todo en este orden.
Ahora ciframos con WriteAnsiString, como parámetro le pasamos la constante de la función llamada texto.
Liberamos el stream de cifrado, el texto cifrado está en el stream de texto (streamtexto).
Finalmente asignamos al resultado de la función el DataString del stream de texto y liberamos el mismo.

Ahora la función DesCifrar:

function TForm1.DesCifrar(const texto: String): RawByteString;
var
  str_DesCifrar:TBlowFishDeCryptStream;
  unstream:TStringStream;
  temp:RawByteString;
begin
  unstream:=TStringStream.Create(texto);
  unstream.Position:=0;
  str_DesCifrar:=TBlowFishDeCryptStream.Create(llave,unstream);
  temp:=str_DesCifrar.ReadAnsiString;
  str_DesCifrar.Free;
  unstream.Free;
  Result:=temp;
end;


También usamos dos variables para los streams y una tercera del tipo RawByteString que contendrá el valor que retornará la función. Podría obviarse esta variable supuestamente, pero por algo está ahí, la verdad no me acuerdo, algún error me habrá hecho intentar con una variable temporal, funcionó y ahí está.
Aquí cuando creamos el stream de texto, no lo hacemos vacío sino con el valor de la constante texto y a su vez, volvemos a cero su posición para que BF la lea desde el comienzo.
Creamos el stream de descifrado de BF y le pasamos también la llave y el stream de texto.
Ahora sí desciframos usando el método ReadAnsiString perteciente a TBlowFishDeCryptStream y lo asignamos a la variable temp; liberamos los streams y como resultado enviamos el valor de temp.

El resto del código no lo voy a explicar porque simplemente es usar estas dos funciones, escribir y leer el archivo binario de la manera habitual.

Descargar el código fuente: BlowFish.7z

o en GitLab


No hay comentarios:

Publicar un comentario