Skip to content

Commit d7acf92

Browse files
authored
Merge pull request #1143 from stephencelis/sqlcipher
Add sqlcipher_export
2 parents f21ff7d + ceb13aa commit d7acf92

File tree

6 files changed

+63
-5
lines changed

6 files changed

+63
-5
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Support `WITH` clause ([#1139][])
66
* Add `Value` conformance for `NSURL` ([#1110][], [#1141][])
77
* Add decoding for `UUID` ([#1137][])
8+
* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` ([#1101][])
89
* Fix `insertMany([Encodable])` ([#1130][], [#1138][])
910
* Fix incorrect spelling of `remove_diacritics` ([#1128][])
1011
* Fix project build order ([#1131][])
@@ -162,7 +163,9 @@
162163
[#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077
163164
[#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094
164165
[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095
166+
[#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098
165167
[#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100
168+
[#1101]: https://github.com/stephencelis/SQLite.swift/issues/1101
166169
[#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105
167170
[#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109
168171
[#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110

Documentation/Index.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,16 @@ extend `Connection` with methods to change the database key:
184184
```swift
185185
import SQLite
186186
187-
let db = try Connection("path/to/db.sqlite3")
187+
let db = try Connection("path/to/encrypted.sqlite3")
188188
try db.key("secret")
189-
try db.rekey("another secret")
189+
try db.rekey("new secret") // changes encryption key on already encrypted db
190+
```
191+
192+
To encrypt an existing database:
193+
194+
```swift
195+
let db = try Connection("path/to/unencrypted.sqlite3")
196+
try db.sqlcipher_export(.uri("encrypted.sqlite3"), key: "secret")
190197
```
191198

192199
[CocoaPods]: https://cocoapods.org
@@ -2103,6 +2110,13 @@ try db.detach("external")
21032110
// DETACH DATABASE 'external'
21042111
```
21052112

2113+
When compiled for SQLCipher, you can additionally pass a `key` parameter to `attach`:
2114+
2115+
```swift
2116+
try db.attach(.uri("encrypted.sqlite"), as: "encrypted", key: "secret")
2117+
// ATTACH DATABASE 'encrypted.sqlite' AS 'encrypted' KEY 'secret'
2118+
```
2119+
21062120
## Logging
21072121

21082122
We can log SQL using the database’s `trace` function.

Sources/SQLite/Core/Connection+Attach.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,21 @@ import SQLite3
1010
#endif
1111

1212
extension Connection {
13-
13+
#if SQLITE_SWIFT_SQLCIPHER
14+
/// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#attach
15+
public func attach(_ location: Location, as schemaName: String, key: String? = nil) throws {
16+
if let key = key {
17+
try run("ATTACH DATABASE ? AS ? KEY ?", location.description, schemaName, key)
18+
} else {
19+
try run("ATTACH DATABASE ? AS ?", location.description, schemaName)
20+
}
21+
}
22+
#else
1423
/// See https://www3.sqlite.org/lang_attach.html
1524
public func attach(_ location: Location, as schemaName: String) throws {
1625
try run("ATTACH DATABASE ? AS ?", location.description, schemaName)
1726
}
27+
#endif
1828

1929
/// See https://www3.sqlite.org/lang_detach.html
2030
public func detach(_ schemaName: String) throws {

Sources/SQLite/Extensions/Cipher.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import SQLCipher
66
extension Connection {
77

88
/// - Returns: the SQLCipher version
9+
///
10+
/// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_version
911
public var cipherVersion: String? {
1012
(try? scalar("PRAGMA cipher_version")) as? String
1113
}
@@ -23,6 +25,8 @@ extension Connection {
2325
/// of key data.
2426
/// e.g. x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'
2527
/// @param db name of the database, defaults to 'main'
28+
///
29+
/// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlite3_key
2630
public func key(_ key: String, db: String = "main") throws {
2731
try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count)
2832
}
@@ -39,6 +43,7 @@ extension Connection {
3943
/// As "PRAGMA cipher_migrate;" is time-consuming, it is recommended to use this function
4044
/// only after failure of `key(_ key: String, db: String = "main")`, if older versions of
4145
/// your app may ise older version of SQLCipher
46+
///
4247
/// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_migrate
4348
/// and https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283
4449
/// for more details regarding SQLCipher upgrade
@@ -51,11 +56,13 @@ extension Connection {
5156
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true)
5257
}
5358

54-
/// Change the key on an open database. If the current database is not encrypted, this routine
55-
/// will encrypt it.
59+
/// Change the key on an open database. NB: only works if the database is already encrypted.
60+
///
5661
/// To change the key on an existing encrypted database, it must first be unlocked with the
5762
/// current encryption key. Once the database is readable and writeable, rekey can be used
5863
/// to re-encrypt every page in the database with a new key.
64+
///
65+
/// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlite3_rekey
5966
public func rekey(_ key: String, db: String = "main") throws {
6067
try _rekey_v2(db: db, keyPointer: key, keySize: key.utf8.count)
6168
}
@@ -64,6 +71,17 @@ extension Connection {
6471
try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
6572
}
6673

74+
/// Converts a non-encrypted database to an encrypted one.
75+
///
76+
/// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export
77+
public func sqlcipher_export(_ location: Location, key: String) throws {
78+
let schemaName = "cipher_export"
79+
80+
try attach(location, as: schemaName, key: key)
81+
try run("SELECT sqlcipher_export(?)", schemaName)
82+
try detach(schemaName)
83+
}
84+
6785
// MARK: - private
6886
private func _key_v2(db: String,
6987
keyPointer: UnsafePointer<UInt8>,

Tests/SQLiteTests/CipherTests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ class CipherTests: XCTestCase {
9999
XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64)
100100
}
101101

102+
func test_export() throws {
103+
let tmp = temporaryFile()
104+
try db1.sqlcipher_export(.uri(tmp), key: "mykey")
105+
106+
let conn = try Connection(tmp)
107+
try conn.key("mykey")
108+
XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64)
109+
}
110+
102111
private func keyData(length: Int = 64) -> NSMutableData {
103112
let keyData = NSMutableData(length: length)!
104113
let result = SecRandomCopyBytes(kSecRandomDefault, length,

Tests/SQLiteTests/Fixtures.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ func fixture(_ name: String, withExtension: String?) -> String {
1616
}
1717
fatalError("Cannot find \(name).\(withExtension ?? "")")
1818
}
19+
20+
func temporaryFile() -> String {
21+
URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).path
22+
}

0 commit comments

Comments
 (0)