Encryption plugin wide

IBPhoenix have developed an encyption plugin for Firebird 3.x and higher that is compatible with AES128 or AES256. The AES128 is considered to be a "business-grade security", whilst AES256 is "military". Note that AES256 has a performance cost v's AES128.

The plugin is available for Windows (32/64bit) and Linux (32/64bit). If you are interested in MacOSX (32/64bit) version, please contact us.

The plugin is currently in use at a number of sites and shows no problems with performance or reliability. However if you would like to test it, please contact us directly and an appropriate download will be made available.

Note

To use this plugin you need to have CPU that supports the SSE2 instruction set. The AES instruction set is desirable, but optional. For Linux also make sure that the libtommath libraries are installed. Also GLIBC_2.14+ is required.

Licensing

On purchase of an appropriate server license from our store a download of the software will be made available.

A server license will allow you to encrypt multiple databases on that server. Should you wish to use the encryption plugin with an embedded application or for deployment to multiple servers you can purchase an unlimited use license (this also includes upgrades and support).

Installation

Unzip the encryption libraries and configuration files into $(root_dir)/plugins

Create a keyfile somewhere, e.g:

C:\MyKey.txt or on Posix /opt/firebird/MyKey.txt

Now insert your chosen key into it e.g. the text string TestKey.

Then in the file /plugins/KeyFile.conf you can set a server wide key using the parameter:

KeyFile = C:\MyKey.txt or /opt/firebird/MyKey.txt.

Alternatively entries for database dedicated key files can be specified for each database individually using the format <key name> = <key file>.

For example suppose you have two databases testa.fdb and testb.fdb that you want to encrypt and decrypt with a separate key. Make a name up for each key and point each key name to the file holding the relevant key:

Key4TestA = C:\Key4TestA.jpg
Key4TestB = C:\Key4TestB.txt

If dedicated key names are configured, the name of the key must be supplied in the optional KEY parameter of the encrypt and decrypt statements.

Another alternative is to configure the keyfile so the plugin will send a request to the user application for the keyfile name and path (Keyfile = ?) via a database callback.

You also need to set the following in the Keyfile configuration when using a known key file, set DisableCallback to "true". (Make sure you remove the # signs when setting conf parameters):

DisableCallback = true

Tip

Perhaps the securest and simplest way to manage this is to put the key file onto a secure USB stick inserted into database server).

In databases.conf (or firebird.conf) set the following parameter for the appropriate database:

KeyHolderPlugin = KeyFile

Restart the Firebird Server.

Then:

isql> connect 'C:\test\firebird\test.fdb' user 'SYSDBA' password 'whatever';
isql> alter database encrypt with AES128;
isql> commit;

or on Posix:

isql>connect 'localhost:/opt/firebird/test.fdb' user 'SYSDBA' password 'whatever';
isql>alter database encrypt with AES128;
isql>commit;

If using individual keys:

isql> connect 'C:\test\firebird\test.fdb' user 'SYSDBA' password 'whatever';
isql> alter database encrypt with AES128 key TestKey;
isql> commit;

On Posix:

isql>connect 'localhost:/opt/firebird/test.fdb' user 'SYSDBA' password 'whatever';
isql>alter database encrypt with AES128 key TestKey;
isql>commit;

Note

Use AES256 if you are using the AE256 key.

If the encryption library fails to load then this error will be seen:

Statement failed, SQLSTATE = HY024
unsuccessful metadata update
-ALTER DATABASE failed
-Crypt plugin AES256/128 failed to load

There are three ways to verify encryption.

Via isql show db command:

SHOW DB;
Database: test
Owner: SYSDBA
[...]
Database encrypted, crypt thread not complete

By querying the monitoring tables:

select MON$CRYPT_PAGE * 100 / MON$PAGES as ENCRYPTED_PAGES from mon$database;

ENCRYPTED_PAGES
=====================
0

If ZERO is returned, then encryption has completed. Otherwise the value represents the percentage of total pages encrypted.

Or by using gstat (you can use shell to run gstat from isql), With the default -h option the Attributes field and the Key Hash fields will indicate encryption.

Or you can use:

/opt/firebird3/bin/gstat -e test.fdb

Database "/data_store/ssd/ods120/test.fdb"
Database header page information:
        Flags                   0
        Generation              2990307
        System Change Number    0
        Page size               16384
        ODS version             12.0
     [...]
        Creation date           May 24, 2016 12:18:12
        Attributes              force write, encrypted, plugin AES128

    Variable header data:
       Key hash:       X2ZorqRShODg1mhSg5yQhpFxtjk=
       Sweep interval:         0
       *END*
     Data pages: total 159004, encrypted 159004, non-crypted 0
     Index pages: total 16409, encrypted 16409, non-crypted 0
     Blob pages: total 0, encrypted 0, non-crypted 0

Should show that the database has been encrypted.

Note

You can also use the fb_info_crypt_state item for the isc_database_info() API call to check the encryption status of the database too.

To decrypt use:

isql> alter database decrypt;

Application Key

Rather than providing the decryption key in a file, it is possible to provide the decryption key directly within your application using the relevant Firebird API calls. To do this, change the declaration of KeyHolderPlugin in databases.conf (or Firebird.conf) from KeyFile to Callback. This will allow you to provide the crypt key directly from the application.

To check if the configuration is correct, you can look at the data, received by the application callback in the first two parameters. The data will be "AES128.<something>" for old versions of plugin and "AES256/128" for version 1.2+

If you can see that, then the configuration is correct and the application can send the crypt key. If you still see "KeyFile.<something>", the configured key holder is still KeyFile and that is a file name, not the key itself.

Currently we have examples for Delphi, C++ and .NET.

Full Delphi / Free Pascal example

Program create;
{$APPTYPE CONSOLE}

uses
  Sysutils,Firebird,Windows;

var
st : IStatus;
master : IMaster;
util : IUtil;
dpb : IXpbBuilder;
prov : IProvider;
att : IAttachment;
tra : ITransaction;

procedure PrintError(s : IStatus);
var
maxMessage : Integer;
outMessage : PAnsiChar;
begin
maxMessage := 256;
outMessage := PAnsiChar(StrAlloc(maxMessage));
util.formatStatus(outMessage, maxMessage, s);
writeln(outMessage);
StrDispose(outMessage);
end;

type
IKeyCallback = class(ICryptKeyCallbackImpl)
function callback(dataLength: Cardinal; data: Pointer; bufferLength: Cardinal; buffer: Pointer): Cardinal; override;
end;

function IKeyCallback.callback(dataLength: Cardinal; data: Pointer; bufferLength: Cardinal; buffer: Pointer): Cardinal;
var Key: PAnsiChar;
    KeyLength:Integer;
begin
  KeyLength := 0;
If (bufferLength > 0) and (buffer <> nil) then
begin
    WriteLn('Sending key');
    Key := '0123456789';
    KeyLength := Length(Key);
    WriteLn('got key request: ', PAnsiChar(Data):dataLength);
    Move(Key^, buffer^, KeyLength);
end;
Result := KeyLength;
end;

var param1:String;

begin
  if ParamCount>0 then
  begin
    param1 := ParamStr(1);
  end;
  if param1<>'open' then
    param1 := 'create';

WriteLn('starting...('+param1+')');
master := fb_get_master_interface;
if master = nil then
     begin
      WriteLn('master is nil');
      Exit;
     end;
    Write('getting util interface...');
    util := master.getUtilInterface;
    WriteLn('done.');

    Write('getting status and dispatcher...');
st := master.getStatus;
prov := master.getDispatcher;
    WriteLn('done.');
if (util = nil) or (st = nil) or (prov = nil) then
begin
WriteLn('null pointers');
Exit;
end;

try
prov.setDbCryptCallback(st, IKeyCallback.create);
Write('Generating DPB...');
//create DPB
dpb := util.getXpbBuilder(st, IXpbBuilder.DPB, nil, 0);
Write('page size...');
dpb.insertInt(st, isc_dpb_page_size, 4 * 1024);
Write('user name...');
dpb.insertString(st, isc_dpb_user_name, 'sysdba');
Write('password...');
dpb.insertString(st, isc_dpb_password, 'masterkey');
WriteLn('done.');

    //create empty database
    if param1='create' then
    begin
att := prov.createDatabase(st, 'fbtests.fdb', dpb.getBufferLength(st), dpb.getBuffer(st));
writeln ('Database fbtests.fdb created');

      //start transaction
      tra := att.startTransaction(st, 0, nil);

      //Encrypt database
      att.execute(st, tra, 0, 'alter database encrypt with aes128', 3,
        nil, nil, nil, nil); // Input parameters and output data not used

      //commit transaction (will close interface)
      tra.commit(st);
      tra := nil;

      WriteLn('Press Enter to continue');
      ReadLn;

      //detach from database
      att.detach(st);
      att := nil;

      //remove unneeded tag from DPB
      if dpb.findFirst(st, isc_dpb_page_size)
        then dpb.removeCurrent(st);

      //attach once again
      att := prov.attachDatabase(st, 'fbtests.fdb', dpb.getBufferLength(st), dpb.getBuffer(st));
      writeln ('Re-attached database fbtests.fdb');

      //start transaction
      tra := att.startTransaction(st, 0, nil);

      //create table
      att.execute(st, tra, 0, 'create table dates_table (d1 date)', 3,
        nil, nil, nil, nil); // Input parameters and output data not used

      //commit transaction retaining
      tra.commitRetaining(st);
      writeln ('Table dates_table created');

      //insert a record into dates_table
      att.execute(st, tra, 0, 'insert into dates_table values (CURRENT_DATE)', 3,
        nil, nil, nil, nil); // Input parameters and output data not used

      //commit transaction (will close interface)
      tra.commit(st);
      tra := nil;

      writeln ('Record inserted into dates_table');
    end

    //open database
    else if param1='open' then
    begin
      att := prov.attachDatabase(st, 'fbtests.fdb', dpb.getBufferLength(st), dpb.getBuffer(st));
      writeln ('Database fbtests.fdb Opened');
      exit;
    end;

//detach from the database (will close interface)
att.detach(st);
att := nil;

dpb.dispose;
dpb := nil;
except
on e: FbException do PrintError(e.getStatus);
end;

prov.release;
end.

Note

The Firebird referred to in the "uses" section is the Firebird.pas that can be found in the include Firebird directory of the Firebird installation.

Another Delphi example

type
  IKeyCallback = class(ICryptKeyCallbackImpl)
  private
    fKey : String;
    function callback(dataLength: Cardinal; data: Pointer; bufferLength: Cardinal; buffer: Pointer): Cardinal; override;
  end;


function IKeyCallback.callback(dataLength: Cardinal; data: Pointer; bufferLength: Cardinal; buffer: Pointer): Cardinal;
Var b:TBytes;
begin
  b:=TEncoding.ASCII.GetBytes(fKey);
  If (bufferLength > 0) and (buffer <> nil) then begin
    Result := Length(b);
    Move(b[0],buffer^,Result);
  end else
    Result := 0;
end;
{
function TFXClientLibrary.Call_attach_database(Const db_name,db_crypt_key:String;Const db_handle:PISC_DB_HANDLE;Const parm_buffer_length:FXShort;Const parm_buffer:PFXByte): ISC_STATUS;
Var key:IKeyCallback;
    cb:Pointer ABSOLUTE key;
    lDBN:FXShort;
    sDBN:TBytes;
Begin
  sDBN := TEncoding.Convert(TEncoding.Default, TEncoding.ANSI, BytesOf(db_name));
  lDBN := length(sDBN);

  if (db_crypt_key<>EmptyStr)and(Assigned(fdatabase_crypt_callback)) then Begin
    key:=IKeyCallback.create;key.fKey:=db_crypt_key;
Result:=fdatabase_crypt_callback(@fStatusVector,cb);
    if Result>0 Then
      Exit;
    end;

  Result:=fisc_attach_database( @fStatusVector,
                                   lDBN,
                                   PFXByte(sDBN),
                                   db_handle,
                                   parm_buffer_length,
                                   parm_buffer
                                   );
  // key is released
end;

C/C++ Code example of how to use the encryption plugin and pass the key from a client application.

Or alternatively you can use the following new function in ibase.h:

/*******************************************/
/* Set callback for database crypt plugins */
/*******************************************/
ISC_STATUS ISC_EXPORT fb_database_crypt_callback(ISC_STATUS*,
    FB_NAMESPACE_USE(Firebird, ICryptKeyCallback*));
#ifdef __cplusplus
}    /* extern "C" */
#endif

Note

The second parameter is declared as void*, because of the use of namespace Firebird (where the interfaces live) breaks the old-style ibase.h. This can cause problems when passing some implementations of ICallback due to incorrect type casts.

To avoid it one should first cast the pointer to the implementation of ICallback* like this:

CryptKey key;
ICryptKeyCallback* cb = &key;
fb_database_crypt_callback(status, cb);

Doing the following directly:

CryptKey key;
fb_database_crypt_callback(status, &key);

may cause a segmentation fault.

Example of a Visual C++ Class that uses fb_database_crypt_callback

This class can be compiled with Visual C++ and be used with C++Builder which lacks proper Template support.

Note

Note the loading of gds32.dll, normally the dll loaded would be fbclient.dll while the main application (because because its using IBX) will use gds32.dll.

Because we are using a global thread-local variable for legacy callback into the Firebird client we must make sure only one firebird client dll is loaded and that dll must be gds32.dll

CryptKey key;

typedef ISC_STATUS(__stdcall *p_fb_database_crypt_callback)(ISC_STATUS*, void*);

extern "C"
int __declspec(dllexport) SetKey1(char *szkey, int len )
{
    ICryptKeyCallback* cb = &key;
    HMODULE hGDS32 = NULL;
    p_fb_database_crypt_callback pSetCallback = NULL;

    strncpy_s(szKey, szkey, len);
    szKey[len] = NULL;

    try
    {
        ISC_STATUS status;
        ISC_STATUS statusArray[20];

        hGDS32 = LoadLibraryA("gds32.dll");
        if(hGDS32==NULL)
            return -2;

        pSetCallback = (p_fb_database_crypt_callback)GetProcAddress(hGDS32, "fb_database_crypt_callback");
        if (pSetCallback == NULL)
            throw "GetProcAddress error";

        status = pSetCallback(statusArray, (void*)cb);
        char szMsg[64],szRet[16];

        //strcpy_s(szMsg, "fb_database_crypt_callback returned ");
        //_itoa_s(status, szRet, 10);
        //strcat_s(szMsg,szRet);
        //MessageBoxA(NULL, szMsg, "SetColor", MB_OK);

        FreeLibrary(hGDS32);

        return (int)status;
    }
    catch (...)
    {
        if(hGDS32) FreeLibrary(hGDS32);
        return -1;
    }
}

.NET needs the ADO.NET provider 5.11.0.0, and then you can use this code.

Note

In the configuration files Callback.conf and KeyFile.conf it is now possible to use the parameter ShareKey, where a key received from a client application will be shared between connections in SuperServer mode. When set to false (default is true) it will then require a key for each database attachment:

ShareKey=false

Backup and Restore

The crypt plugin also supports the backup and restore of an encrypted Firebird database using either the Firebird Service Manager or Gbak

Examples:

This is a typical backup command using gbak:

gbak -b -user sysdba -pas masterkey -keyholder KeyFile -crypt AES128 -verify empcryptAES128 /path/to/backupdir/empcryptAES128.fbkz

And this would be a typical restore command:

gbak -v -c -user sysdba -pas masterkey -keyholder KeyFile -crypt AES128 /path/to/backupdir/empcryptAES128.fbkz /path/to/databases/empcrypt_AES128_restored.fdb


for fbsvcmgr typical commands would be like this:

fbsvcmgr localhost:service_mgr key_holder KeyFile action_backup \
        dbname empcryptAES128 bkp_file $TMP/empcryptAES128.fbkz \
        bkp_keyholder KeyFile bkp_keyname ServerKeyAES128 bkp_crypt AES128 bkp_zip


fbsvcmgr localhost:service_mgr key_holder KeyFile action_restore res_replace \
        dbname $TMP/empcryptAES128-restored.fdb \
        res_keyholder KeyFile bkp_file $TMP/empcryptAES128.fbkz