-
Asking this here because this is, I'm pretty sure, not an issue with SQLite3MC at all, so issues to my knowledge isn't the most appropriate forum for it. I'm trying to add salting to my cipher implementation. However, I can't seem to quite get encryption/decryption right. I've looked at the various cipher implementations and they all handle this differently in non-legacy mode: some use OTKs, some are pure legacy ciphers, some copy the SQLite file header into static int encrypt_page(void* cipher, int page, unsigned char* data, int len, int reserved) {
struct xchacha20_cipher* c = (struct xchacha20_cipher*)cipher;
if (c->counter == ULLONG_MAX) {
sqlite3_log(SQLITE_ERROR, "Nonce overflow in encryption/decryption routine; aborting");
return SQLITE_ABORT;
}
if (reserved != 40 || (len - reserved) < 1 || page < 0) {
return SQLITE_IOERR_CORRUPTFS;
}
const int actual_size = len - reserved;
uint8_t nonce[24];
memset(nonce, 0, 24);
const uint64_t n = c->counter;
const uint64_t n2 = (uint64_t)page;
nonce[8] = (n >> (8 * 0)) & 0xff;
nonce[9] = (n >> (8 * 1)) & 0xff;
nonce[10] = (n >> (8 * 2)) & 0xff;
nonce[11] = (n >> (8 * 3)) & 0xff;
nonce[12] = (n >> (8 * 4)) & 0xff;
nonce[13] = (n >> (8 * 5)) & 0xff;
nonce[14] = (n >> (8 * 6)) & 0xff;
nonce[15] = (n >> (8 * 7)) & 0xff;
nonce[16] = (n2 >> (8 * 0)) & 0xff;
nonce[17] = (n2 >> (8 * 1)) & 0xff;
nonce[18] = (n2 >> (8 * 2)) & 0xff;
nonce[19] = (n2 >> (8 * 3)) & 0xff;
nonce[20] = (n2 >> (8 * 4)) & 0xff;
nonce[21] = (n2 >> (8 * 5)) & 0xff;
nonce[22] = (n2 >> (8 * 6)) & 0xff;
nonce[23] = (n2 >> (8 * 7)) & 0xff;
if (page == 1) {
crypto_aead_lock(data + 24, &data[24 + actual_size + 24], c->key, nonce, NULL, 0, data + 24, actual_size - 24);
for (int i = 0; i < 24; ++i) {
data[24 + actual_size + i] = nonce[i];
}
memcpy(data, c->salt, 16);
} else {
crypto_aead_lock(data, &data[actual_size + 24], c->key, nonce, NULL, 0, data, actual_size);
for (int i = 0; i < 24; ++i) {
data[actual_size + i] = nonce[i];
}
}
c->counter++;
return SQLITE_OK;
}
static int decrypt_page(void* cipher, int page, unsigned char* data, int len, int reserved, int hmac_check) {
struct xchacha20_cipher* c = (struct xchacha20_cipher*)cipher;
if (reserved != 40 || ((len - reserved) < 1) || page < 0) {
return SQLITE_IOERR_CORRUPTFS;
}
const int actual_size = len - reserved;
uint8_t nonce[24];
uint8_t mac[16];
memset(nonce, 0, 24);
memset(mac, 0, 16);
if (page == 1) {
for (int i = 0; i < 24; ++i) {
nonce[i] = data[24 + actual_size + i];
}
for (int i = 0; i < 16; ++i) {
mac[i] = data[24 + actual_size + 24 + i];
}
if (crypto_aead_unlock(data + 24, mac, c->key, nonce, NULL, 0, data + 24, actual_size - 24) == -1) {
return SQLITE_IOERR_CORRUPTFS;
}
memcpy(data, "SQLite format 3\0", 16);
} else {
for (int i = 0; i < 24; ++i) {
nonce[i] = data[actual_size + i];
}
for (int i = 0; i < 16; ++i) {
mac[i] = data[actual_size + 24 + i];
}
if (crypto_aead_unlock(data, mac, c->key, nonce, NULL, 0, data, actual_size) == -1) {
return SQLITE_IOERR_CORRUPTFS;
}
}
return SQLITE_OK;
} I'm a bit confused and uncertain what I should try next. I could stick with a legacy cipher implementation, but that would make salting pretty much impossible and would make keys pretty much completely deterministic, which I don't want. I could reserve an extra 16 bytes and put the salt at the end of the page, but that also seems like a weird solution and an unnecessary hack given that SQLite3MC already provides this (but it is an option I suppose...). Am I just not copying something, or what should I change to get this to work right? |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 3 replies
-
You are right. It is not an issue with SQLite3MC. Therefore opening a discussion was the right thing to do.
Why don't you use one of the cipher schemes provided by SQLite3MC? The cipher schemes
Well, different cipher schemes use different approaches. If a cipher scheme uses a general key salt, it is stored at the beginning of the database header (´data[0..15]` of database page 1) - this is the only space where up to 16 bytes can be stored, because the unencrypted content of those bytes is fixed. On some platforms the first 16 bytes of the database header need to stay unencrypted. In such a case the application needs to store the salt elsewhere and has to provide it via URI parameter on opening the database.
In short, your implementation is broken. And I doubt that it works in any journal mode.
So, the number of reserved bytes is 40 - 16 bytes for a MAC and 24 bytes for nonce, if I interpreted the rest of your code
Up to here the code seems to be ok, although I don't understand why you set the first 8 bytes of the nonce always to zero.
The pointer to the MAC is wrong! Keep in mind that you use 40 reserved bytes, but The correct expression would be
Same problem here: the correct expression would be
Again: correct expression would be
Same here: correct expression
Indeed you could stick to one of the already implemented cipher schemes. However, you are not forced to use a legacy cipher. Legacy ciphers are supported, so that databases created by legacy applications using the original implementation of the corresponding cipher can be accessed. If compatibility with legacy applications is not an issue for you, you can use the (preferred) non-legacy mode of the implemented cipher schemes in SQLite3MC.
This is nonsense. As already said the cipher schemes On the contrary, your implementation seems to be somewhat deterministic: you use a (most likely deterministic) counter and the (definitely deterministic) page number for the nonce.
Not sure, what you are referring to. Storing the general key salt in the reserved area of a page would be a complete waste of page space, because this salt is used only once for deriving the key. SQLite3MC uses the first 16 bytes of the database header for this purpose.
Adjust the buffer addresses as stated in my comments, and it should work. But to be frank, I would strongly recommend you to use one of the existing cipher schemes. |
Beta Was this translation helpful? Give feedback.
-
So for the salt, I use random bytes, and then I derive the key with argon2I. It's a password hash algorithm, sure, but it also can be used as a KDF, so I thought why not? As for the nonce being zero for the first 8 bytes, I did that because I didn't want to generate 8 random bytes for every nonce as that would exhaust most platform RNG pools (i.e. on Linux) especially for a large number of pages/encryption operations. The nonce may be deterministic, but it's not private, either. By being deterministic previously, I meant that the key would be deterministic for the KDF given input As for the offsets, I did that since I saw the built-in ciphers doing something like that, and I wasn't sure if "encrypt all the things" is what I should be doing or if I only need to encrypt bytes |
Beta Was this translation helpful? Give feedback.
-
I would agree with this if my algorithm was hand-coded. It is not. The algorithm is Argon2, a winner of the 2015 password hashing competition (PHC). Although it is primarily used for hashing passwords, it can also be used as a KDF. I may however switch to the built-in ciphers just so that everything integrates smoother. I would encourage you to switch to Argon2 at some point; see the OWASP recommendations on password storage for reasons why and good parameters to use if you aren't already using them. |
Beta Was this translation helpful? Give feedback.
-
To be clear, to set the default cipher to something like ASCON-128, do I define |
Beta Was this translation helpful? Give feedback.
-
On the currently release this doesn't appear to be correct, unless I'm mistaking the code or something. There's a Edit: my bad, I misinterpreted the directives. :P Might need more caffeine. :D Thank you for all your help, I greatly appreciate it. |
Beta Was this translation helpful? Give feedback.
In principle, yes. However, for all built-in ciphers the corresponding symbol
HAVE_CIPHER_*=1
is defined. You have to setHAVE_CIPHER_*=0
for all ciphers you want to exclude.