使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫(kù)
本文檔主要描述通過(guò) https://github.com/mutecomm/go-sqlcipher 生成和讀取 SQLCipher 加密數(shù)據(jù)庫(kù)以及其中踩的一些坑
軟件版本
go: v1.22.2
sqlcipher cli(ubuntun):3.15.2
sqlcipher(used for encrypt):v3
go-sqlcipher-package:https://github.com/mutecomm/go-sqlcipher v0.0.0-20190227152316-55dbde17881f
Go 生成和讀取 SQLCipher 數(shù)據(jù)庫(kù)
生成數(shù)據(jù)庫(kù)
創(chuàng)建一個(gè)名為 encrypt-data.db
,表為 test
的數(shù)據(jù)庫(kù)
import _ "github.com/mutecomm/go-sqlcipher" func NewSQLCipherDB() { var ( db *sql.DB testDir = "go-sqlcipher_test" tables = `CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT);` data = `INSERT INTO test (data) VALUES ('Hello, World!');` ) // create DB key := "passphrase" tmpdir, err := os.MkdirTemp("", testDir) if err != nil { panic(err) } dbname := filepath.Join(tmpdir, "encrypt-data.db") dbnameWithDSN := dbname + fmt.Sprintf("?_pragma_key=%s", key) if db, err = sql.Open("sqlite3", dbnameWithDSN); err != nil { panic(err) } defer db.Close() if _, err = db.Exec(tables); err != nil { panic(err) } if _, err = db.Exec(data); err != nil { panic(err) } return }
判斷數(shù)據(jù)庫(kù)是否加密
import sqlite3 "github.com/mutecomm/go-sqlcipher" func IsSQLCipherEncrypted(dbName string) { // make sure DB is encrypted encrypted, err := sqlite3.IsEncrypted(dbName) if err != nil { panic(err) } if !encrypted { panic(errors.New("go-sqlcipher: DB not encrypted")) } fmt.Println("encrypted") }
讀取數(shù)據(jù)庫(kù)
import _ "github.com/mutecomm/go-sqlcipher" func QuerySQLCipherDB(dbPath,key string) { var ( db *sql.DB err error ) dbnameWithDSN := dbPath + fmt.Sprintf("?_pragma_key=%s", key) // open DB for testing db, err = sql.Open("sqlite3", dbnameWithDSN) if err != nil { panic(err) } _, err = db.Exec("SELECT count(*) FROM test;") if err != nil { panic(err) } return }
如果密碼錯(cuò)誤或者是數(shù)據(jù)庫(kù)錯(cuò)誤,Line 15 會(huì)報(bào) err
Gorm 連接 SQLCipher 數(shù)據(jù)庫(kù)
用原生方式讀取肯定不方便,所以還是找了一下如何用 gorm 來(lái)連接并讀取。其實(shí)這個(gè) go-sqlcipher 就是一個(gè)驅(qū)動(dòng),所以跟 gorm 讀取 mysql 數(shù)據(jù)庫(kù)是差不多的。就是要注意把 “github.com/mutecomm/go-sqlcipher” import 進(jìn)去。
import _ "github.com/mutecomm/go-sqlcipher" var ( db *gorm.DB ) func Init(dbPath string) (err error) { key := "passphrase" dbPath = fmt.Sprintf(dbPath+"?_pragma_key=%s", key) db, err = gorm.Open("sqlite3", dbPath) if err != nil { return err } // logger Open db.LogMode(true) // Set Idle db.DB().SetMaxIdleConns(10) return nil }
可視化工具讀取 SQLCipher 加密數(shù)據(jù)庫(kù)(1)
本篇下面的描述內(nèi)容主要是,因?yàn)閯?chuàng)建加密數(shù)據(jù)庫(kù)參數(shù)出入,而要去修改可視化工具的一些參數(shù),具體見(jiàn)下文。
踩坑 & 分析
上述的方式都是基礎(chǔ)的,也正常是應(yīng)該這么創(chuàng)建以及讀取的,但是我接手到的代碼是長(zhǎng)下面這樣子的。
key := "passphrase" dbPath = fmt.Sprintf(dbPath+"?_pragma_key=x'%s'&_pragma_cipher_page_size=4096", key) db, err = gorm.Open("sqlite3", dbPath) if err != nil { return err }
奇奇怪怪的事情就開(kāi)始發(fā)生了,用最基礎(chǔ)的 sqlcipher 指令讀取都會(huì)說(shuō)密碼錯(cuò)誤。
sqlcipher 密碼錯(cuò)誤
sqlite> PRAGMA key = x'passphrase'; # 格式錯(cuò)誤 sqlite> PRAGMA key = '70617373706872617365'; # passphrase hex 之后,密碼錯(cuò)誤 sqlite> PRAGMA key = '78277061737370687261736527'; # x'passphrase' hex 之后,密碼錯(cuò)誤 sqlite> PRAGMA key = "x'passphrase'";
先透露,第四個(gè)才是對(duì)的
按正常情況來(lái)看,應(yīng)該這樣就可以正常讀取了,還是報(bào)密碼錯(cuò)誤。
db browser 密碼錯(cuò)誤
之前沒(méi)碰過(guò)這個(gè),覺(jué)得 sqlcipher 是不是我不會(huì),所以找了這個(gè)工具。
不過(guò)按照流程輸入密碼,也還是進(jìn)不去,也選擇了 SQLCipher 3 也不行。
這邊 algorithm 跟源碼 README 里面的 AES 256 對(duì)不上,我以為是 db browser 不支持我這種加密格式
跑單測(cè)
按照別人給的不行,就從頭開(kāi)始,自己創(chuàng)建,自己測(cè)試。
- go 代碼創(chuàng)建加密數(shù)據(jù)庫(kù),sqlcipher 指令讀取,這個(gè)是可以的。這一個(gè)測(cè)試我用的是最上面生成數(shù)據(jù)庫(kù)的代碼。
- 因?yàn)槲沂盏降拇a里面有帶,_pragma_cipher_page_size=4096。然后用這個(gè)方式創(chuàng)建的就是不行,以為我輸入的 key 是不是在第三方包內(nèi)有做什么動(dòng)作,所以去分析了源碼庫(kù)。
跑完單元測(cè)試,說(shuō)明密碼的輸入沒(méi)錯(cuò),就是這個(gè) page size 的問(wèn)題。
此時(shí)我還沒(méi)意識(shí)到是 page size 默認(rèn)配置的問(wèn)題
查源碼
以下源碼的 README,看得我迷糊,以為還要再 hex,多測(cè)了不同的加密方式也不行。
To create and open encrypted database files use the following DSN parameters:
key := "2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99" dbname := fmt.Sprintf("db?_pragma_key=x'%s'&_pragma_cipher_page_size=4096", key) db, _ := sql.Open("sqlite3", dbname)
_pragma_key is the hex encoded 32 byte key (must be 64 characters long). _pragma_cipher_page_size is the page size of the encrypted database (set if you want a different value than the default size).
key := url.QueryEscape("secret") dbname := fmt.Sprintf("db?_pragma_key=%s&_pragma_cipher_page_size=4096", key) db, _ := sql.Open("sqlite3", dbname)
This uses a passphrase directly as _pragma_key with the key derivation function in SQLCipher. Do not forget the url.QueryEscape() call in your code!
找 ISSUE
https://github.com/mutecomm/go-sqlcipher/issues/15
這個(gè) issue 是對(duì) SQLCipher V4 的,里面有這么一段:
The parameters seem to be the same. I'm wondering if you have to switch the order of key and cipher_page_size in the sqlcipher call. Also the documentation https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_default_page_size seems to indicate that you have to use cipher_default_page_size in the command line call. But it shouldn't make any difference anyway since 4096 is the default value in SQLCipher 4.
說(shuō)明 SQLCipher 的 cipher_page_size
有默認(rèn)值,并且在調(diào)用 sqlcipher 加密的時(shí)候,會(huì)受影響。所以,在可視化頁(yè)面連接的時(shí)候要指定。
回看代碼
dbname := fmt.Sprintf("db?_pragma_key=x'%s'&_pragma_cipher_page_size=4096", key)
這個(gè)庫(kù)只支持 SQLCipher v3,v4 的默認(rèn)值才是 4096,v3的默認(rèn)值是1024(雖然我不知道這個(gè)什么用)
各個(gè)可視化工具默認(rèn)都是 1024,跟代碼里面 4096 對(duì)不上,改參數(shù)
改參數(shù)
sqlcipher
sqlite> PRAGMA key = "x'passphrase'"; sqlite> PRAGMA cipher_page_size=4096; sqlite> SELECT * from test; 1|Hello, World! sqlite> .exit
db browser
先選 SQLCipher 3, 然后選擇 Custom,再點(diǎn)擊 Page size 的下拉選擇 4096,就可以了
DBeaver
修改 legacy_page_size
為 4096 就可以了
總結(jié)
其實(shí)這個(gè)懂的人,估計(jì)看到這個(gè) page size 不同就知道要去配置了。對(duì)于不懂的人,看密碼,又登入不進(jìn)去就會(huì)很煩,就會(huì)亂。
后面分析的方法就是從單測(cè)入手,用最簡(jiǎn)單的方式先跑通一個(gè)。比如,密碼先不要設(shè)置那么復(fù)雜的,就設(shè)置 123456,然后測(cè)試。通過(guò)再往下一步,往自己收到的問(wèn)題去靠。
以上就是使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫(kù)的詳細(xì)內(nèi)容,更多關(guān)于Go Gorm讀取SQLCipher的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go語(yǔ)言中的Carbon庫(kù)時(shí)間處理技巧
這篇文章主要介紹了go語(yǔ)言中的Carbon庫(kù)時(shí)間處理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02Go語(yǔ)言學(xué)習(xí)筆記之錯(cuò)誤和異常詳解
Go語(yǔ)言采用返回值的形式來(lái)返回錯(cuò)誤,這一機(jī)制既可以讓開(kāi)發(fā)者真正理解錯(cuò)誤處理的含義,也可以大大降低程序的復(fù)雜度,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言學(xué)習(xí)筆記之錯(cuò)誤和異常的相關(guān)資料,需要的朋友可以參考下2022-07-07Go的gin參數(shù)校驗(yàn)中的validator庫(kù)詳解
這篇文章主要介紹了Go的gin參數(shù)校驗(yàn)之validator庫(kù),使用 validator 以后,只需要在定義結(jié)構(gòu)體時(shí)使用 binding 或 validate tag標(biāo)識(shí)相關(guān)校驗(yàn)規(guī)則,就可以進(jìn)行參數(shù)校驗(yàn)了,而不用自己?jiǎn)为?dú)去寫常見(jiàn)的校驗(yàn)規(guī)則,需要的朋友可以參考下2023-08-08關(guān)于golang中map使用的幾點(diǎn)注意事項(xiàng)總結(jié)(強(qiáng)烈推薦!)
map是一種無(wú)序的基于key-value的數(shù)據(jù)結(jié)構(gòu),Go語(yǔ)言中的map是引用類型,必須初始化才能使用,下面這篇文章主要給大家介紹了關(guān)于golang中map使用的幾點(diǎn)注意事項(xiàng),需要的朋友可以參考下2023-01-01Go語(yǔ)言開(kāi)發(fā)快速學(xué)習(xí)CGO編程
這篇文章主要為大家介紹了Go語(yǔ)言開(kāi)發(fā)之快速學(xué)習(xí)CGO編程,看了本文你就會(huì)發(fā)現(xiàn)CGO編程其實(shí)沒(méi)有想象的那么難,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Golang 并發(fā)讀寫鎖的具體實(shí)現(xiàn)
Go語(yǔ)言中的sync.RWMutex提供了讀寫鎖機(jī)制,允許多個(gè)協(xié)程并發(fā)讀取共享資源,但在寫操作時(shí)保持獨(dú)占性,本文主要介紹了Golang 并發(fā)讀寫鎖的具體實(shí)現(xiàn),感興趣的可以了解一下2025-02-02