piątek, 29 maja 2020

C# - SQL Cipher z biblioteką sqlite-net-sqlcipher

W tym poście chciałbym opisać wykorzystanie biblioteki sqlite-net-sqlcipher w projekcie C# WPF.

[Źródło: https://docs.microsoft.com/en-us/dotnet/]

Program:


Dokładniejsze informacje o bibliotece można znaleźć w serwisie Github ( https://github.com/sqlcipher/sqlite-net ). 

Z opisanej biblioteki najbardziej potrzebna i warta uwagi wydaje mi się opcja SQLCipher, która pozwoli na dosyć zaawansowane zabezpieczenie bazy danych przed odczytem.

Na samym początku przedstawię widok bazy z tymi samymi danymi. Jedna zabezpieczona przed dostępem. Druga dostępna bez żadnych zabezpieczeń. Do odczytu wykorzystam program Atom.

Poniżej screen z prostego odczytu bazy danych niezabezpieczonej:


Jak można zaobserwować na obrazku powyżej struktura bazy danych jak i dostępne dane są widoczne i dostępne w bardzo łatwy sposób.

Poniżej screen z prostego odczytu bazy danych zabezpieczonej:


Jak teraz można zaobserwować baza danych jest kompletnie nieczytelna. 

Poniżej sposób przygotowania bazy danych bez zabezpieczenia:

  1. var dataBase= new SQLiteConnection("dataBase1");

W przypadku bazy danych zabezpieczonej ustanowienie połączenia wygląda nieco inaczej:

  1. var options = new SQLiteConnectionString("dataBase1"true, key: "password",
  2.       preKeyAction: db => db.Execute("PRAGMA cipher_default_use_hmac = OFF;"),
  3.       postKeyAction: db => db.Execute("PRAGMA kdf_iter = 128000;"));
  4. var encryptedDb2 = new SQLiteConnection(options);

Strukturę baz danych jest określona w dwóch klasach:

  1. public class Company
  2. {
  3.     [PrimaryKey, AutoIncrement]
  4.     public int Id { get; set; }
  5.     public string CompanyName { get; set; }
  6.     public string CompanyKeyA { get; set; }
  7.     public string CompanyKeyB { get; set; }
  8. }
  9.  
  10. public class Valuation
  11. {
  12.     [PrimaryKey, AutoIncrement]
  13.     public int Id { get; set; }
  14.     [Indexed]
  15.     public int CompanyId { get; set; }
  16.     public DateTime Time { get; set; }
  17. }

Aby stworzyć tablicę wewnątrz bazy danych należy wywołać następującą komendę:

  1. encryptedDb2.CreateTable<Company>();
  2. encryptedDb2.CreateTable<Valuation>();

W celu odczytania danych z bazy i załadowaniu ich do kontrolki DataGrid można posłużyć się następującą funkcją:

  1. public void LoadDataFromDB()
  2. {
  3.     List<Company> CompaniesData = new List<Company>();
  4.  
  5.     CompaniesData.Clear();
  6.  
  7.     var query = encryptedDb2.Table<Company>();
  8.  
  9.     foreach (var stock in query)
  10.     {
  11.         CompaniesData.Add(new Company()
  12.         {
  13.             Id = company.Id,
  14.             CompanyName = company.CompanyName,
  15.             CompanyKeyA = company.CompanyKeyA,
  16.             CompanyKeyB = company.CompanyKeyB
  17.         });
  18.     }
  19.  
  20.     DG1.ItemsSource = CompaniesData;
  21.     DG1.Items.Refresh();
  22. }

Dane można pobrać także modyfikując rozkaz. Tak aby pobrać dane po określonym parametrze:

  1. var query = encryptedDb2.Table<Company>().Where(=> v.Symbol.StartsWith("T"));

Dodanie wartości do bazy danych:

  1. public void AddStock(SQLiteConnection db, string companyName, string companyKeyA, string companyKeyB)
  2. {
  3.     var company = new Company()
  4.     {
  5.         CompanyName = companyName,
  6.         CompanyKeyA = companyKeyA,
  7.         CompanyKeyB = companyKeyB
  8.     };
  9.     int addVal = db.Insert(company);
  10.  
  11.     LoadDataFromDB();
  12.  
  13.     if(addVal == 0x01)
  14.     {
  15.         MessageBox.Show("Dodano dane do bazy danych""Dodanie danych", MessageBoxButton.OK, MessageBoxImage.Information);
  16.     }
  17.     else
  18.     {
  19.         MessageBox.Show("Poprawnie usunięte dane z pamięci.""Usuwanie danych",MessageBoxButton.OK, MessageBoxImage.Information);
  20.     }
  21. }

Usunięcie wartości:

  1. public void DeleteStock(SQLiteConnection db, int selectedCompanyID, string companyName, string companyKeyA, string companyKeyB)
  2. {
  3.     var company= new Company()
  4.     {
  5.         Id = selectedCompanyID,
  6.         CompanyName = companyName,
  7.         CompanyKeyA = companyKeyA,
  8.         CompanyKeyB = companyKeyB
  9.     };
  10.  
  11.     int value = db.Delete(company);
  12.  
  13.     LoadDataFromDB();
  14.  
  15.     if(value >= 0x01)
  16.     {
  17.         MessageBox.Show("Poprawnie usunięte dane z pamięci.""Usuwanie danych",
  18.                     MessageBoxButton.OK, MessageBoxImage.Information);
  19.     }
  20.     else
  21.     {
  22.         MessageBox.Show("Błąd usuwania danych z pamięci.""Usuwanie danych",
  23.                     MessageBoxButton.OK, MessageBoxImage.Error);
  24.     }
  25. }

Powyżej usunięcie następuje po wprowadzeniu pełnych wartości z kolumn. Poniżej sposób usunięcia wartości na podstawie wartości jednej zmiennej:

  1. private void DeleteValue(string val)
  2. {
  3.     var query = encryptedDb2.Table<Company>().Delete(=> v.CompanyName == val);
  4.     LoadDataFromDB();
  5. }

Edycja wartości:

  1. public void UpdateStock(SQLiteConnection db, int selectedCompanyID, string companyName,
  2.     string companyKeyA, string companyKeyB)
  3. {
  4.     var company = new Company()
  5.     {
  6.         Id = selectedCompanyID,
  7.         CompanyName = companyName,
  8.         CompanyKeyA = companyKeyA,
  9.         CompanyKeyB = companyKeyB
  10.     };
  11.     int UpdateValue = db.Update(company);
  12.  
  13.     LoadDataFromDB();
  14.  
  15.     if (UpdateValue == 0x01)
  16.     {
  17.         MessageBox.Show("Aktualizacja danych przeprowadzone poprawnie.""Aktualizacja danych",
  18.                     MessageBoxButton.OK, MessageBoxImage.Information);
  19.     }
  20.     else
  21.     {
  22.         MessageBox.Show("Błąd aktualizacji danych""Aktualizacja danych",
  23.                     MessageBoxButton.OK, MessageBoxImage.Error);
  24.     }
  25. }