Skip to content

Commit dbe79d6

Browse files
committed
Add SetKey to connection to support SQLCipher
The key quoting is not perfect - need to improve it in the future. Working on #597
1 parent bb5eee3 commit dbe79d6

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed

src/SQLite.cs

+42
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,48 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st
287287
Tracer = line => Debug.WriteLine (line);
288288
}
289289

290+
/// <summary>
291+
/// Convert an input string to a quoted SQL string that can be safely used in queries.
292+
/// </summary>
293+
/// <returns>The quoted string.</returns>
294+
/// <param name="unsafeString">The unsafe string to quote.</param>
295+
static string Quote (string unsafeString)
296+
{
297+
// TODO: Doesn't call sqlite3_mprintf("%Q", u) because we're waiting on https://github.com/ericsink/SQLitePCL.raw/issues/153
298+
if (unsafeString == null) return "NULL";
299+
var safe = unsafeString.Replace ("'", "''");
300+
return "'" + safe + "'";
301+
}
302+
303+
/// <summary>
304+
/// Sets the key used to encrypt/decrypt the database.
305+
/// This must be the first thing you call before doing anything else with this connection
306+
/// if your database is encrypted.
307+
/// This only has an effect if you are using the SQLCipher nuget package.
308+
/// </summary>
309+
/// <param name="key">Ecryption key plain text that is converted to the real encryption key using PBKDF2 key derivation</param>
310+
public void SetKey (string key)
311+
{
312+
if (key == null) throw new ArgumentNullException (nameof (key));
313+
var q = Quote (key);
314+
Execute ("pragma key = " + q);
315+
}
316+
317+
/// <summary>
318+
/// Sets the key used to encrypt/decrypt the database.
319+
/// This must be the first thing you call before doing anything else with this connection
320+
/// if your database is encrypted.
321+
/// This only has an effect if you are using the SQLCipher nuget package.
322+
/// </summary>
323+
/// <param name="key">256-bit (32 byte) ecryption key data</param>
324+
public void SetKey (byte[] key)
325+
{
326+
if (key == null) throw new ArgumentNullException (nameof (key));
327+
if (key.Length != 32) throw new ArgumentException ("Key must be 32 bytes (256-bit)", nameof(key));
328+
var s = String.Join ("", key.Select (x => x.ToString ("X2")));
329+
Execute ("pragma key = \"x'" + s + "'\"");
330+
}
331+
290332
/// <summary>
291333
/// Enable or disable extension loading.
292334
/// </summary>

src/SQLiteAsync.cs

+33
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,39 @@ Task<T> WriteAsync<T> (Func<SQLiteConnectionWithLock, T> write)
198198
});
199199
}
200200

201+
/// <summary>
202+
/// Sets the key used to encrypt/decrypt the database.
203+
/// This must be the first thing you call before doing anything else with this connection
204+
/// if your database is encrypted.
205+
/// This only has an effect if you are using the SQLCipher nuget package.
206+
/// </summary>
207+
/// <param name="key">Ecryption key plain text that is converted to the real encryption key using PBKDF2 key derivation</param>
208+
public Task SetKeyAsync (string key)
209+
{
210+
if (key == null) throw new ArgumentNullException (nameof (key));
211+
return WriteAsync<object> (conn => {
212+
conn.SetKey (key);
213+
return null;
214+
});
215+
}
216+
217+
/// <summary>
218+
/// Sets the key used to encrypt/decrypt the database.
219+
/// This must be the first thing you call before doing anything else with this connection
220+
/// if your database is encrypted.
221+
/// This only has an effect if you are using the SQLCipher nuget package.
222+
/// </summary>
223+
/// <param name="key">256-bit (32 byte) ecryption key data</param>
224+
public Task SetKeyAsync (byte[] key)
225+
{
226+
if (key == null) throw new ArgumentNullException (nameof (key));
227+
if (key.Length != 32) throw new ArgumentException ("Key must be 32 bytes (256-bit)", nameof (key));
228+
return WriteAsync<object> (conn => {
229+
conn.SetKey (key);
230+
return null;
231+
});
232+
}
233+
201234
/// <summary>
202235
/// Enable or disable extension loading.
203236
/// </summary>

tests/SQLCipherTest.cs

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
#if NETFX_CORE
6+
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
7+
using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute;
8+
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
9+
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
10+
#else
11+
using NUnit.Framework;
12+
#endif
13+
14+
namespace SQLite.Tests
15+
{
16+
[TestFixture]
17+
public class SQLCipherTest
18+
{
19+
class TestTable
20+
{
21+
[PrimaryKey, AutoIncrement]
22+
public int Id { get; set; }
23+
24+
public string Value { get; set; }
25+
}
26+
27+
[Test]
28+
public void SetStringKey ()
29+
{
30+
string path;
31+
32+
var key = "SecretPassword";
33+
34+
using (var db = new TestDb ()) {
35+
path = db.DatabasePath;
36+
37+
db.SetKey (key);
38+
39+
db.CreateTable<TestTable> ();
40+
db.Insert (new TestTable { Value = "Hello" });
41+
}
42+
43+
using (var db = new TestDb (path)) {
44+
path = db.DatabasePath;
45+
46+
db.SetKey (key);
47+
48+
var r = db.Table<TestTable> ().First ();
49+
50+
Assert.AreEqual ("Hello", r.Value);
51+
}
52+
}
53+
54+
[Test]
55+
public void SetBytesKey ()
56+
{
57+
string path;
58+
59+
var rand = new Random ();
60+
var key = new byte[32];
61+
rand.NextBytes (key);
62+
63+
using (var db = new TestDb ()) {
64+
path = db.DatabasePath;
65+
66+
db.SetKey (key);
67+
68+
db.CreateTable<TestTable> ();
69+
db.Insert (new TestTable { Value = "Hello" });
70+
}
71+
72+
using (var db = new TestDb (path)) {
73+
path = db.DatabasePath;
74+
75+
db.SetKey (key);
76+
77+
var r = db.Table<TestTable> ().First ();
78+
79+
Assert.AreEqual ("Hello", r.Value);
80+
}
81+
}
82+
83+
[Test]
84+
public void SetBadBytesKey ()
85+
{
86+
try {
87+
using (var db = new TestDb ()) {
88+
db.SetKey (new byte[] { 1, 2, 3, 4 });
89+
}
90+
Assert.Fail ("Should have thrown");
91+
}
92+
catch (ArgumentException) {
93+
}
94+
}
95+
}
96+
}

tests/SQLite.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<Compile Include="DateTimeOffsetTest.cs" />
8181
<Compile Include="TableChangedTest.cs" />
8282
<Compile Include="IgnoreTest.cs" />
83+
<Compile Include="SQLCipherTest.cs" />
8384
</ItemGroup>
8485
<ItemGroup>
8586
<None Include="packages.config" />

tests/TestDb.cs

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public TestDb (bool storeDateTimeAsTicks = true) : base (TestPath.GetTempFileNam
5757
{
5858
Trace = true;
5959
}
60+
61+
public TestDb (string path, bool storeDateTimeAsTicks = true) : base (path, storeDateTimeAsTicks)
62+
{
63+
Trace = true;
64+
}
6065
}
6166

6267
public class TestPath

0 commit comments

Comments
 (0)