The design document covers the cryptography details. Filippo Valsorda's article is also interesting, and a testament to restic's good design.
In its simplest form, our sample repository will have a single encryption key -available in the keys
directory- so, from a practical point of view, all the interesting files (except they key file which is a plain json file with the encrypted master key) are encrypted with that key.
This is the key that was created after initializing the repository:
cat /tmp/restic/keys/e2e644764f1a5b13eea6c8e351cadd718b7d14c273fe1c5e12a7023ba20c88e4 | jq
{
"created": "2020-09-30T17:43:20.86292862+02:00",
"username": "rubiojr",
"hostname": "x390",
"kdf": "scrypt",
"N": 32768,
"r": 8,
"p": 6,
"salt": "zRz3EKT9XzWbiU/cVdcYZwSIknD1DcbQylQuS9vKVJkwxu1aldX9bYon8edHQdCM10r+hrT1hPW0GxyyyR62zw==",
"data": "nzTzHz/T5D4isFeQeMiGBpWScy23vdf6CT9eTltG276KeTTJZ3p2V8WK8WUyEPr0vTKWkOKWkh/X+JjLYVV2242Jo7ZZ/X5T3PVJ+5BdLGXrLrX21lEkq766fVVZg4IgeJt7jl3EL7fOvRZ+ef3w205JdOigWJjcoGDmtj/i2818ZZCWYOP4PHLUPlaLoDGoNgBvfNPwoU4InjSaisy3hQ=="
}
The contents of the key that was created with restic init
.
Recommended read: Is storing key in the backup location really safe?
The file name, like evey other file in a restic repository, is the file content SHA256 hash.
sha256sum /tmp/restic/keys/e2e644764f1a5b13eea6c8e351cadd718b7d14c273fe1c5e12a7023ba20c88e4
e2e644764f1a5b13eea6c8e351cadd718b7d14c273fe1c5e12a7023ba20c88e4 /tmp/restic/keys/e2e644764f1a5b13eea6c8e351cadd718b7d14c273fe1c5e12a7023ba20c88e4
The following example, decrypts the repository configuration (/tmp/restic/config
in our test repository) -encrypted like (almost) every other file in a restic repository- and writes it to the standard output.
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/rubiojr/rapi/examples/util"
)
func main() {
k := util.FindAndOpenKey()
// open repository config file
h, err := os.Open(filepath.Join(util.RepoPath, "config"))
util.CheckErr(err)
// decrypt the repo configuration, print it to stdout
text, err := k.Decrypt(h)
util.CheckErr(err)
fmt.Println(string(text))
}
If we run the example (make sure you read prerequisites first), it'll decrypt /tmp/restic/config
and print its content:
{
"version": 1,
"id": "49ccedbed680cc3bebff15d77ee6343dd8b39563869de93abb3f2d0eae3e38d6",
"chunker_polynomial": "240d51ccba496d"
}
Once we understand how to use restic's repository keys, we can use restic's encryption to encrypt ordinary files.
The following code encrypts the file available in examples/data/hello
and saves it to /tmp/safehello
:
package main
import (
"fmt"
"io/ioutil"
"os"
"github.com/rubiojr/rapi/examples/util"
)
func main() {
if len(os.Args) < 3 {
fmt.Printf("Usage: encrypt_file <read_from> <write_to>\n")
os.Exit(1)
}
in, out := os.Args[1], os.Args[2]
// our sample repo in /tmp/restic will only have one key, let's
// find it and use it
k := util.FindAndOpenKey()
h, err := os.Open(in)
defer h.Close()
util.CheckErr(err)
// Read the file contents
plain, err := ioutil.ReadAll(h)
util.CheckErr(err)
// Encrypt the file using restic's repository master key
ciphertext := k.Encrypt(plain)
// Write the resulting ciphertext to the target file
outf, err := os.Create(out)
util.CheckErr(err)
defer outf.Close()
outf.Write(ciphertext)
}
Run it like this (don't forget to read prerequisites first):
go run examples/encrypt_file.go data/hello /tmp/hello.encrypted
If we inspect the new file we'll get the encrypted content, encrypted like every other file backed up by restic:
cat /tmp/hello.encrypted
:Vbsy.iw,f?hDx?يX>htwjk
If we want to decrypt it, we can use a slightly different version of the decryption example above.
go run decrypt_file.go /tmp/hello.encrypted
hello rapi!