【Go】用 Go 原生以及 Gorm 读取 SQLCipher 加密数据库

本文档主要描述通过 https://github.com/mutecomm/go-sqlcipher 生成和读取 SQLCipher 加密数据库以及其中踩的一些坑

  • 用 go 去生成读取 SQLCipher 数据库
  • 用 gorm 去读取 SQLCipher 数据库
  • 在生成后分别用 DBeaver、db browser 和 sqlcipher 读取 SQLCipher 数据库,基础操作见 用 DBeaver、db browser 和 SqlCipher 读取 SqlCipher 数据库
  • 因为创建方式的参数导致读取时候的坑,以及我的分析思路

软件版本

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 数据库
生成数据库

创建一个名为 encrypt-data.db,表为 test 的数据库

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
}
判断数据库是否加密
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")
} 
读取数据库
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
}

如果密码错误或者是数据库错误,Line 15 会报 err

Gorm 连接 SQLCipher 数据库

用原生方式读取肯定不方便,所以还是找了一下如何用 gorm 来连接并读取。其实这个 go-sqlcipher 就是一个驱动,所以跟 gorm 读取 mysql 数据库是差不多的。就是要注意把 “github.com/mutecomm/go-sqlcipher” import 进去。

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 加密数据库(1)

可视化部分的可以通过这一篇文章细看 用 DBeaver、db browser 和 SqlCipher 读取 SqlCipher 数据库

本篇下面的描述内容主要是,因为创建加密数据库参数出入,而要去修改可视化工具的一些参数,具体见下文。

踩坑 & 分析

上述的方式都是基础的,也正常是应该这么创建以及读取的,但是我接手到的代码是长下面这样子的。

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
}

奇奇怪怪的事情就开始发生了,用最基础的 sqlcipher 指令读取都会说密码错误。

sqlcipher 密码错误
sqlite> PRAGMA key = x'passphrase'; # 格式错误
sqlite> PRAGMA key = '70617373706872617365'; # passphrase hex 之后,密码错误
sqlite> PRAGMA key = '78277061737370687261736527'; # x'passphrase' hex 之后,密码错误
sqlite> PRAGMA key = "x'passphrase'";

先透露,第四个才是对的

按正常情况来看,应该这样就可以正常读取了,还是报密码错误。

db browser 密码错误

之前没碰过这个,觉得 sqlcipher 是不是我不会,所以找了这个工具。

不过按照流程输入密码,也还是进不去,也选择了 SQLCipher 3 也不行。

这边 algorithm 跟源码 README 里面的 AES 256 对不上,我以为是 db browser 不支持我这种加密格式

在这里插入图片描述

跑单测

按照别人给的不行,就从头开始,自己创建,自己测试。

  1. go 代码创建加密数据库,sqlcipher 指令读取,这个是可以的。这一个测试我用的是最上面生成数据库的代码。
  2. 因为我收到的代码里面有带,_pragma_cipher_page_size=4096。然后用这个方式创建的就是不行,以为我输入的 key 是不是在第三方包内有做什么动作,所以去分析了源码库。

跑完单元测试,说明密码的输入没错,就是这个 page size 的问题。

此时我还没意识到是 page size 默认配置的问题

查源码

以下源码的 README,看得我迷糊,以为还要再 hex,多测了不同的加密方式也不行。

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

这个 issue 是对 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.

说明 SQLCipher 的 cipher_page_size 有默认值,并且在调用 sqlcipher 加密的时候,会受影响。所以,在可视化页面连接的时候要指定。

回看代码
dbname := fmt.Sprintf("db?_pragma_key=x'%s'&_pragma_cipher_page_size=4096", key)

这个库只支持 SQLCipher v3,v4 的默认值才是 4096,v3的默认值是1024(虽然我不知道这个什么用)

各个可视化工具默认都是 1024,跟代码里面 4096 对不上,改参数

改参数

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,再点击 Page size 的下拉选择 4096,就可以了
在这里插入图片描述

DBeaver

DBeaver driver 的驱动配置见另一个文档 用 DBeaver、db browser 和 SqlCipher 读取 SqlCipher 数据库

修改 legacy_page_size 为 4096 就可以了

在这里插入图片描述

总结

其实这个懂的人,估计看到这个 page size 不同就知道要去配置了。对于不懂的人,看密码,又登入不进去就会很烦,就会乱。

后面分析的方法就是从单测入手,用最简单的方式先跑通一个。比如,密码先不要设置那么复杂的,就设置 123456,然后测试。通过再往下一步,往自己收到的问题去靠。

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
根据提供的引用内容,我可以为您介绍两种gorm sqlite数据库加密的方式: 1. 对数据库中的数据进行加密: 这种方式是将数据库中的数据进行加密,保护敏感信息。您可以使用加密算法对数据进行加密,然后存储到数据库中。在读取数据时,再进行解密操作。这种方式的优点是可以保护数据的机密性,但缺点是需要在每次读写数据时进行加解密操作,可能会影响性能。 2. 对数据库文件进行加密: 这种方式是对整个数据库文件进行加密,保护整个数据库的机密性。您可以使用加密算法对数据库文件进行加密,然后在使用时解密。这种方式的优点是可以一次性对整个数据库进行加解密,不需要在每次读写数据时进行操作,但缺点是可能会增加数据库的访问时间。 以下是两种加密方式的示例代码: 1. 对数据库中的数据进行加密: ```go import ( "gorm.io/driver/sqlite" "gorm.io/gorm" "crypto/aes" "crypto/cipher" "encoding/base64" ) // 定义加密密钥 var key = []byte("0123456789abcdef") // 定义加密函数 func encrypt(data []byte) []byte { block, _ := aes.NewCipher(key) ciphertext := make([]byte, aes.BlockSize+len(data)) iv := ciphertext[:aes.BlockSize] stream := cipher.NewCTR(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize:], data) return ciphertext } // 定义解密函数 func decrypt(ciphertext []byte) []byte { block, _ := aes.NewCipher(key) iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] stream := cipher.NewCTR(block, iv) stream.XORKeyStream(ciphertext, ciphertext) return ciphertext } func main() { db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) if err != nil { panic("failed to connect database") } // 自动迁移模式 db.AutoMigrate(&User{}) // 创建用户 user := User{Name: "Alice", Age: 18} // 加密用户数据 encryptedData := encrypt([]byte(user.Name)) user.Name = base64.StdEncoding.EncodeToString(encryptedData) // 存储用户数据 db.Create(&user) // 查询用户 var result User db.First(&result, user.ID) // 解密用户数据 decryptedData, _ := base64.StdEncoding.DecodeString(result.Name) result.Name = string(decrypt(decryptedData)) fmt.Println(result) } ``` 2. 对数据库文件进行加密: ```go import ( "gorm.io/driver/sqlite" "gorm.io/gorm" "crypto/aes" "crypto/cipher" "io/ioutil" "os" ) // 定义加密密钥 var key = []byte("0123456789abcdef") // 定义加密函数 func encryptFile(filename string) error { plaintext, err := ioutil.ReadFile(filename) if err != nil { return err } block, _ := aes.NewCipher(key) ciphertext := make([]byte, aes.BlockSize+len(plaintext)) iv := ciphertext[:aes.BlockSize] stream := cipher.NewCTR(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) return ioutil.WriteFile(filename, ciphertext, os.ModePerm) } // 定义解密函数 func decryptFile(filename string) error { ciphertext, err := ioutil.ReadFile(filename) if err != nil { return err } block, _ := aes.NewCipher(key) iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] stream := cipher.NewCTR(block, iv) stream.XORKeyStream(ciphertext, ciphertext) return ioutil.WriteFile(filename, ciphertext, os.ModePerm) } func main() { db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) if err != nil { panic("failed to connect database") } // 自动迁移模式 db.AutoMigrate(&User{}) // 创建用户 user := User{Name: "Alice", Age: 18} db.Create(&user) // 加密数据库文件 err = encryptFile("gorm.db") if err != nil { panic("failed to encrypt database file") } // 解密数据库文件 err = decryptFile("gorm.db") if err != nil { panic("failed to decrypt database file") } // 查询用户 var result User db.First(&result, user.ID) fmt.Println(result) } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值