Skip to content

Plaintext header for sqleet similar to sqlcipher #209

@fhilgers

Description

@fhilgers

Currently IOS pretty much always instantly kills the app on suspension when the sqlite database is stored in a shared container for app groups.

The following patch might be useful for others and it would be awesome if some form of it could be included in the main repository. The behavior is pretty similar to cipher_plaintext_header_size for sqlcipher, with the difference, basically:

  • If salt is provided with a key on creation, we don't need to store the salt instead of the header (SQLite format 3)
  • If the header does not include a salt, the salt is required to be provided with the key.

This currently only works only with raw keys but could surely be adapted to work for passphrases, too. Probably with a salt macro instead. I'd be happy to implement the missing pieces if you decide that this functionality belongs into the main repo.

diff --git a/src/cipher_chacha20.c b/src/cipher_chacha20.c
index 67dca9c..18b5fa5 100644
--- a/src/cipher_chacha20.c
+++ b/src/cipher_chacha20.c
@@ -53,6 +53,7 @@ typedef struct _chacha20Cipher
   int     m_legacyPageSize;
   int     m_kdfIter;
   int     m_keyLength;
+  int     m_plaintext_header;
   uint8_t m_key[KEYLENGTH_CHACHA20];
   uint8_t m_salt[SALTLENGTH_CHACHA20];
 } ChaCha20Cipher;
@@ -67,6 +68,7 @@ AllocateChaCha20Cipher(sqlite3* db)
     chacha20Cipher->m_keyLength = KEYLENGTH_CHACHA20;
     memset(chacha20Cipher->m_key, 0, KEYLENGTH_CHACHA20);
     memset(chacha20Cipher->m_salt, 0, SALTLENGTH_CHACHA20);
+    chacha20Cipher->m_plaintext_header = 0;
   }
   if (chacha20Cipher != NULL)
   {
@@ -99,6 +101,7 @@ CloneChaCha20Cipher(void* cipherTo, void* cipherFrom)
   chacha20CipherTo->m_legacyPageSize = chacha20CipherFrom->m_legacyPageSize;
   chacha20CipherTo->m_kdfIter = chacha20CipherFrom->m_kdfIter;
   chacha20CipherTo->m_keyLength = chacha20CipherFrom->m_keyLength;
+  chacha20CipherTo->m_plaintext_header = chacha20CipherFrom->m_plaintext_header;
   memcpy(chacha20CipherTo->m_key, chacha20CipherFrom->m_key, KEYLENGTH_CHACHA20);
   memcpy(chacha20CipherTo->m_salt, chacha20CipherFrom->m_salt, SALTLENGTH_CHACHA20);
 }
@@ -146,6 +149,11 @@ GenerateKeyChaCha20Cipher(void* cipher, char* userPassword, int passwordLength,
   int bypass = 0;

   int keyOnly = 1;
+  int needSalt = 0;
+  if (cipherSalt != NULL && !memcmp(cipherSalt, SQLITE_FILE_HEADER, 16))
+  {
+    needSalt = 1;
+  }
   if (rekey || cipherSalt == NULL)
   {
     chacha20_rng(chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
@@ -165,8 +173,9 @@ GenerateKeyChaCha20Cipher(void* cipher, char* userPassword, int passwordLength,
     {
       /* Binary key (and salt) */
       case KEYLENGTH_CHACHA20 + SALTLENGTH_CHACHA20:
-        if (!keyOnly)
+        if (!keyOnly || needSalt)
         {
+          chacha20Cipher->m_plaintext_header = 1;
           memcpy(chacha20Cipher->m_salt, zRaw + KEYLENGTH_CHACHA20, SALTLENGTH_CHACHA20);
         }
         /* fall-through */
@@ -189,8 +198,9 @@ GenerateKeyChaCha20Cipher(void* cipher, char* userPassword, int passwordLength,
         if (sqlite3mcIsHexKey(zRaw, nRaw) != 0)
         {
           sqlite3mcConvertHex2Bin(zRaw, 2 * KEYLENGTH_CHACHA20, chacha20Cipher->m_key);
-          if (!keyOnly)
+          if (!keyOnly || needSalt)
           {
+            chacha20Cipher->m_plaintext_header = 1;
             sqlite3mcConvertHex2Bin(zRaw + 2 * KEYLENGTH_CHACHA20, 2 * SALTLENGTH_CHACHA20, chacha20Cipher->m_salt);
           }
           bypass = 1;
@@ -202,6 +212,18 @@ GenerateKeyChaCha20Cipher(void* cipher, char* userPassword, int passwordLength,
     }
   }

+  if (needSalt && !chacha20Cipher->m_plaintext_header)
+  {
+    memset(chacha20Cipher->m_key, 0, KEYLENGTH_CHACHA20);
+    memset(chacha20Cipher->m_salt, 0, SALTLENGTH_CHACHA20);
+    chacha20Cipher->m_plaintext_header = 0;
+    sqlite3_log(
+      SQLITE_ERROR,
+      "you must provide a salt via the raw key API `PRAGMA key = \"raw:...\"` for a database with plaintext header."
+    );
+    return;
+  }
+
   if (!bypass)
   {
     fastpbkdf2_hmac_sha256((unsigned char*)userPassword, passwordLength,
@@ -246,7 +268,10 @@ EncryptPageChaCha20Cipher(void* cipher, int page, unsigned char* data, int len,
     chacha20_xor(data + offset, n - offset, otk + 32, data + n, counter + 1);
     if (page == 1)
     {
-      memcpy(data, chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
+      if (!chacha20Cipher->m_plaintext_header)
+      {
+        memcpy(data, chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
+      }
     }
     poly1305(data, n + PAGE_NONCE_LEN_CHACHA20, otk, data + n + PAGE_NONCE_LEN_CHACHA20);
   }
@@ -264,7 +289,10 @@ EncryptPageChaCha20Cipher(void* cipher, int page, unsigned char* data, int len,
     chacha20_xor(data + offset, n - offset, otk + 32, nonce, counter + 1);
     if (page == 1)
     {
-      memcpy(data, chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
+      if (!chacha20Cipher->m_plaintext_header)
+      {
+        memcpy(data, chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
+      }
     }
   }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions