From afb7077dad6094aaae4b75e541f03df0e347b530 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Mon, 6 Oct 2025 15:58:34 +0200 Subject: [PATCH 01/10] add Password property to SqliteCacheOption to produce Sqlite ConnectionString utilizing SQLite with encryption --- SqliteCache/SqliteCacheOptions.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SqliteCache/SqliteCacheOptions.cs b/SqliteCache/SqliteCacheOptions.cs index 49bd4be..c3f4dcb 100755 --- a/SqliteCache/SqliteCacheOptions.cs +++ b/SqliteCache/SqliteCacheOptions.cs @@ -39,6 +39,11 @@ public string CachePath } } + /// + /// + /// + public string SqliteEncryptionPassword { get; set; } = ""; + /// /// Specifies how often expired items are removed in the background. /// Background eviction is disabled if set to null. @@ -55,6 +60,10 @@ internal string ConnectionString Mode = MemoryOnly ? SqliteOpenMode.Memory : SqliteOpenMode.ReadWriteCreate, Cache = SqliteCacheMode.Shared }; + if (string.IsNullOrEmpty(SqliteEncryptionPassword)) + { + sb.Password = SqliteEncryptionPassword; + } return sb.ConnectionString; } From 9eecbfb598aee12be410dfa510af935f202fbc25 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Mon, 6 Oct 2025 16:10:30 +0200 Subject: [PATCH 02/10] add information about how to use encryption to readme --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index c657955..3a0c116 100755 --- a/README.md +++ b/README.md @@ -120,6 +120,31 @@ public ActionResult OnPostAsync() } ``` +## Encryption +To use SqliteCache with encryption make sure you are using the corresponding providers for your target framework. +E.g. for Net481: +* https://www.nuget.org/packages/SQLitePCLRaw.provider.sqlcipher/2.0.4 +* https://www.nuget.org/packages/SQLitePCLRaw.lib.e_sqlcipher/2.0.4 +* https://www.nuget.org/packages/sqlite-net-sqlcipher/1.8.116 +* https://www.nuget.org/packages/Microsoft.Data.Sqlite.Core/5.0.17 + +And make sure to use the correct provider when registering Sqlite +```csharp +raw.SetProvider((ISQLite3Provider)new SQLite3Provider_e_sqlcipher()); +serviceCollectionAccess.AddSingleton(); +serviceCollectionAccess.AddSingleton(aServiceProvider => aServiceProvider.GetRequiredService()); +``` + +then just configure SqliteCache with the PasswordOption +```csharp +serviceCollectionAccess.Configure(aSqliteCacheOptions => +{ +... + aSqliteCacheOptions.Password = password; +... +}); +``` + ## License SqliteCache is developed and maintained by Mahmoud Al-Qudsi of NeoSmart Technologies. The project is From c3e00da22c37fc7d5e398fccd357f5add788fc24 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Tue, 7 Oct 2025 11:21:06 +0200 Subject: [PATCH 03/10] reduce required nuget packages --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3a0c116..c4b3835 100755 --- a/README.md +++ b/README.md @@ -123,10 +123,8 @@ public ActionResult OnPostAsync() ## Encryption To use SqliteCache with encryption make sure you are using the corresponding providers for your target framework. E.g. for Net481: -* https://www.nuget.org/packages/SQLitePCLRaw.provider.sqlcipher/2.0.4 -* https://www.nuget.org/packages/SQLitePCLRaw.lib.e_sqlcipher/2.0.4 -* https://www.nuget.org/packages/sqlite-net-sqlcipher/1.8.116 -* https://www.nuget.org/packages/Microsoft.Data.Sqlite.Core/5.0.17 +* https://www.nuget.org/packages/SQLitePCLRaw.provider.sqlcipher/2.1.10 +* https://www.nuget.org/packages/SQLitePCLRaw.lib.e_sqlcipher/2.1.10 And make sure to use the correct provider when registering Sqlite ```csharp From 4cc117b3ca5372230a33edf963f915c5e9480365 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Tue, 7 Oct 2025 11:25:15 +0200 Subject: [PATCH 04/10] clean up SqliteCacheOption documentation --- SqliteCache/SqliteCacheOptions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SqliteCache/SqliteCacheOptions.cs b/SqliteCache/SqliteCacheOptions.cs index c3f4dcb..7a1a153 100755 --- a/SqliteCache/SqliteCacheOptions.cs +++ b/SqliteCache/SqliteCacheOptions.cs @@ -40,9 +40,10 @@ public string CachePath } /// - /// + /// Use this to specify a password for the SqliteConnection that is to be created. + /// If no is set, the connectionStringBuilder will not use the "Password" option. /// - public string SqliteEncryptionPassword { get; set; } = ""; + public string? SqliteEncryptionPassword { get; set; } = null; /// /// Specifies how often expired items are removed in the background. @@ -60,6 +61,8 @@ internal string ConnectionString Mode = MemoryOnly ? SqliteOpenMode.Memory : SqliteOpenMode.ReadWriteCreate, Cache = SqliteCacheMode.Shared }; + + // only set the password option if the user actually set a Password if (string.IsNullOrEmpty(SqliteEncryptionPassword)) { sb.Password = SqliteEncryptionPassword; From 8103011cb6f5377f882070a3da60e5b81674d28e Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Tue, 7 Oct 2025 11:35:07 +0200 Subject: [PATCH 05/10] add warning to new password option --- SqliteCache/SqliteCacheOptions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SqliteCache/SqliteCacheOptions.cs b/SqliteCache/SqliteCacheOptions.cs index 7a1a153..5bab0eb 100755 --- a/SqliteCache/SqliteCacheOptions.cs +++ b/SqliteCache/SqliteCacheOptions.cs @@ -42,6 +42,9 @@ public string CachePath /// /// Use this to specify a password for the SqliteConnection that is to be created. /// If no is set, the connectionStringBuilder will not use the "Password" option. + /// + /// CAREFUL! this option will break your SqliteConnection if the sqliteProvider that you are using does not support encryption. + /// You will have to use e.g. (SQLitePCLRaw.provider.sqlcipher) instead of (SQLitePCLRaw.provider.e_sqlite3) /// public string? SqliteEncryptionPassword { get; set; } = null; From 7a935d2222de700d1f1136042854c743322f7a77 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Tue, 7 Oct 2025 11:53:59 +0200 Subject: [PATCH 06/10] extend basic tests to test with and without setting a password --- SqliteCache.Tests/BasicTests.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/SqliteCache.Tests/BasicTests.cs b/SqliteCache.Tests/BasicTests.cs index bc2d6f3..c7c7663 100755 --- a/SqliteCache.Tests/BasicTests.cs +++ b/SqliteCache.Tests/BasicTests.cs @@ -38,19 +38,21 @@ public void Dispose() } } - private SqliteCache CreateDefault(bool persistent = true) + private SqliteCache CreateDefault(bool persistent = true, string password = null) { var logger = new TestLogger(); logger.LogInformation("Creating a connection to db {DbPath}", Configuration.CachePath); - var cacheDb = new SqliteCache(Configuration with { MemoryOnly = !persistent }, logger); + var cacheDb = new SqliteCache(Configuration with { MemoryOnly = !persistent, SqliteEncryptionPassword = password}, logger); return cacheDb; } - [TestMethod] - public async Task BasicSetGet() + [DataTestMethod] + [DataRow(null)] + [DataRow("testPassword")] + public async Task BasicSetGet(string password) { - using (var cache = CreateDefault(true)) + using (var cache = CreateDefault(true, password)) { var bytes = cache.Get("hello"); Assert.IsNull(bytes); @@ -67,7 +69,7 @@ public async Task BasicSetGet() } // Check persistence - using (var cache = CreateDefault(true)) + using (var cache = CreateDefault(true, password)) { var bytes = await cache.GetAsync("hello"); CollectionAssert.AreEqual(bytes, DefaultEncoding.GetBytes("hello")); From 7f39c5d736ed749b116027bb2e965339779c902c Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Tue, 7 Oct 2025 12:00:38 +0200 Subject: [PATCH 07/10] make basic tests execute one itme with and one time without password --- SqliteCache.Tests/BasicTests.cs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/SqliteCache.Tests/BasicTests.cs b/SqliteCache.Tests/BasicTests.cs index c7c7663..105dcf2 100755 --- a/SqliteCache.Tests/BasicTests.cs +++ b/SqliteCache.Tests/BasicTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -47,7 +48,7 @@ private SqliteCache CreateDefault(bool persistent = true, string password = null return cacheDb; } - [DataTestMethod] + [TestMethod] [DataRow(null)] [DataRow("testPassword")] public async Task BasicSetGet(string password) @@ -77,9 +78,11 @@ public async Task BasicSetGet(string password) } [TestMethod] - public void ExpiredIgnored() + [DataRow(null)] + [DataRow("testPassword")] + public void ExpiredIgnored(string password) { - using (var cache = CreateDefault()) + using (var cache = CreateDefault(true, password)) { cache.Set("hi there", DefaultEncoding.GetBytes("hello"), new DistributedCacheEntryOptions() @@ -90,9 +93,11 @@ public void ExpiredIgnored() } [TestMethod] - public void ExpiredRenewal() + [DataRow(null)] + [DataRow("testPassword")] + public void ExpiredRenewal(string password) { - using (var cache = CreateDefault()) + using (var cache = CreateDefault(true, password)) { cache.Set("hi there", DefaultEncoding.GetBytes("hello"), new DistributedCacheEntryOptions() @@ -106,12 +111,14 @@ public void ExpiredRenewal() } [TestMethod] - public void ExpirationStoredInUtc() + [DataRow(null)] + [DataRow("testPassword")] + public void ExpirationStoredInUtc(string password) { var expiryUtc = DateTimeOffset.UtcNow.AddMinutes(-1); var expiryLocal = expiryUtc.ToOffset(TimeSpan.FromHours(5)); - using (var cache = CreateDefault()) + using (var cache = CreateDefault(true, password)) { cache.Set("key", DefaultEncoding.GetBytes("value"), new DistributedCacheEntryOptions { @@ -123,9 +130,11 @@ public void ExpirationStoredInUtc() } [TestMethod] - public void DoubleDispose() + [DataRow(null)] + [DataRow("testPassword")] + public void DoubleDispose(string password) { - using (var cache = CreateDefault(true)) + using (var cache = CreateDefault(true, password)) { cache.Dispose(); } @@ -133,9 +142,11 @@ public void DoubleDispose() #if NETCOREAPP3_0_OR_GREATER [TestMethod] - public async Task AsyncDispose() + [DataRow(null)] + [DataRow("testPassword")] + public async Task AsyncDispose(string password) { - await using (var cache = CreateDefault(true)) + await using (var cache = CreateDefault(true, password)) { await cache.SetAsync("foo", DefaultEncoding.GetBytes("hello")); var bytes = await cache.GetAsync("foo"); From 91352c1a218924a77cacc28031cadab01efbe151 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Wed, 8 Oct 2025 09:47:49 +0200 Subject: [PATCH 08/10] fix inverted if --- SqliteCache/SqliteCacheOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SqliteCache/SqliteCacheOptions.cs b/SqliteCache/SqliteCacheOptions.cs index 5bab0eb..5fdaf97 100755 --- a/SqliteCache/SqliteCacheOptions.cs +++ b/SqliteCache/SqliteCacheOptions.cs @@ -66,7 +66,7 @@ internal string ConnectionString }; // only set the password option if the user actually set a Password - if (string.IsNullOrEmpty(SqliteEncryptionPassword)) + if (!string.IsNullOrEmpty(SqliteEncryptionPassword)) { sb.Password = SqliteEncryptionPassword; } From 519ec0e275f19cd9e74d05cf4be39cc128c716d4 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Wed, 8 Oct 2025 09:52:34 +0200 Subject: [PATCH 09/10] Revert "make basic tests execute one itme with and one time without password" This reverts commit 7f39c5d736ed749b116027bb2e965339779c902c. --- SqliteCache.Tests/BasicTests.cs | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/SqliteCache.Tests/BasicTests.cs b/SqliteCache.Tests/BasicTests.cs index 105dcf2..c7c7663 100755 --- a/SqliteCache.Tests/BasicTests.cs +++ b/SqliteCache.Tests/BasicTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -48,7 +47,7 @@ private SqliteCache CreateDefault(bool persistent = true, string password = null return cacheDb; } - [TestMethod] + [DataTestMethod] [DataRow(null)] [DataRow("testPassword")] public async Task BasicSetGet(string password) @@ -78,11 +77,9 @@ public async Task BasicSetGet(string password) } [TestMethod] - [DataRow(null)] - [DataRow("testPassword")] - public void ExpiredIgnored(string password) + public void ExpiredIgnored() { - using (var cache = CreateDefault(true, password)) + using (var cache = CreateDefault()) { cache.Set("hi there", DefaultEncoding.GetBytes("hello"), new DistributedCacheEntryOptions() @@ -93,11 +90,9 @@ public void ExpiredIgnored(string password) } [TestMethod] - [DataRow(null)] - [DataRow("testPassword")] - public void ExpiredRenewal(string password) + public void ExpiredRenewal() { - using (var cache = CreateDefault(true, password)) + using (var cache = CreateDefault()) { cache.Set("hi there", DefaultEncoding.GetBytes("hello"), new DistributedCacheEntryOptions() @@ -111,14 +106,12 @@ public void ExpiredRenewal(string password) } [TestMethod] - [DataRow(null)] - [DataRow("testPassword")] - public void ExpirationStoredInUtc(string password) + public void ExpirationStoredInUtc() { var expiryUtc = DateTimeOffset.UtcNow.AddMinutes(-1); var expiryLocal = expiryUtc.ToOffset(TimeSpan.FromHours(5)); - using (var cache = CreateDefault(true, password)) + using (var cache = CreateDefault()) { cache.Set("key", DefaultEncoding.GetBytes("value"), new DistributedCacheEntryOptions { @@ -130,11 +123,9 @@ public void ExpirationStoredInUtc(string password) } [TestMethod] - [DataRow(null)] - [DataRow("testPassword")] - public void DoubleDispose(string password) + public void DoubleDispose() { - using (var cache = CreateDefault(true, password)) + using (var cache = CreateDefault(true)) { cache.Dispose(); } @@ -142,11 +133,9 @@ public void DoubleDispose(string password) #if NETCOREAPP3_0_OR_GREATER [TestMethod] - [DataRow(null)] - [DataRow("testPassword")] - public async Task AsyncDispose(string password) + public async Task AsyncDispose() { - await using (var cache = CreateDefault(true, password)) + await using (var cache = CreateDefault(true)) { await cache.SetAsync("foo", DefaultEncoding.GetBytes("hello")); var bytes = await cache.GetAsync("foo"); From 38a2594b7efcd5ad3a70a7a08d32533609cbd8d4 Mon Sep 17 00:00:00 2001 From: Niko Bergemann Date: Wed, 8 Oct 2025 09:52:38 +0200 Subject: [PATCH 10/10] Revert "extend basic tests to test with and without setting a password" This reverts commit 7a935d2222de700d1f1136042854c743322f7a77. --- SqliteCache.Tests/BasicTests.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/SqliteCache.Tests/BasicTests.cs b/SqliteCache.Tests/BasicTests.cs index c7c7663..bc2d6f3 100755 --- a/SqliteCache.Tests/BasicTests.cs +++ b/SqliteCache.Tests/BasicTests.cs @@ -38,21 +38,19 @@ public void Dispose() } } - private SqliteCache CreateDefault(bool persistent = true, string password = null) + private SqliteCache CreateDefault(bool persistent = true) { var logger = new TestLogger(); logger.LogInformation("Creating a connection to db {DbPath}", Configuration.CachePath); - var cacheDb = new SqliteCache(Configuration with { MemoryOnly = !persistent, SqliteEncryptionPassword = password}, logger); + var cacheDb = new SqliteCache(Configuration with { MemoryOnly = !persistent }, logger); return cacheDb; } - [DataTestMethod] - [DataRow(null)] - [DataRow("testPassword")] - public async Task BasicSetGet(string password) + [TestMethod] + public async Task BasicSetGet() { - using (var cache = CreateDefault(true, password)) + using (var cache = CreateDefault(true)) { var bytes = cache.Get("hello"); Assert.IsNull(bytes); @@ -69,7 +67,7 @@ public async Task BasicSetGet(string password) } // Check persistence - using (var cache = CreateDefault(true, password)) + using (var cache = CreateDefault(true)) { var bytes = await cache.GetAsync("hello"); CollectionAssert.AreEqual(bytes, DefaultEncoding.GetBytes("hello"));