Um cofre cifrado em Go
Vamos juntar as três peças anteriores:
Um banco SQLite que vive na RAM e, no disco existe apenas como um blob criptografado.
O banco roda em :memory:.
Ao sair, tiramos o snapshot dos bytes do banco, criptografamos com a senha e gravamos.
Ao entrar, lemos o blob, decriptamos com a senha e fazemos o deserialize de volta para a RAM.
Aqui a proteção dos dados depende da força da senha, e do algoritmo de criptografia (aqui, AES-GCM). Precisa tomar cuidado com o tamanho do banco porque fazer o dump, criptografar e gravar tudo de uma vez pode ser lento. Mas é muito bom para salvar dados sensíveis, como chaves de API, tokens, etc.
O fluxo principal decide entre abrir um cofre existente ou criar um novo:
exists := fileExists(*vault)
pass, err := askPassphrase(!exists) // a new vault asks for confirmation
if err != nil {
fatal(err)
}
db, _ := sql.Open("sqlite", ":memory:")
db.SetMaxOpenConns(1)
mustExec(db, `PRAGMA temp_store=MEMORY;`)
if exists {
blob, _, err := readVault(*vault, pass)
if err != nil {
fatal(err) // wrong password or tampered: abort WITHOUT overwriting
}
restore(db, blob)
list(db)
return
}
// first time: create, serialize, encrypt and seal
Dois pontos importantes:
- Nunca sobrescrever em senha errada, se o
decryptfalha, abortamos antes de gravar qualquer coisa. - Escrita atômica, gravando num temporário e renomeando, para que uma queda no meio da escrita não deixe um cofre pela metade.
func seal(db *sql.DB, path, pass string) error {
plain, err := snapshot(db)
if err != nil {
return err
}
blob, err := encrypt(pass, plain)
if err != nil {
return err
}
tmp, err := os.CreateTemp("", ".vault-*")
if err != nil {
return err
}
tmpName := tmp.Name()
if _, werr := tmp.Write(blob); werr != nil {
_ = tmp.Close()
_ = os.Remove(tmpName)
return werr
}
if cerr := tmp.Close(); cerr != nil {
_ = os.Remove(tmpName)
return cerr
}
if cerr := os.Chmod(tmpName, 0o600); cerr != nil {
_ = os.Remove(tmpName)
return cerr
}
return os.Rename(tmpName, path)
}
Para testar:
go run . # create, ask for the password twice, seal
file vault.blob # not a SQLite file
hexdump -C vault.blob | head # nothing readable
go run . # ask for the password, open and list
Isso protege o conteúdo do banco at rest: sem a senha, o arquivo é ruído, e qualquer adulteração é detectada pelo GCM.
Claro que os dados são protegidos apenas no disco, não protege contra um atacante que leia a memória do processo.