РУС Site Map Contact Us Home
E-mail Newsletter
Subscribe to get informed about
Clever Components news.

Your Name:
Your Email:
 
SUBSCRIBE
 
Previous Newsletters
 




Products Articles Downloads Order Support
Customer Portal   

Hashing FTP user passwords

Submitted on February 26, 2005

Introduction

When implementing an FTP server, it is important to keep the user accounts information secured. In most Delphi FTP Server components information about the user passwords comes as a clean text. The task of the encoding and encrypting of the password information should be solved by the end-user when implementing the FTP server application. This article demonstrated a sample of storing passwords as MD5 hash values.

As an example, let's take the TclFtpServer component from the Internet Components - Clever Internet Suite v 5.0 library. Also you can apply this approach to the TIdFTPServer component from Indy9 or Indy10 by http://www.indyproject.org/ . In case of using Indy10 the task of validating FTP passwords is simplified since this version of the TIdFTPServer supports the MD5 (and also MD4 and SHA1) OTP (One Time Password) authentication method.

Calculating of the password hash

The password hash can be calculated by using the standard Microsoft Crypto Api library. The sample code which implements the MD5 hashing algorithm can be seen below:

function GetPasswordHash(const APassword: string): string;
var
   context: HCRYPTPROV;
   hash: HCRYPTHASH;
   data: PByte;
   hashSize, dwordSize: DWORD;
begin
   if not CryptAcquireContext(@context, nil, nil, PROV_RSA_SCHANNEL, 0) then
   begin
      if not CryptAcquireContext(@context, nil, nil, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET) then
      raise Exception.CreateFmt('CryptAcquireContext error %d', [GetLastError()]);
   end;
   try
      if not CryptCreateHash(context, CALG_MD5, 0, 0, @hash) then
         raise Exception.CreateFmt('CryptCreateHash error %d', [GetLastError()]);
      if not CryptHashData(hash, Pointer(APassword), Length(APassword), 0) then
         raise Exception.CreateFmt('CryptHashData error %d', [GetLastError()]);
      data := nil;
      try
         dwordSize := SizeOf(DWORD);
         if not CryptGetHashParam(hash, HP_HASHSIZE, @hashSize, @dwordSize, 0) then
            raise Exception.CreateFmt('CryptGetHashParam error %d', [GetLastError()]);
         GetMem(data, hashSize);
         if not CryptGetHashParam(hash, HP_HASHVAL, data, @hashSize, 0) then
            raise Exception.CreateFmt('CryptGetHashParam error %d', [GetLastError()]);
         SetLength(Result, hashSize);
         system.Move(data^, Pointer(Result)^, hashSize);
      finally
         FreeMem(data);
         CryptDestroyHash(hash);
      end;
   finally
      CryptReleaseContext(context, 0);
   end;
end;

You can choose the used hash algorithm specifying the corresponding parameter for the CryptCreateHash CryptoApi function. The number of supported algorithms depends on the cryptographic provider being used. You can learn more about cryptographic providers and supported algorithms from the MSDN Microsoft Knowledge base.

Storing the hashed password

Let's go ahead and consider a task of modifying user accounts at the FTP server side.

Using the Edit Box above you can create new users and also modify existing ones.

Please note, we cannot see the passwords for the users which have been created previously since their password information is represented by the cryptographic hash. This approach is implemented in most professional systems including MS Windows User accounts.

The code-behind for this dialog looks trivial except for two methods: loading and storing the account details.

Since we cannot see passwords for existing users, we must keep all password hashes, which have been entered previously:

procedure TfrmUsers.LoadData(Accounts: TclUserAccounts);
var
   i: Integer;
begin
   FAccounts.Assign(Accounts);
   for i := 0 to FAccounts.Count - 1 do
   begin
      FAccounts[i].Password := '';
   end;
   FillListbox(0);
end;

This method copies the user accounts to the temporary accounts collection in order to edit it with the Edit Box. We must clean the password values for all users from temporary accounts collection for separating users which are already exist from newly created users.

procedure TfrmUsers.StoreData(Accounts: TclUserAccounts);
var
   i: Integer;
   item: TclUserAccount;
   psw: string;
begin
   for i := Accounts.Count - 1 downto 0 do
   begin
      if (FAccounts.AccountByUserName(Accounts[i].UserName) = nil) then
      begin
         Accounts.Delete(i);
      end;
   end;

   for i := 0 to FAccounts.Count - 1 do
   begin
      item := Accounts.AccountByUserName(FAccounts[i].UserName);
      if (item = nil) then
      begin
         item := Accounts.Add();
         item.Assign(FAccounts[i]);
         psw := GetPasswordHash(item.Password);
      end else
      begin
         psw := item.Password;
         item.Assign(FAccounts[i]);
         if (item.Password <> '') then
         begin
            psw := GetPasswordHash(item.Password);
         end;
      end;
      item.Password := psw;
   end;
end;

This method synchronizes the user accounts from the temporary FAccounts collection with the ones available on the FTP server. We cannot call the Assign method for the entire collection. In such case we can lose the password hashes calculated during the previous session of editing FTP accounts.

Validating the password hash

When a FTP client is about to connect to the FTP server, it provides both the user name and the password information by using the 'USER' and 'PASS' commands correspondingly. In case of using the OTP approach for sending-receiving password values, the password value comes from a client as a calculated MD4, MD5 or SHA1 hash value. In such a case we do not need to do anything for preparing the retrieved password before validating it. But in case of sending passwords as a clean text, it is necessary to apply the same hash algorithm as the one used for storing the user accounts collection.

In case of using the TclFtpServer from Internet Components - Clever Internet Suite v 5.0, the default password validation algorithm can be overridden using the OnAuthenticate component event. The event handler for the OnAuthenticate component event looks like the following:

procedure TMainForm.clFtpServer1Authenticate(Sender: TObject;
   Account: TclUserAccount; const APassword: String; var IsAuthorized,
   Handled: Boolean);
begin
   IsAuthorized := (GetPasswordHash(APassword) = Account.Password);
   Handled := True;
end;

Source Code and working sample

A full source code of all classes described in this article can be downloaded at FtpServer.zip

This code is constantly refined and improved and your comments and suggestions are always welcome.

With best regards,
Sergey Shirokov 
Clever Components team
Please feel free to Contact Us

    Copyright © 2000-2017