PDA

Ver la Versión Completa : ftp Upload / Download (Aplicación)


seoane
26-08-2007, 01:53:37
Hace tiempo que no pongo código por aquí, al menos no código útil (http://www.clubdelphi.com/foros/showthread.php?t=41240), así que vamos a ver que os parece esta pequeña herramienta.

Se trata de una aplicación de consola que permite subir o bajar un archivo de un servidor ftp, pero con la particularidad que lo cifra al subirlo y lo descifra al bajarlo. El cifrado que utiliza es AES de 256 bits, que nos permite usar contraseñas de hasta 32 caracteres, y es lo suficientemente seguro para que lo utilice el gobierno de los EE.UU.

Pero dije que este no era código inútil, así que vamos a buscarle alguna utilidad. La primera, y mas evidente, es subir un archivo:

ftpup --key=clave --local=archivo.txt --password=pass --user=user --remote=/archivo.txt --server=servidor.com

El comando anterior subiría el archivo "archivo.txt" al servidor "servidor.com", usando el nombre de usuario "user", el password "pass" y la clave de cifrado "clave". Hasta ahora nada que no se pudiera hacer con cualquier otro cliente de ftp, salvo el cifrado. Se me ocurre que puede servir para hacer una copia de seguridad de un documento privado, si lo combinamos, por ejemplo, con las tareas programadas de Windows.

Para bajarlo, nada mas sencillo que esto:

ftpup -d --key=clave --local=archivo.txt --password=pass --user=user --remote=/archivo.txt --server=servidor.com

Pero ya me conocéis, hay que buscar otro uso mas interesante a esto, darle otra vuelta de tuerca. Que os parece utilizarlo para guardar, y leer, anotaciones cifradas desde cualquier ordenador, sin dejar ningún rastro en el equipo. Pues vamos a probar, lo primero es decir que en el parámetro --local se puede usar un "." para decir que se utilice en vez de un archivo, la entrada y salida estándar. Sabiendo esto, podríamos hacer algo como esto:

echo Mensaje secreto | ftpup --key=clave --local=. --password=pass --user=user --remote=/archivo.txt --server=servidor.com


Y para leerlo algo como esto:

ftpup -d --key=clave --local=. --password=pass --user=user --remote=/archivo.txt --server=servidor.com

Y ya puestos, podríamos guardar cómodamente nuestras contraseñas en un archivo de texto, y desde nuestra casa subirlas al ftp. Así desde cualquier equipo en el que estemos podremos ver las contraseñas en pantalla, si necesidad de guardar ningún archivo en el disco.

Bueno, seguro que alguna utilidad mas se le puede sacar. Como siempre, criticas (constructivas) y sugerencias serán bien recibidas. Espero vuestros comentarios.

El código (es una aplicación de consola):

program ftpup;

{$APPTYPE CONSOLE}

uses
Windows,
SysUtils,
Classes,
WinInet,
AES in 'AES.pas';

type
EGetOptError = class(Exception);

var
Options: record
Download: Boolean;
Key: String;
Local: String;
Password: String;
Port: Integer;
Remote: String;
Server: String;
User: String;
end;

function Starts(Sub, Str: String): Boolean;
begin
Result:= SameText(Sub,Copy(Str,1,Length(sub)));
end;

procedure Getopt;
var
i,j: Integer;
Str: String;
begin
FillChar(Options,Sizeof(Options),#0);
for i:= 1 to ParamCount do
begin
Str:= ParamStr(i);
if (Copy(Str,1,2) = '--') and (Length(Str) > 2) then
begin
if SameText(Str,'--Download') then
Options.Download:= TRUE
else if Starts('--Key=',Str) then
begin
if Options.Key = EmptyStr then
Options.Key:= Copy(Str,Length('--Key=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--Local=',Str) then
begin
if Options.Local = EmptyStr then
Options.Local:= Copy(Str,Length('--Local=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--Remote=',Str) then
begin
if Options.Remote = EmptyStr then
Options.Remote:= Copy(Str,Length('--Remote=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--Password=',Str) then
begin
if Options.Password = EmptyStr then
Options.Password:= Copy(Str,Length('--Password=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--Port=',Str) then
begin
if Options.Port = 0 then
Options.Port:= StrToInt(Copy(Str,Length('--Port=') + 1,MAXINT))
else
raise EGetOptError.Create(Str);
end else if Starts('--Server=',Str) then
begin
if Options.Server = EmptyStr then
Options.Server:= Copy(Str,Length('--Server=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--User=',Str) then
begin
if Options.User = EmptyStr then
Options.User:= Copy(Str,Length('--User=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else
raise EGetOptError.Create(Str);
end else if (Copy(Str,1,1) = '-') and (Length(Str) > 1) then
begin
Str:= UpperCase(Str);
for j:= 2 to Length(Str) do
begin
if Str[j] = 'D' then
Options.Download:= TRUE
else
raise EGetOptError.Create(Str);
end;
end else
begin
raise EGetOptError.Create(Str);
end;
end;
if Options.Port = 0 then
Options.Port:= 21;
end;

function ftpUpload(Stream: TStream; RemoteFile, Server: String; Port: Word;
Username, Password: PChar; Key: TAESExpandedKey): Boolean;
var
hNet: HINTERNET;
hCon: HINTERNET;
hFile: HINTERNET;
Context: DWORD;
Count: Cardinal;
State, Temp: TAESState;
Size: int64;
begin
Result := FALSE;
Context:= 0;
hNet := InternetOpen('agent', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if (hNet <> nil) then
begin
hCon:= InternetConnect(hNet,PChar(Server),Port,Username,Password,
INTERNET_SERVICE_FTP,0,Context);
if (hCon <> nil) then
begin
hFile:= FtpOpenFile(hCon,PChar(RemoteFile),GENERIC_WRITE,
FTP_TRANSFER_TYPE_BINARY,Context);
if hFile <> nil then
begin
try
if Stream is TFileStream then
Size:= Stream.Size
else
Size:= -1;
if InternetWriteFile(hFile,@Size,Sizeof(Size),Count) and
(Count = Sizeof(Size)) then
begin
FillChar(Temp,Sizeof(Temp),#0);
FillChar(State,Sizeof(State),#0);
Count:= Stream.Read(State,Sizeof(State));
while Count > 0 do
begin
XORState(State,Temp);
AESEncrypt(State,Key);
if not InternetWriteFile(hFile,@State,Sizeof(State),Count) then
break;
Temp:= State;
FillChar(State,Sizeof(State),#0);
Count:= Stream.Read(State,Sizeof(State));
end;
Result:= Count = 0;
end;
if not Result then
Writeln(SysErrorMessage(GetLastError));
except
On E: Exception do
Writeln(E.Message);
end;
InternetCloseHandle(hFile);
end else Writeln(SysErrorMessage(GetLastError));
InternetCloseHandle(hCon);
end else Writeln(SysErrorMessage(GetLastError));
InternetCloseHandle(hNet);
end else Writeln(SysErrorMessage(GetLastError));
end;

function ftpDownload(Stream: TStream; RemoteFile, Server: String; Port: Word;
Username, Password: PChar; Key: TAESExpandedKey): Boolean;
var
hNet: HINTERNET;
hCon: HINTERNET;
hFile: HINTERNET;
Context: DWORD;
Count: Cardinal;
State, Temp, Ant: TAESState;
Size: int64;
begin
Result := FALSE;
Context:= 0;
hNet := InternetOpen('agent', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if (hNet <> nil) then
begin
hCon:= InternetConnect(hNet,PChar(Server),Port,Username,Password,
INTERNET_SERVICE_FTP,0,Context);
if (hCon <> nil) then
begin
hFile:= FtpOpenFile(hCon,PChar(RemoteFile),GENERIC_READ,
FTP_TRANSFER_TYPE_BINARY,Context);
if hFile <> nil then
begin
try
if InternetReadFile(hFile,@Size,Sizeof(Size),Count) and
(Count = Sizeof(Size)) then
begin
FillChar(Temp,Sizeof(Temp),#0);
FillChar(State,Sizeof(State),#0);
if InternetReadFile(hFile,@State,Sizeof(State),Count) then
begin
while Count > 0 do
begin
Ant:= State;
AESDecrypt(State,Key);
XORState(State,Temp);
if Stream.Write(State,Count) <> Integer(Count) then
break;
Temp:= Ant;
FillChar(State,Sizeof(State),#0);
if not InternetReadFile(hFile,@State,Sizeof(State),Count) then
break;
end;
Result:= Count = 0;
end;
if (Stream is TFileStream) and (Stream.Size > Size)
and (Size > 0 )then
Stream.Size:= Size;
if not Result then
Writeln(SysErrorMessage(GetLastError));
end;
except
On E: Exception do
Writeln(E.Message);
end;
InternetCloseHandle(hFile);
end else Writeln(SysErrorMessage(GetLastError));
InternetCloseHandle(hCon);
end else Writeln(SysErrorMessage(GetLastError));
InternetCloseHandle(hNet);
end else Writeln(SysErrorMessage(GetLastError));
end;

procedure Run;
var
AESKey: TAESKey;
ExKey: TAESExpandedKey;
Stream: TStream;
begin
FillChar(AESKey,Sizeof(AESKey),#0);
if Length(Options.Key) > Sizeof(AESKey) then
move(PChar(Options.Key)^,AESKey,Sizeof(AESKey))
else
move(PChar(Options.Key)^,AESKey,Length(Options.Key));
AESExpandKey(ExKey,AESKey);
if Options.Local = '.' then
begin
if Options.Download then
Stream:= THandleStream.Create(GetStdHandle(STD_OUTPUT_HANDLE))
else
Stream:= THandleStream.Create(GetStdHandle(STD_INPUT_HANDLE));
end else
begin
if Options.Download then
Stream:= TFileStream.Create(Options.Local,fmCreate)
else
Stream:= TFileStream.Create(Options.Local,fmOpenRead or fmShareDenyNone);
end;
try
if Options.Download then
ftpDownload(Stream,Options.Remote,Options.Server,Options.Port,
PChar(Options.User),PChar(Options.Password),ExKey)
else
ftpUpload(Stream,Options.Remote,Options.Server,Options.Port,
PChar(Options.User),PChar(Options.Password),ExKey)
finally
Stream.Free;
end;
end;

procedure Help;
begin
Writeln;
Writeln('Uso: ftpup [opciones]');
Writeln(' --download -d');
Writeln(' Baja el archivo del servidor');
Writeln(' --key');
Writeln(' Clave de cifrado, hasta 32 caracteres.');
Writeln(' --local');
Writeln(' Archivo local. Se puede utilizar un . para indicar la');
Writeln(' entrada o salida estandar');
Writeln(' --password');
Writeln(' Clave de acceso al servidor ftp');
Writeln(' --port');
Writeln(' El puerto del ftp');
Writeln(' --remote');
Writeln(' Ruta del archivo remoto');
Writeln(' --server');
Writeln(' Direccion del servidor ftp');
Writeln(' --user');
Writeln(' Nombre de usuario del ftp');
Writeln;
Writeln('Ejemplos:');
Writeln(' ftpup --key=clave --local=archivo.txt --password=pass ' +
'--user=user --remote=/archivo.txt --server=servidor.com');
Writeln(' Sube al servidor el archivo "archivo.txt".');
Writeln(' ftpup -d --key=clave --local=. --password=pass ' +
'--user=user --remote=/archivo.txt --server=servidor.com');
Writeln(' Descarga el archivo "archivo.txt" y lo muestra por pantalla');
Writeln;
end;


begin
try
if ParamCount > 0 then
begin
GetOpt;
Run;
end else
Help;
except
On E: EGetOptError do
begin
Writeln('Error en el parametro: ',E.Message);
Help;
end;
On E: Exception do
Writeln(E.Message);
end;
end.

ArdiIIa
26-08-2007, 01:57:13
A pesar de que lo has proclamado a los cuatro vientos, no estoy muy seguro de que no tengas nada contra las Indy...:D:D

seoane
26-08-2007, 02:31:15
A pesar de que lo has proclamado a los cuatro vientos, no estoy muy seguro de que no tengas nada contra las Indy...:D:D
Yo contra Indy no tengo nada, pero a veces es como "matar moscas a cañonazos". Con lo simple que queda con wininet, además de pequeño (menos de 100 kb) y, sobre todo, utiliza la misma configuración del proxy que el internet explorer, ¿que mas se puede pedir?.

Todo esto no significa que cuando hay que "sacar el cañón para matar un mosca rebelde", por ejemplo para usar SSL, no utilice Indy.

A todo esto, ¿que te parece el programa Ardilla? :D

Casimiro Notevi
26-08-2007, 03:04:48
Esto sí que es código "no inútil".
Tiene todo lo mejor que existe, las 3 "b": bueno, bonito y barato :)

seoane
26-08-2007, 03:09:46
Esto sí que es código "no inútil".
Tiene todo lo mejor que existe, las 3 "b": bueno, bonito y barato :)
Eso, eso ... así me gusta Casimiro :D

eduarcol
26-08-2007, 19:55:17
Esta muy bueno seoane, con tu permiso lo voy a comenzar a utilizar, voy a crear una especie de servicio para actualizar los sistemas, cuando lo termine lo coloco por aca a ver que tal

Jure
26-08-2007, 21:32:31
... Con lo simple que queda con wininet, además de pequeño (menos de 100 kb) y, sobre todo, utiliza la misma configuración del proxy que el internet explorer, ¿que mas se puede pedir?....

Seoane es un excelente código, como muy bien dices: Simple, corto y práctico. Gracias por compartirlo ;)

seoane
26-08-2007, 22:52:15
Esta muy bueno seoane, con tu permiso lo voy a comenzar a utilizar, voy a crear una especie de servicio para actualizar los sistemas, cuando lo termine lo coloco por aca a ver que tal

Excelente, ya nos contaras que tal te fue :)


Seoane es un excelente código, como muy bien dices: Simple, corto y práctico. Gracias por compartirlo ;)

Gracias :)

seoane
31-08-2007, 13:31:21
Vamos a darle un poco de vida a este hilo ;)

Imaginar que que queremos descifrar un archivo cifrado con ftpup desde linux.

Nada nos impide usar algo como esto:

wine ftpup (Parametros)

Funcionaría, pero por qué no crear una aplicación propia para linux. Vamos a utilizar el compilador freepascal (http://www.freepascal.org/) y el siguiente código:

program cifrar;

uses
SysUtils,
Classes,
AES in 'AES.pas';

type
EGetOptError = class(Exception);

var
Options: record
Decrypt: Boolean;
Dest: String;
Key: String;
Source: String;
end;

function Starts(Sub, Str: String): Boolean;
begin
Result:= SameText(Sub,Copy(Str,1,Length(sub)));
end;

procedure Getopt;
var
i,j: Integer;
Str: String;
begin
FillChar(Options,Sizeof(Options),#0);
for i:= 1 to ParamCount do
begin
Str:= ParamStr(i);
if (Copy(Str,1,2) = '--') and (Length(Str) > 2) then
begin
if SameText(Str,'--Decrypt') then
Options.Decrypt:= TRUE
else if Starts('--Key=',Str) then
begin
if Options.Key = EmptyStr then
Options.Key:= Copy(Str,Length('--Key=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--Source=',Str) then
begin
if Options.Source = EmptyStr then
Options.Source:= Copy(Str,Length('--Source=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end else if Starts('--Dest=',Str) then
begin
if Options.Dest = EmptyStr then
Options.Dest:= Copy(Str,Length('--Dest=') + 1,MAXINT)
else
raise EGetOptError.Create(Str);
end;
end else if (Copy(Str,1,1) = '-') and (Length(Str) > 1) then
begin
Str:= UpperCase(Str);
for j:= 2 to Length(Str) do
begin
if Str[j] = 'D' then
Options.Decrypt:= TRUE
else
raise EGetOptError.Create(Str);
end;
end else
begin
raise EGetOptError.Create(Str);
end;
end;
end;


procedure Run;
var
AESKey: TAESKey;
ExKey: TAESExpandedKey;
Src, Dst: TStream;
Size: int64;
IV: TAESState;
begin
FillChar(AESKey,Sizeof(AESKey),#0);
if Length(Options.Key) > Sizeof(AESKey) then
move(PChar(Options.Key)^,AESKey,Sizeof(AESKey))
else
move(PChar(Options.Key)^,AESKey,Length(Options.Key));
AESExpandKey(ExKey,AESKey);
FillChar(IV,Sizeof(IV),#0);
Src:= TFileStream.Create(Options.Source,fmOpenRead or fmShareDenyNone);
try
Dst:= TFileStream.Create(Options.Dest,fmCreate);
try
if Options.Decrypt then
begin
if Src.Read(Size,Sizeof(Size)) = Sizeof(Size) then
begin
AESDecryptStreamCBC(IV,Src,Dst,ExKey);
if (Size > 0) and (Src.Size > Size) then
Dst.Size:= Size;
end;
end else
begin
Size:= Src.Size;
Dst.Write(Size,Sizeof(Size));
AESEncryptStreamCBC(IV,Src,Dst,ExKey);
end;
finally
Dst.Free;
end;
finally
Src.Free;
end;
end;

procedure Help;
begin
Writeln;
Writeln('Uso: cifrar [opciones]');
Writeln(' --decrypt -d');
Writeln(' Descifrar (Cifrar es la opcion por defecto)');
Writeln(' --key');
Writeln(' Clave de cifrado, hasta 32 caracteres.');
Writeln(' --source');
Writeln(' Archivo de origen');
Writeln(' --dest');
Writeln(' Archivo de destino.');
Writeln;
Writeln('Ejemplos:');
Writeln(' cifrar --key=clave --source=archivo.txt --dest=archivo.bin');
Writeln(' cifrar -d --key=clave --source=archivo.txt --dest=archivo.bin');
Writeln;
end;


begin
try
if ParamCount > 0 then
begin
GetOpt;
Run;
end else
Help;
except
On E: EGetOptError do
begin
Writeln('Error en el parametro: ',E.Message);
Help;
end;
On E: Exception do
Writeln(E.Message);
end;
end.

Y lo compilamos de la siguiente manera:

fpc -Sd cifrar.pas

El programa resultante permite cifrar y descifrar un archivo utilizando AES 256, siguiendo el mismo formato que el programa ftpup.

Por ejemplo, así cifra:

./cifrar --source=archivo.txt --dest=archivo.bin --key=clave

Y así descifra:

./cifrar -d --source=archivo.bin --dest=archivo.txt --key=clave


PD: La unit AES.pas la puedes conseguir aquí (http://delphi.jmrds.com/?q=node/31)

Casimiro Notevi
31-08-2007, 15:09:12
Estupendo, seoane, lo pruebo en cuanto tenga un momento. (*)






(*) Cuando reciba mi nuevo monitor :)

seoane
31-08-2007, 15:32:15
Estupendo, seoane, lo pruebo en cuanto tenga un momento.

Sera un placer que me digas tu opinión :)


PD: Usa el código que hay ahora, el que puse al principio tenia algunos errores.

Casimiro Notevi
06-03-2008, 22:51:02
Sera un placer que me digas tu opinión :)

PD: Usa el código que hay ahora, el que puse al principio tenia algunos errores.

Un poquito tarde, pero más vale tarde que nunca :D

Lo he probado en mi ubuntu, compilación sin ningún error, el programita funciona perfecto, sin problema alguno y muy rápido.
Lo estoy usando para hacer copias encriptadas y luego subirlas por ftp con tu ftpup.

seoane
06-03-2008, 23:13:14
Excelente :D me encanta que la gente use los programas que hago.

Para saber mas sobre el tema, 4 entradas de mi pagina que tratan sobre AES:


Cifrado AES-256 (http://delphi.jmrds.com/?q=node/31)
Ejemplo de AES utilizando freepascal (http://delphi.jmrds.com/?q=node/40)
Cifrar texto con AES-256 (http://delphi.jmrds.com/?q=node/44)
Subir archivos cifrados a un ftp (http://delphi.jmrds.com/?q=node/39)

BlackDaemon
08-03-2008, 03:03:47
Muchas gracias maestro, algún día entenderé el código que escribes :D
Por el momento probando los binarios, esta muy chiquito :)

Por cierto, no puedes subir un directorio ?


saludos!

seoane
08-03-2008, 15:07:11
Por cierto, no puedes subir un directorio ?

:D Para eso esta el Winrar, Winzip, tar, 7z, .... :D

eduarcol
08-03-2008, 15:24:37
:D Para eso esta el Winrar, Winzip, tar, 7z, .... :D

Mira como seras de malo, ayuda al amigo con codigo no con ideas :D:D

Hablando en serio, en mis poquitisimos tiempos libres estoy creando un programa que suba un respaldo cifrado con el codigo del amigo Seoane asi como lo dije en su momento que lo iba a hacer, pero de a poquito a poquito ya he llegado mas o menos lejos, cuando lo termine y cuando Emilio me reactive el dominio lo subo para que lo vean.

Este es el codigo un poquito chapucero que me he inventado para comprimir un directorio:

cComprimir := 'LHA a ' + RutaRespaldo + ' ' + Ruta + '\*.*';
winexec(PAnsiChar(cComprimir), SW_NORMAL);

Tienes que descargarte el LHA, RutaRespaldo es la ruta y nombre completo del archivo comprimido, y Ruta es el directorio que quieres comprimir.