domingo, 11 de marzo de 2018

Guardar y leer registros en archivos binarios.

Hay dos maneras de hacer esto, con AssingFile o con TFileStream. Prefiero AssingFile, pues se pueden hacer cosas que no se pueden con stream, por ejemplo mover el puntero hacia un registro (aunque solo en modo lectura) mediante el procedimiento Seek y también podemos usar la función FilePos para averiguar sobre qué registro está el puntero. Agregar datos a un archivo binario no se puede ni con AssigFile ni con FileStream, siempre debemos cargar los datos en memoria y re-escribir todo el archivo, lo cual hoy no es la gran cosa debido a la cantidad de sobra de memoria RAM que tiene cualquier ordenar y la velocidad de procesamiento.

En este ejemplo guardaremos registros con nombres de empresas, número ID y nombre de la base de datos, algo simple. Como buffer usaremos un array (vector o matriz unidimensional) de registros.

Código:

unit Unit1;

{$mode objfpc}{$H+}

interface

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

type

  { TForm1 }

    TForm1 = class(TForm)
    BGuardar: TButton;
    BLeer: TButton;
    BAgregar: TButton;
    edID: TEdit;
    EdEmpresa: TEdit;
    EdBD: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Memo1: TMemo;
    procedure BAgregarClick(Sender: TObject);
        procedure BGuardarClick(Sender: TObject);
      procedure BLeerClick(Sender: TObject);
        procedure FormCreate(Sender: TObject);
    private
        { private declarations }
    public
        { public declarations }
    end;
type
    TReg=record
      ID:Integer;
      Empresa:string[100];
      BD:string[100];
  end;

var
    Form1: TForm1;
    archivo:String;
    aReg:array[0..99] of TReg;
    cantReg:Integer;


implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  archivo:=GetCurrentDir+PathDelim+'datareg.bin';
  cantReg:=0;
  if FileExists(archivo) then BLeerClick(Sender);
  //Si el archivo existe lo carga al array
end;

procedure TForm1.BGuardarClick(Sender: TObject);
var
  FReg:File of TReg; //Archivo que contendrá registros tipo TReg
  i:Integer;
begin
  AssignFile(FReg,archivo);  //Vinculamos el archivo
  Rewrite(FReg);             //Lo vamos a sobreescribir
  for i:=0 to cantReg-1 do //Recorremos el array y lo escribimos
                           //con Write
  begin
    Write(FReg,aReg[i]);
 end;
  CloseFile(FReg);   //Cerramos el archivo
end;

procedure TForm1.BLeerClick(Sender: TObject);
var
  FReg:File of TReg; //Archivo que contendrá registros tipo TReg
  i:Integer;
begin
  AssignFile(FReg,archivo); //Vinculamos el archivo
  Reset(FReg); //Lo abrimos en modo solo lectura
  i:=0;
  while not (EOF(FReg)) do      //Lo cargamos al array
  begin
    Read(FReg,aReg[i]);
    Memo1.Lines.Add(IntToStr(aReg[i].ID)+' '+aReg[i].Empresa+' '+
    aReg[i].BD);
    Inc(i);
    Inc(cantReg);
  end;
  CloseFile(FReg);  //Cerramos el archivo
end;

procedure TForm1.BAgregarClick(Sender: TObject);
begin
  aReg[cantReg].ID:=StrToInt(edID.Text);//Agregamos solo al array, 
                                        //no al archivo.
  aReg[cantReg].Empresa:=EdEmpresa.Text;
  aReg[cantReg].BD:=EdBD.Text;
  Inc(cantReg);
  Memo1.Lines.Add('aReg['+IntToStr(cantReg-1)+']: '+EdEmpresa.Text);
end;

end.


En el registro debemos definir la longitud de los strings.
El primer registro de un archivo binario está en la posición 0 (cero).
No hay forma de agregar un registro a un archivo binario, como sí podemos hacerlo con archivos de texto plano, siempre ha que sobreescribir todo el archivo, de ahí ReWrite.
Al utilizar AssignFile podemos acceder mediante Seek a un determinado registro conociendo su posición y leerlo.

Código fuente:  archivosbinarios.7z (incluye e archivo binario con 5 registros).

o en GitLab



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