go-sqlcipher

repository·master·Indexed 42 days ago

https://github.com/mutecomm/go-sqlcipher

A self-contained Go sqlite3 driver providing AES-256 encryption for SQLite databases, conforming to the standard database/sql interface. It includes support for opening encrypted databases via hex keys or passphrases, checking encryption status with IsEncrypted(), and performing database backups via SQLiteConn.Backup. The driver also allows registering native Go functions as SQLite extensions and provides custom encoders (SHA1, SHA256, SHA384, SHA512) to override the sqlite_crypt function.

Tokens
11K
Snippets
48
Records
53
Agent score
52%

What's inside go-sqlcipher

  1. Upgrade to go-sqlcipher v4

    master

    SQLCipher 4.x is incompatible with SQLCipher 3.x. If you are upgrading a major version, you are responsible for migrating existing database files. To upgrade your Go code to the 4.x series, update your import path to:

    "github.com/mutecomm/go-sqlcipher/v4"

    import "github.com/mutecomm/go-sqlcipher/v4"
  2. Override the sqlite_crypt function with custom encoders

    master

    The built-in sqlite_crypt(X, Y) function used by the UserAuthentication module uses a simple Caesar-cypher which is insecure. To improve security, you can provide a stronger implementation (e.g., using SHA hashes) by using the RegisterFunc method on your connection to overload sqlite_crypt.

    When sqlite_crypt(X, Y) is called:

    • X is the plaintext password.
    • Y is the existing stored hash (used to extract the salt).
    • The result is the new encoded blob.

    To verify credentials, SQLite performs: sqlite_user.pw == sqlite_crypt(X, sqlite_user.pw). To generate a new hash with a random salt, call sqlite_crypt(X, NULL).

  3. Understand TraceInfo event data

    master

    The TraceInfo struct contains the data payload for different SQLite trace events. The fields populated depend on the EventCode:

    • TraceStmt (SQLITE_TRACE_STMT):
      • StmtHandle: Identifier for the prepared statement.
      • StmtOrTrigger: The unexpanded SQL text or an SQL comment indicating a trigger.
      • ExpandedSQL: The expanded SQL (only if TraceConfig.WantExpandedSQL is true).
    • TraceProfile (SQLITE_TRACE_PROFILE):
      • StmtHandle: Identifier for the prepared statement.
      • RunTimeNanosec: Estimated nanoseconds the statement took to run.
      • DBError: Any error encountered during the profile event.
    • TraceRow (SQLITE_TRACE_ROW):
      • StmtHandle: Identifier for the prepared statement.
    • TraceClose (SQLITE_TRACE_CLOSE):
      • Used for internal cleanup; StmtHandle is typically not used.

    Common Fields (All Events):

    • EventCode: The type of event.
    • AutoCommit: Whether the connection is in auto-commit mode.
    • ConnHandle: The connection handle (uintptr).
    type TraceInfo struct {
    	EventCode      uint32
    	AutoCommit     bool
    	ConnHandle     uintptr
    	StmtHandle     uintptr
    	StmtOrTrigger  string
    	ExpandedSQL    string
    	RunTimeNanosec int64
    	DBError        Error
    }
  4. Open an encrypted database with a hex key

    master

    To open an encrypted database using a 32-byte hex-encoded key, include _pragma_key and optionally _pragma_cipher_page_size in your Data Source Name (DSN). The _pragma_key must be a 64-character hex string wrapped in x'' literals.

    key := "2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99"
    dbname := fmt.Sprintf("db?_pragma_key=x'%s'&_pragma_cipher_page_size=4096", key)
    db, _ := sql.Open("sqlite3", dbname)
  5. Open an encrypted database with a passphrase

    master

    To use a plain-text passphrase instead of a hex key, pass the passphrase to _pragma_key. You must use url.QueryEscape() on the passphrase to ensure the DSN is valid.

    key := url.QueryEscape("secret")
    dbname := fmt.Sprintf("db?_pragma_key=%s&_pragma_cipher_page_size=4096", key)
    db, _ := sql.Open("sqlite3", dbname)
  6. Use TraceConfig to configure tracing behavior

    master

    The TraceConfig struct defines how the trace callback behaves and which events are captured.

    FieldTypeDescription
    CallbackTraceUserCallbackThe function called when a matching event occurs.
    EventMaskuint32A bitmask of Trace... constants (e.g., TraceStmt, TraceProfile) defining which events to trigger the callback.
    WantExpandedSQLboolIf true, the TraceInfo.ExpandedSQL field will be populated when a TraceStmt event occurs. Note: This is automatically disabled if TraceStmt is not in the EventMask.
    type TraceConfig struct {
    	Callback        TraceUserCallback
    	EventMask       uint32
    	WantExpandedSQL bool
    }
  7. CGO_ENABLED=0 incompatibility error

    master

    If you attempt to use go-sqlcipher in an environment where CGO_ENABLED=0 was used during compilation, the driver will fail to open connections and return the following error: Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub.

    To use this library, you must ensure that CGO is enabled during the build process.

    errors.New("Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub")
  8. Register C-based SQLite3 Extensions

    master

    You can load external C extensions (compiled as .so or .dll shared libraries) by registering a custom driver with the Extensions field in the sqlite3.SQLiteDriver struct. The strings in the Extensions slice should correspond to the names of the shared libraries.

    sql.Register("sqlite3_with_extensions",
    	&sqlite3.SQLiteDriver{
    		Extensions: []string{
    			"sqlite3_mod_regexp",
    		},
    	})
  9. Register Go functions as SQLite extensions

    master

    You can register native Go functions as SQLite extension functions by using the ConnectHook to call conn.RegisterFunc. This allows you to use Go logic directly within your SQL queries.

    // Define your Go function
    regex = func(re, s string) (bool, error) {
    	return regexp.MatchString(re, s)
    }
    
    // Register a custom driver that injects the function
    sql.Register("sqlite3_extended",
    	&sqlite3.SQLiteDriver{
    		ConnectHook: func(conn *sqlite3.SQLiteConn) error {
    			return conn.RegisterFunc("regexp", regex, true)
    		},
    	})
    
    // Use the custom driver
    var i int
    conn, err := sql.Open("sqlite3_extended", "./foo.db")
    // ... handle err
    
    err = conn.QueryRow(`SELECT regexp("foo.*", "seafood")`).Scan(&i)
  10. Delete a user with AuthUserDelete()

    master

    Use AuthUserDelete(username string) to remove a user from the database.

    Permissions and Safety:

    • Admin Only: This operation requires administrator privileges.
    • Preventing Lockout: The currently logged-in user cannot be deleted. This guarantee ensures that there is always at least one administrator, preventing the database from being reverted to a no-authentication state accidentally.
    err := db.AuthUserDelete("user_to_remove")
    if err != nil {
        if errors.Is(err, sqlite3.ErrAdminRequired) {
            // Handle permission error
        }
    }