using System; using System.Diagnostics; using System.Text; using u8 = System.Byte; using u16 = System.UInt16; using Pgno = System.UInt32; namespace Community.CsharpSqlite { using sqlite3_int64 = System.Int64; using sqlite3_stmt = Sqlite3.Vdbe; using System.Security.Cryptography; using System.IO; public partial class Sqlite3 { /* ************************************************************************* ** Included in SQLite3 port to C#-SQLite; 2010 Noah B Hart, Diego Torres ** C#-SQLite is an independent reimplementation of the SQLite software library ** ************************************************************************* */ /* ** SQLCipher ** crypto.c developed by Stephen Lombardo (Zetetic LLC) ** sjlombardo at zetetic dot net ** http://zetetic.net ** ** Copyright (c) 2009, ZETETIC LLC ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** * Neither the name of the ZETETIC LLC nor the ** names of its contributors may be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY ** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** */ /* BEGIN CRYPTO */ #if SQLITE_HAS_CODEC //#include //#include //#include //#include //#include "sqliteInt.h" //#include "btreeInt.h" //#include "crypto.h" #if CODEC_DEBUG || TRACE //#define CODEC_TRACE(X) {printf X;fflush(stdout);} static void CODEC_TRACE( string T, params object[] ap ) { if ( sqlite3PagerTrace )sqlite3DebugPrintf( T, ap ); } #else //#define CODEC_TRACE(X) static void CODEC_TRACE( string T, params object[] ap ) { } #endif //void sqlite3FreeCodecArg(void *pCodecArg); public class cipher_ctx {//typedef struct { public string pass; public int pass_sz; public bool derive_key; public byte[] key; public int key_sz; public byte[] iv; public int iv_sz; public ICryptoTransform encryptor; public ICryptoTransform decryptor; public cipher_ctx Copy() { cipher_ctx c = new cipher_ctx(); c.derive_key = derive_key; c.pass = pass; c.pass_sz = pass_sz; if ( key != null ) { c.key = new byte[key.Length]; key.CopyTo( c.key, 0 ); } c.key_sz = key_sz; if ( iv != null ) { c.iv = new byte[iv.Length]; iv.CopyTo( c.iv, 0 ); } c.iv_sz = iv_sz; c.encryptor = encryptor; c.decryptor = decryptor; return c; } public void CopyTo( cipher_ctx ct ) { ct.derive_key = derive_key; ct.pass = pass; ct.pass_sz = pass_sz; if ( key != null ) { ct.key = new byte[key.Length]; key.CopyTo( ct.key, 0 ); } ct.key_sz = key_sz; if ( iv != null ) { ct.iv = new byte[iv.Length]; iv.CopyTo( ct.iv, 0 ); } ct.iv_sz = iv_sz; ct.encryptor = encryptor; ct.decryptor = decryptor; } } public class codec_ctx {//typedef struct { public int mode_rekey; public byte[] buffer; public Btree pBt; public cipher_ctx read_ctx; public cipher_ctx write_ctx; public codec_ctx Copy() { codec_ctx c = new codec_ctx(); c.mode_rekey = mode_rekey; c.buffer = sqlite3MemMalloc( buffer.Length ); c.pBt = pBt; if ( read_ctx != null ) c.read_ctx = read_ctx.Copy(); if ( write_ctx != null ) c.write_ctx = write_ctx.Copy(); return c; } } const int FILE_HEADER_SZ = 16; //#define FILE_HEADER_SZ 16 const string CIPHER = "aes-256-cbc"; //#define CIPHER "aes-256-cbc" const int CIPHER_DECRYPT = 0; //#define CIPHER_DECRYPT 0 const int CIPHER_ENCRYPT = 1; //#define CIPHER_ENCRYPT 1 #if NET_2_0 static RijndaelManaged Aes = new RijndaelManaged(); #else static AesManaged Aes = new AesManaged(); #endif /* BEGIN CRYPTO */ static void sqlite3pager_get_codec( Pager pPager, ref codec_ctx ctx ) { ctx = pPager.pCodec; } static int sqlite3pager_is_mj_pgno( Pager pPager, Pgno pgno ) { return ( PAGER_MJ_PGNO( pPager ) == pgno ) ? 1 : 0; } static sqlite3_file sqlite3Pager_get_fd( Pager pPager ) { return ( isOpen( pPager.fd ) ) ? pPager.fd : null; } static void sqlite3pager_sqlite3PagerSetCodec( Pager pPager, dxCodec xCodec, dxCodecSizeChng xCodecSizeChng, dxCodecFree xCodecFree, codec_ctx pCodec ) { sqlite3PagerSetCodec( pPager, xCodec, xCodecSizeChng, xCodecFree, pCodec ); } /* END CRYPTO */ //static void activate_openssl() { // if(EVP_get_cipherbyname(CIPHER) == null) { // OpenSSL_add_all_algorithms(); // } //} /** * Free and wipe memory * If ptr is not null memory will be freed. * If sz is greater than zero, the memory will be overwritten with zero before it is freed */ static void codec_free( ref byte[] ptr, int sz ) { if ( ptr != null ) { if ( sz > 0 ) Array.Clear( ptr, 0, sz );//memset( ptr, 0, sz ); sqlite3_free( ref ptr ); } } /** * Set the raw password / key data for a cipher context * * returns SQLITE_OK if assignment was successfull * returns SQLITE_NOMEM if an error occured allocating memory * returns SQLITE_ERROR if the key couldn't be set because the pass was null or size was zero */ static int cipher_ctx_set_pass( cipher_ctx ctx, string zKey, int nKey ) { ctx.pass = null; // codec_free( ctx.pass, ctx.pass_sz ); ctx.pass_sz = nKey; if ( !string.IsNullOrEmpty( zKey ) && nKey > 0 ) { //ctx.pass = sqlite3Malloc(nKey); //if(ctx.pass == null) return SQLITE_NOMEM; ctx.pass = zKey;//memcpy(ctx.pass, zKey, nKey); return SQLITE_OK; } return SQLITE_ERROR; } /** * Initialize a new cipher_ctx struct. This function will allocate memory * for the cipher context and for the key * * returns SQLITE_OK if initialization was successful * returns SQLITE_NOMEM if an error occured allocating memory */ static int cipher_ctx_init( ref cipher_ctx iCtx ) { iCtx = new cipher_ctx(); //iCtx = sqlite3Malloc( sizeof( cipher_ctx ) ); //ctx = *iCtx; //if ( ctx == null ) return SQLITE_NOMEM; //memset( ctx, 0, sizeof( cipher_ctx ) ); //ctx.key = sqlite3Malloc( EVP_MAX_KEY_LENGTH ); //if ( ctx.key == null ) return SQLITE_NOMEM; return SQLITE_OK; } /** * free and wipe memory associated with a cipher_ctx */ static void cipher_ctx_free( ref cipher_ctx ictx ) { cipher_ctx ctx = ictx; CODEC_TRACE( "cipher_ctx_free: entered ictx=%d\n", ictx ); ctx.pass = null;//codec_free(ctx.pass, ctx.pass_sz); if ( ctx.key != null ) Array.Clear( ctx.key, 0, ctx.key.Length );//codec_free(ctx.key, ctx.key_sz); if ( ctx.iv != null ) Array.Clear( ctx.iv, 0, ctx.iv.Length ); ictx = new cipher_ctx();// codec_free( ref ctx, sizeof( cipher_ctx ) ); } /** * Copy one cipher_ctx to another. For instance, assuming that read_ctx is a * fully initialized context, you could copy it to write_ctx and all yet data * and pass information across * * returns SQLITE_OK if initialization was successful * returns SQLITE_NOMEM if an error occured allocating memory */ static int cipher_ctx_copy( cipher_ctx target, cipher_ctx source ) { //byte[] key = target.key; CODEC_TRACE( "cipher_ctx_copy: entered target=%d, source=%d\n", target, source ); //codec_free(target.pass, target.pass_sz); source.CopyTo( target );//memcpy(target, source, sizeof(cipher_ctx); //target.key = key; //restore pointer to previously allocated key data //memcpy(target.key, source.key, EVP_MAX_KEY_LENGTH); //target.pass = sqlite3Malloc(source.pass_sz); //if(target.pass == null) return SQLITE_NOMEM; //memcpy(target.pass, source.pass, source.pass_sz); return SQLITE_OK; } /** * Compare one cipher_ctx to another. * * returns 0 if all the parameters (except the derived key data) are the same * returns 1 otherwise */ static int cipher_ctx_cmp( cipher_ctx c1, cipher_ctx c2 ) { CODEC_TRACE( "cipher_ctx_cmp: entered c1=%d c2=%d\n", c1, c2 ); if ( c1.key_sz == c2.key_sz && c1.pass_sz == c2.pass_sz && c1.pass == c2.pass ) return 0; return 1; } /** * Free and wipe memory associated with a cipher_ctx, including the allocated * read_ctx and write_ctx. */ static void codec_ctx_free( ref codec_ctx iCtx ) { codec_ctx ctx = iCtx; CODEC_TRACE( "codec_ctx_free: entered iCtx=%d\n", iCtx ); cipher_ctx_free( ref ctx.read_ctx ); cipher_ctx_free( ref ctx.write_ctx ); iCtx = new codec_ctx();//codec_free(ctx, sizeof(codec_ctx); } /** * Derive an encryption key for a cipher contex key based on the raw password. * * If the raw key data is formated as x'hex' and there are exactly enough hex chars to fill * the key space (i.e 64 hex chars for a 256 bit key) then the key data will be used directly. * * Otherwise, a key data will be derived using PBKDF2 * * returns SQLITE_OK if initialization was successful * returns SQLITE_NOMEM if the key could't be derived (for instance if pass is null or pass_sz is 0) */ static int codec_key_derive( codec_ctx ctx, cipher_ctx c_ctx ) { CODEC_TRACE( "codec_key_derive: entered c_ctx.pass=%s, c_ctx.pass_sz=%d ctx.iv=%d ctx.iv_sz=%d c_ctx.kdf_iter=%d c_ctx.key_sz=%d\n", c_ctx.pass, c_ctx.pass_sz, c_ctx.iv, c_ctx.iv_sz, c_ctx.key_sz ); if ( c_ctx.pass != null && c_ctx.pass_sz > 0 ) { // if pass is not null if ( ( c_ctx.pass_sz == ( c_ctx.key_sz * 2 ) + 3 ) && c_ctx.pass.StartsWith( "x'", StringComparison.OrdinalIgnoreCase ) ) { int n = c_ctx.pass_sz - 3; /* adjust for leading x' and tailing ' */ string z = c_ctx.pass.Substring( 2 );// + 2; /* adjust lead offset of x' */ CODEC_TRACE( "codec_key_derive: deriving key from hex\n" ); c_ctx.key = sqlite3HexToBlob( null, z, n ); } else { CODEC_TRACE( "codec_key_derive: deriving key using AES256\n" ); Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes( c_ctx.pass, c_ctx.iv, 2010 ); c_ctx.key_sz = 32; c_ctx.key = k1.GetBytes( c_ctx.key_sz ); } #if NET_2_0 Aes.BlockSize = 0x80; Aes.FeedbackSize = 8; Aes.KeySize = 0x100; Aes.Mode = CipherMode.CBC; #endif c_ctx.encryptor = Aes.CreateEncryptor( c_ctx.key, c_ctx.iv ); c_ctx.decryptor = Aes.CreateDecryptor( c_ctx.key, c_ctx.iv ); return SQLITE_OK; }; return SQLITE_ERROR; } /* * ctx - codec context * pgno - page number in database * size - size in bytes of input and output buffers * mode - 1 to encrypt, 0 to decrypt * in - pointer to input bytes * out - pouter to output bytes */ static int codec_cipher( cipher_ctx ctx, Pgno pgno, int mode, int size, byte[] bIn, byte[] bOut ) { int iv; int tmp_csz, csz; CODEC_TRACE( "codec_cipher:entered pgno=%d, mode=%d, size=%d\n", pgno, mode, size ); /* just copy raw data from in to out when key size is 0 * i.e. during a rekey of a plaintext database */ if ( ctx.key_sz == 0 ) { Array.Copy( bIn, bOut, bIn.Length );//memcpy(out, in, size); return SQLITE_OK; } MemoryStream dataStream = new MemoryStream(); CryptoStream encryptionStream; if ( mode == CIPHER_ENCRYPT ) { encryptionStream = new CryptoStream( dataStream, ctx.encryptor, CryptoStreamMode.Write ); } else { encryptionStream = new CryptoStream( dataStream, ctx.decryptor, CryptoStreamMode.Write ); } encryptionStream.Write( bIn, 0, size ); encryptionStream.FlushFinalBlock(); dataStream.Position = 0; dataStream.Read( bOut, 0, (int)dataStream.Length ); encryptionStream.Close(); dataStream.Close(); return SQLITE_OK; } /** * * when for_ctx == 0 then it will change for read * when for_ctx == 1 then it will change for write * when for_ctx == 2 then it will change for both */ static int codec_set_cipher_name( sqlite3 db, int nDb, string cipher_name, int for_ctx ) { Db pDb = db.aDb[nDb]; CODEC_TRACE( "codec_set_cipher_name: entered db=%d nDb=%d cipher_name=%s for_ctx=%d\n", db, nDb, cipher_name, for_ctx ); if ( pDb.pBt != null ) { codec_ctx ctx = null; cipher_ctx c_ctx; sqlite3pager_get_codec( pDb.pBt.pBt.pPager, ref ctx ); c_ctx = for_ctx != 0 ? ctx.write_ctx : ctx.read_ctx; c_ctx.derive_key = true; if ( for_ctx == 2 ) cipher_ctx_copy( for_ctx != 0 ? ctx.read_ctx : ctx.write_ctx, c_ctx ); return SQLITE_OK; } return SQLITE_ERROR; } static int codec_set_pass_key( sqlite3 db, int nDb, string zKey, int nKey, int for_ctx ) { Db pDb = db.aDb[nDb]; CODEC_TRACE( "codec_set_pass_key: entered db=%d nDb=%d cipher_name=%s nKey=%d for_ctx=%d\n", db, nDb, zKey, nKey, for_ctx ); if ( pDb.pBt != null ) { codec_ctx ctx = null; cipher_ctx c_ctx; sqlite3pager_get_codec( pDb.pBt.pBt.pPager, ref ctx ); c_ctx = for_ctx != 0 ? ctx.write_ctx : ctx.read_ctx; cipher_ctx_set_pass( c_ctx, zKey, nKey ); c_ctx.derive_key = true; if ( for_ctx == 2 ) cipher_ctx_copy( for_ctx != 0 ? ctx.read_ctx : ctx.write_ctx, c_ctx ); return SQLITE_OK; } return SQLITE_ERROR; } /* * sqlite3Codec can be called in multiple modes. * encrypt mode - expected to return a pointer to the * encrypted data without altering pData. * decrypt mode - expected to return a pointer to pData, with * the data decrypted in the input buffer */ static byte[] sqlite3Codec( codec_ctx iCtx, byte[] data, Pgno pgno, int mode ) { codec_ctx ctx = (codec_ctx)iCtx; int pg_sz = sqlite3BtreeGetPageSize( ctx.pBt ); int offset = 0; byte[] pData = data; CODEC_TRACE( "sqlite3Codec: entered pgno=%d, mode=%d, ctx.mode_rekey=%d, pg_sz=%d\n", pgno, mode, ctx.mode_rekey, pg_sz ); /* derive key on first use if necessary */ if ( ctx.read_ctx.derive_key ) { codec_key_derive( ctx, ctx.read_ctx ); ctx.read_ctx.derive_key = false; } if ( ctx.write_ctx.derive_key ) { if ( cipher_ctx_cmp( ctx.write_ctx, ctx.read_ctx ) == 0 ) { cipher_ctx_copy( ctx.write_ctx, ctx.read_ctx ); // the relevant parameters are the same, just copy read key } else { codec_key_derive( ctx, ctx.write_ctx ); ctx.write_ctx.derive_key = false; } } CODEC_TRACE( "sqlite3Codec: switch mode=%d offset=%d\n", mode, offset ); if ( ctx.buffer.Length != pg_sz ) ctx.buffer = sqlite3MemMalloc( pg_sz ); switch ( mode ) { case SQLITE_DECRYPT: codec_cipher( ctx.read_ctx, pgno, CIPHER_DECRYPT, pg_sz, pData, ctx.buffer ); if ( pgno == 1 ) Buffer.BlockCopy( Encoding.UTF8.GetBytes( SQLITE_FILE_HEADER ), 0, ctx.buffer, 0, FILE_HEADER_SZ );// memcpy( ctx.buffer, SQLITE_FILE_HEADER, FILE_HEADER_SZ ); /* copy file header to the first 16 bytes of the page */ Buffer.BlockCopy( ctx.buffer, 0, pData, 0, pg_sz ); //memcpy( pData, ctx.buffer, pg_sz ); /* copy buffer data back to pData and return */ return pData; case SQLITE_ENCRYPT_WRITE_CTX: /* encrypt */ if ( pgno == 1 ) Buffer.BlockCopy( ctx.write_ctx.iv, 0, ctx.buffer, 0, FILE_HEADER_SZ );//memcpy( ctx.buffer, ctx.iv, FILE_HEADER_SZ ); /* copy salt to output buffer */ codec_cipher( ctx.write_ctx, pgno, CIPHER_ENCRYPT, pg_sz, pData, ctx.buffer ); return ctx.buffer; /* return persistent buffer data, pData remains intact */ case SQLITE_ENCRYPT_READ_CTX: if ( pgno == 1 ) Buffer.BlockCopy( ctx.read_ctx.iv, 0, ctx.buffer, 0, FILE_HEADER_SZ );//memcpy( ctx.buffer, ctx.iv, FILE_HEADER_SZ ); /* copy salt to output buffer */ codec_cipher( ctx.read_ctx, pgno, CIPHER_ENCRYPT, pg_sz, pData, ctx.buffer ); return ctx.buffer; /* return persistent buffer data, pData remains intact */ default: return pData; } } static int sqlite3CodecAttach( sqlite3 db, int nDb, string zKey, int nKey ) { Db pDb = db.aDb[nDb]; CODEC_TRACE( "sqlite3CodecAttach: entered nDb=%d zKey=%s, nKey=%d\n", nDb, zKey, nKey ); //activate_openssl(); if ( zKey != null && pDb.pBt != null ) { Aes.KeySize = 256; #if !SQLITE_SILVERLIGHT Aes.Padding = PaddingMode.None; #endif codec_ctx ctx; int rc; ////Pager pPager = pDb.pBt.pBt.pPager; sqlite3_file fd; ctx = new codec_ctx();//sqlite3Malloc(sizeof(codec_ctx); //if(ctx == null) return SQLITE_NOMEM; //memset(ctx, 0, sizeof(codec_ctx); /* initialize all pointers and values to 0 */ ctx.pBt = pDb.pBt; /* assign pointer to database btree structure */ if ( ( rc = cipher_ctx_init( ref ctx.read_ctx ) ) != SQLITE_OK ) return rc; if ( ( rc = cipher_ctx_init( ref ctx.write_ctx ) ) != SQLITE_OK ) return rc; /* pre-allocate a page buffer of PageSize bytes. This will be used as a persistent buffer for encryption and decryption operations to avoid overhead of multiple memory allocations*/ ctx.buffer = sqlite3MemMalloc( sqlite3BtreeGetPageSize( ctx.pBt ) );//sqlite3Malloc(sqlite3BtreeGetPageSize(ctx.pBt); //if(ctx.buffer == null) return SQLITE_NOMEM; /* allocate space for salt data. Then read the first 16 bytes header as the salt for the key derivation */ ctx.read_ctx.iv_sz = FILE_HEADER_SZ; ctx.read_ctx.iv = new byte[ctx.read_ctx.iv_sz];//sqlite3Malloc( ctx.iv_sz ); Buffer.BlockCopy( Encoding.UTF8.GetBytes( SQLITE_FILE_HEADER ), 0, ctx.read_ctx.iv, 0, FILE_HEADER_SZ ); sqlite3pager_sqlite3PagerSetCodec( sqlite3BtreePager( pDb.pBt ), sqlite3Codec, null, sqlite3FreeCodecArg, ctx ); codec_set_cipher_name( db, nDb, CIPHER, 0 ); codec_set_pass_key( db, nDb, zKey, nKey, 0 ); cipher_ctx_copy( ctx.write_ctx, ctx.read_ctx ); //sqlite3BtreeSetPageSize( ctx.pBt, sqlite3BtreeGetPageSize( ctx.pBt ), MAX_IV_LENGTH, 0 ); } return SQLITE_OK; } static void sqlite3FreeCodecArg( ref codec_ctx pCodecArg ) { if ( pCodecArg == null ) return; codec_ctx_free( ref pCodecArg ); // wipe and free allocated memory for the context } static void sqlite3_activate_see( string zPassword ) { /* do nothing, security enhancements are always active */ } static public int sqlite3_key( sqlite3 db, string pKey, int nKey ) { CODEC_TRACE( "sqlite3_key: entered db=%d pKey=%s nKey=%d\n", db, pKey, nKey ); /* attach key if db and pKey are not null and nKey is > 0 */ if ( db != null && pKey != null ) { sqlite3CodecAttach( db, 0, pKey, nKey ); // operate only on the main db // // If we are reopening an existing database, redo the header information setup // BtShared pBt = db.aDb[0].pBt.pBt; byte[] zDbHeader = sqlite3MemMalloc( (int)pBt.pageSize );// pBt.pPager.pCodec.buffer; sqlite3PagerReadFileheader( pBt.pPager, zDbHeader.Length, zDbHeader ); if ( sqlite3Get4byte( zDbHeader ) > 0 ) // Existing Database, need to reset some values { CODEC2( pBt.pPager, zDbHeader, 2, SQLITE_DECRYPT, ref zDbHeader ); byte nReserve = zDbHeader[20]; pBt.pageSize = (uint)( ( zDbHeader[16] << 8 ) | ( zDbHeader[17] << 16 ) ); if ( pBt.pageSize < 512 || pBt.pageSize > SQLITE_MAX_PAGE_SIZE || ( ( pBt.pageSize - 1 ) & pBt.pageSize ) != 0 ) pBt.pageSize = 0; pBt.pageSizeFixed = true; #if !SQLITE_OMIT_AUTOVACUUM pBt.autoVacuum = sqlite3Get4byte( zDbHeader, 36 + 4 * 4 ) != 0; pBt.incrVacuum = sqlite3Get4byte( zDbHeader, 36 + 7 * 4 ) != 0; #endif sqlite3PagerSetPagesize( pBt.pPager, ref pBt.pageSize, nReserve ); pBt.usableSize = (u16)( pBt.pageSize - nReserve ); } return SQLITE_OK; } return SQLITE_ERROR; } /* sqlite3_rekey ** Given a database, this will reencrypt the database using a new key. ** There are two possible modes of operation. The first is rekeying ** an existing database that was not previously encrypted. The second ** is to change the key on an existing database. ** ** The proposed logic for this function follows: ** 1. Determine if there is already a key present ** 2. If there is NOT already a key present, create one and attach a codec (key would be null) ** 3. Initialize a ctx.rekey parameter of the codec ** ** Note: this will require modifications to the sqlite3Codec to support rekey ** */ static int sqlite3_rekey( sqlite3 db, string pKey, int nKey ) { CODEC_TRACE( "sqlite3_rekey: entered db=%d pKey=%s, nKey=%d\n", db, pKey, nKey ); //activate_openssl(); if ( db != null && pKey != null ) { Db pDb = db.aDb[0]; CODEC_TRACE( "sqlite3_rekey: database pDb=%d\n", pDb ); if ( pDb.pBt != null ) { codec_ctx ctx = null; int rc; Pgno page_count = 0; Pgno pgno; PgHdr page = null; Pager pPager = pDb.pBt.pBt.pPager; sqlite3pager_get_codec( pDb.pBt.pBt.pPager, ref ctx ); if ( ctx == null ) { CODEC_TRACE( "sqlite3_rekey: no codec attached to db, attaching now\n" ); /* there was no codec attached to this database,so attach one now with a null password */ sqlite3CodecAttach( db, 0, pKey, nKey ); sqlite3pager_get_codec( pDb.pBt.pBt.pPager, ref ctx ); /* prepare this setup as if it had already been initialized */ Buffer.BlockCopy( Encoding.UTF8.GetBytes( SQLITE_FILE_HEADER ), 0, ctx.read_ctx.iv, 0, FILE_HEADER_SZ ); ctx.read_ctx.key_sz = ctx.read_ctx.iv_sz = ctx.read_ctx.pass_sz = 0; } //if ( ctx.read_ctx.iv_sz != ctx.write_ctx.iv_sz ) //{ // string error = ""; // CODEC_TRACE( "sqlite3_rekey: updating page size for iv_sz change from %d to %d\n", ctx.read_ctx.iv_sz, ctx.write_ctx.iv_sz ); // db.nextPagesize = sqlite3BtreeGetPageSize( pDb.pBt ); // pDb.pBt.pBt.pageSizeFixed = false; /* required for sqlite3BtreeSetPageSize to modify pagesize setting */ // sqlite3BtreeSetPageSize( pDb.pBt, db.nextPagesize, MAX_IV_LENGTH, 0 ); // sqlite3RunVacuum( ref error, db ); //} codec_set_pass_key( db, 0, pKey, nKey, 1 ); ctx.mode_rekey = 1; /* do stuff here to rewrite the database ** 1. Create a transaction on the database ** 2. Iterate through each page, reading it and then writing it. ** 3. If that goes ok then commit and put ctx.rekey into ctx.key ** note: don't deallocate rekey since it may be used in a subsequent iteration */ rc = sqlite3BtreeBeginTrans( pDb.pBt, 1 ); /* begin write transaction */ sqlite3PagerPagecount( pPager, out page_count ); for ( pgno = 1; rc == SQLITE_OK && pgno <= page_count; pgno++ ) { /* pgno's start at 1 see pager.c:pagerAcquire */ if ( 0 == sqlite3pager_is_mj_pgno( pPager, pgno ) ) { /* skip this page (see pager.c:pagerAcquire for reasoning) */ rc = sqlite3PagerGet( pPager, pgno, ref page ); if ( rc == SQLITE_OK ) { /* write page see pager_incr_changecounter for example */ rc = sqlite3PagerWrite( page ); //printf("sqlite3PagerWrite(%d)\n", pgno); if ( rc == SQLITE_OK ) { sqlite3PagerUnref( page ); } } } } /* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */ if ( rc == SQLITE_OK ) { CODEC_TRACE( "sqlite3_rekey: committing\n" ); db.nextPagesize = sqlite3BtreeGetPageSize( pDb.pBt ); rc = sqlite3BtreeCommit( pDb.pBt ); if ( ctx != null ) cipher_ctx_copy( ctx.read_ctx, ctx.write_ctx ); } else { CODEC_TRACE( "sqlite3_rekey: rollback\n" ); sqlite3BtreeRollback( pDb.pBt ); } ctx.mode_rekey = 0; } return SQLITE_OK; } return SQLITE_ERROR; } static void sqlite3CodecGetKey( sqlite3 db, int nDb, out string zKey, out int nKey ) { Db pDb = db.aDb[nDb]; CODEC_TRACE( "sqlite3CodecGetKey: entered db=%d, nDb=%d\n", db, nDb ); if ( pDb.pBt != null ) { codec_ctx ctx = null; sqlite3pager_get_codec( pDb.pBt.pBt.pPager, ref ctx ); if ( ctx != null ) { /* if the codec has an attached codec_context user the raw key data */ zKey = ctx.read_ctx.pass; nKey = ctx.read_ctx.pass_sz; return; } } zKey = null; nKey = 0; } /* END CRYPTO */ #endif const int SQLITE_ENCRYPT_WRITE_CTX = 6; /* Encode page */ const int SQLITE_ENCRYPT_READ_CTX = 7; /* Encode page */ const int SQLITE_DECRYPT = 3; /* Decode page */ } }