-
-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Describe backup algorithm #349
Comments
Structure
Every backup consists of these files:
Meta.jsonA {
"password": "<bcrypt password hash>",
"createdAt": <timestamp>,
"backupVersion": 4,
"photos": [
{
"filename": "myimage.jpg",
"importedAt": 1234567,
"type": 2, # Mapping in code
"size": 1234567,
"uuid": "<uuid>"
},
{
"filename": "myimage.jpg",
"importedAt": 1234567,
"type": 2, # Mapping in code
"size": 1234567,
"uuid": "<uuid>"
},
],
"albums": [
{
"uuid": "<album_uuid>",
"name": "My album"
},
{
"uuid": "<album_uuid>",
"name": "My second album"
},
],
"albumPhotoRefs": [
{
"albumUUID": "<albumUUID>",
"photoUUID": "<photoUUID>",
"linkedAt": 1234567
},
{
"albumUUID": "<albumUUID>",
"photoUUID": "<photoUUID>",
"linkedAt": 1234567
},
{
"albumUUID": "<albumUUID>",
"photoUUID": "<photoUUID>",
"linkedAt": 1234567
},
{
"albumUUID": "<albumUUID>",
"photoUUID": "<photoUUID>",
"linkedAt": 1234567
},
]
} It contains all photos with filenames. The other files each have their Albums and albumPhotoRefs are your created albums and the info what photo is linked to what album. EncryptionThe encryption of the files is exactly the same as inside Photok. Meaning you can find the algorithm in the EncryptionManager The encryption key is derived from the passwords SHA256 hash. private fun genSecKey(password: String): SecretKeySpec {
val md = MessageDigest.getInstance(SHA_256)
val bytes = md.digest(password.toByteArray(StandardCharsets.UTF_8))
return SecretKeySpec(bytes, AES)
} The IV is also derived from the password (This is kown to be a bit unsecure #204) private fun genIv(password: String): IvParameterSpec {
val iv = ByteArray(16)
val charArray = password.toCharArray()
val firstChars = charArray.take(16)
for (i in firstChars.indices) {
iv[i] = firstChars[i].toByte()
}
return IvParameterSpec(iv)
} The encryption algorithm used is AES/GCM/NoPadding with 256 bit key. Are there more questions? |
Not at the moment. Thank you very much! As soon as I get back from vacation, I'll try to sketch out a script. |
Nice. Then I will close this :) |
Here is a Go program to decrypt the encrypted .photok{,.tn,.vp} files: package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"flag"
"os"
)
func main() {
password := flag.String("password", "", "photok password")
flag.Parse()
key := photokKey(*password)
iv := photokIV(*password)
for _, arg := range flag.Args() {
ciphertext, err := os.ReadFile(arg)
if err != nil {
panic(err)
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
if err != nil {
panic(err)
}
plaintext, err := aesgcm.Open(nil, iv, ciphertext, nil)
if err != nil {
panic(err)
}
if err := os.WriteFile(arg+".plaintext", plaintext, 0644); err != nil {
panic(err)
}
}
}
func photokKey(password string) []byte {
h := sha256.New()
h.Write([]byte(password))
return h.Sum(nil)
}
func photokIV(password string) []byte {
iv := make([]byte, 16)
for i := 0; i < 16 && i < len(password); i++ {
iv[i] = password[i]
}
return iv
} |
Thank you @u873838! Here’s the version that decodes the entire archive. If you don’t mind, I’ll create a repository that gathers this code and posts the binaries, just in case someone needs them. package main
import (
"archive/zip"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"sync"
)
func main() {
password := flag.String("password", "", "photok password")
backup := flag.String("file", "f", "path to backup file")
num_workers := flag.Int("worker", 10, "number of workers")
flag.Parse()
var wg sync.WaitGroup
jobs := make(chan *zip.File)
for i := 0; i < *num_workers; i++ {
wg.Add(1)
go worker(*password, jobs, &wg)
}
r, err := zip.OpenReader(*backup)
if err != nil {
panic(err)
}
defer r.Close()
for _, f := range r.File {
if !strings.HasSuffix(f.Name, ".tn") && strings.HasSuffix(f.Name, ".photok") {
fmt.Println(f.Name)
jobs <- f
}
}
close(jobs)
wg.Wait()
}
func worker(password string, jobs <-chan *zip.File, wg *sync.WaitGroup) {
defer wg.Done()
key := photokKey(password)
iv := photokIV(password)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
if err != nil {
panic(err)
}
for f := range jobs {
reader, err := f.Open()
if err != nil {
log.Fatal(err)
}
ciphertext, err := io.ReadAll(reader)
if err != nil {
log.Fatal(err)
}
plaintext, err := aesgcm.Open(nil, iv, ciphertext, nil)
if err != nil {
panic(err)
}
if err := os.WriteFile(f.Name+".plaintext", plaintext, 0o644); err != nil {
panic(err)
}
}
}
func photokKey(password string) []byte {
h := sha256.New()
h.Write([]byte(password))
return h.Sum(nil)
}
func photokIV(password string) []byte {
iv := make([]byte, 16)
for i := 0; i < 16 && i < len(password); i++ {
iv[i] = password[i]
}
return iv
} |
It would be great if there was a detailed description of the backup encryption algorithm somewhere. I would sleep much better if I knew that I could write a script in Python or Golang that would decrypt my backup no matter what happens to my phone.
Describe the solution you'd like
Block diagram or text description of the backup structure
The text was updated successfully, but these errors were encountered: