Add SQLCipher _key DSN support
Some checks failed
Go / Test (1.24, macos-latest) (push) Has been cancelled
Go / Test (1.24, ubuntu-latest) (push) Has been cancelled
Go / Test (1.25, macos-latest) (push) Has been cancelled
Go / Test (1.25, ubuntu-latest) (push) Has been cancelled
Go / Test (1.26, macos-latest) (push) Has been cancelled
Go / Test (1.26, ubuntu-latest) (push) Has been cancelled
Go / Test for Windows (1.24) (push) Has been cancelled
Go / Test for Windows (1.25) (push) Has been cancelled
Go / Test for Windows (1.26) (push) Has been cancelled
dockerfile / Run Dockerfiles in examples (push) Has been cancelled

This commit is contained in:
2026-08-06 14:00:09 +02:00
parent cc41b8c876
commit 9fdc81b5a8
5 changed files with 168 additions and 1 deletions

View File

@@ -946,6 +946,28 @@ func lastError(db *C.sqlite3) error {
}
}
func sqlCipherKeyPragma(key string) (string, error) {
if strings.IndexByte(key, 0) >= 0 {
return "", errors.New("Invalid _key: contains NUL")
}
if isSQLCipherRawKey(key) {
return "PRAGMA key = \"" + key + "\";", nil
}
return "PRAGMA key = '" + strings.ReplaceAll(key, "'", "''") + "';", nil
}
func isSQLCipherRawKey(key string) bool {
if len(key) != 67 || (key[0] != 'x' && key[0] != 'X') || key[1] != '\'' || key[66] != '\'' {
return false
}
for _, c := range key[2:66] {
if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F') {
return false
}
}
return true
}
// Exec implements Execer.
func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
return c.exec(context.Background(), query, valueToNamedValue(args))
@@ -1137,6 +1159,11 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
// _busy_timeout=XXX"| _timeout=XXX
// Specify value for sqlite3_busy_timeout.
//
// _key=KEY
// Set the SQLCipher key before any driver initialization PRAGMA. KEY is a
// passphrase or a raw key expression of the form x'<64 hexadecimal digits>'.
// Requires a SQLCipher library linked with the libsqlite3 build tag.
//
// _case_sensitive_like=Boolean | _cslike=Boolean
// https://www.sqlite.org/pragma.html#pragma_case_sensitive_like
// Default or disabled the LIKE operation is case-insensitive.
@@ -1213,6 +1240,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
vfsName := ""
var cacheSize *int64
stmtCacheSize := 0
keyPragma := ""
pos := strings.IndexRune(dsn, '?')
if pos >= 1 {
@@ -1222,6 +1250,14 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
// Authentication
if _, ok := params["_key"]; ok {
var err error
keyPragma, err = sqlCipherKeyPragma(params.Get("_key"))
if err != nil {
return nil, err
}
}
if _, ok := params["_auth"]; ok {
authCreate = true
}
@@ -1594,6 +1630,16 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
if db == nil {
return nil, errors.New("sqlite succeeded without returning a database")
}
if keyPragma != "" {
cs := C.CString(keyPragma)
rv := C.sqlite3_exec(db, cs, nil, nil, nil)
C.free(unsafe.Pointer(cs))
if rv != C.SQLITE_OK {
err := lastError(db)
C.sqlite3_close_v2(db)
return nil, err
}
}
// Create connection to SQLite
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}