mirror of
https://github.com/gilbertchen/duplicacy
synced 2025-12-15 07:43:21 +00:00
Use random salt and make the number of iterations configurable for storage key derivation
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -28,6 +29,15 @@ var DEFAULT_KEY = []byte("duplicacy")
|
||||
// standard zlib levels of -1 to 9.
|
||||
var DEFAULT_COMPRESSION_LEVEL = 100
|
||||
|
||||
// The new header of the config file (to differentiate from the old format where the salt and iterations are fixed)
|
||||
var CONFIG_HEADER = "duplicacy\001"
|
||||
|
||||
// The length of the salt used in the new format
|
||||
var CONFIG_SALT_LENGTH = 32
|
||||
|
||||
// The default iterations for key derivation
|
||||
var CONFIG_DEFAULT_ITERATIONS = 16384
|
||||
|
||||
type Config struct {
|
||||
CompressionLevel int `json:"compression-level"`
|
||||
AverageChunkSize int `json:"average-chunk-size"`
|
||||
@@ -316,10 +326,45 @@ func DownloadConfig(storage Storage, password string) (config *Config, isEncrypt
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if len(configFile.GetBytes()) < len(ENCRYPTION_HEADER) {
|
||||
return nil, false, fmt.Errorf("The storage has an invalid config file")
|
||||
}
|
||||
|
||||
if string(configFile.GetBytes()[:len(ENCRYPTION_HEADER)-1]) == ENCRYPTION_HEADER[:len(ENCRYPTION_HEADER)-1] && len(password) == 0 {
|
||||
return nil, true, fmt.Errorf("The storage is likely to have been initialized with a password before")
|
||||
}
|
||||
|
||||
var masterKey []byte
|
||||
|
||||
if len(password) > 0 {
|
||||
masterKey = GenerateKeyFromPassword(password)
|
||||
|
||||
if string(configFile.GetBytes()[:len(ENCRYPTION_HEADER)]) == ENCRYPTION_HEADER {
|
||||
// This is the old config format with a static salt and a fixed number of iterations
|
||||
masterKey = GenerateKeyFromPassword(password, DEFAULT_KEY, CONFIG_DEFAULT_ITERATIONS)
|
||||
LOG_TRACE("CONFIG_FORMAT", "Using a static salt and %d iterations for key derivation", CONFIG_DEFAULT_ITERATIONS)
|
||||
} else if string(configFile.GetBytes()[:len(CONFIG_HEADER)]) == CONFIG_HEADER {
|
||||
// This is the new config format with a random salt and a configurable number of iterations
|
||||
encryptedLength := len(configFile.GetBytes()) - CONFIG_SALT_LENGTH - 4
|
||||
|
||||
// Extract the salt and the number of iterations
|
||||
saltStart := configFile.GetBytes()[len(CONFIG_HEADER):]
|
||||
iterations := binary.LittleEndian.Uint32(saltStart[CONFIG_SALT_LENGTH : CONFIG_SALT_LENGTH+4])
|
||||
LOG_TRACE("CONFIG_ITERATIONS", "Using %d iterations for key derivation", iterations)
|
||||
masterKey = GenerateKeyFromPassword(password, saltStart[:CONFIG_SALT_LENGTH], int(iterations))
|
||||
|
||||
// Copy to a temporary buffer to replace the header and remove the salt and the number of riterations
|
||||
var encrypted bytes.Buffer
|
||||
encrypted.Write([]byte(ENCRYPTION_HEADER))
|
||||
encrypted.Write(saltStart[CONFIG_SALT_LENGTH+4:])
|
||||
|
||||
configFile.Reset(false)
|
||||
configFile.Write(encrypted.Bytes())
|
||||
if len(configFile.GetBytes()) != encryptedLength {
|
||||
LOG_ERROR("CONFIG_DOWNLOAD", "Encrypted config has %d bytes instead of expected %d bytes", len(configFile.GetBytes()), encryptedLength)
|
||||
}
|
||||
} else {
|
||||
return nil, true, fmt.Errorf("The config file has an invalid header")
|
||||
}
|
||||
|
||||
// Decrypt the config file. masterKey == nil means no encryption.
|
||||
err = configFile.Decrypt(masterKey, "")
|
||||
@@ -331,23 +376,19 @@ func DownloadConfig(storage Storage, password string) (config *Config, isEncrypt
|
||||
config = CreateConfig()
|
||||
|
||||
err = json.Unmarshal(configFile.GetBytes(), config)
|
||||
|
||||
if err != nil {
|
||||
if bytes.Equal(configFile.GetBytes()[:9], []byte("duplicacy")) {
|
||||
return nil, true, fmt.Errorf("The storage is likely to have been initialized with a password before")
|
||||
} else {
|
||||
return nil, false, fmt.Errorf("Failed to parse the config file: %v", err)
|
||||
}
|
||||
return nil, false, fmt.Errorf("Failed to parse the config file: %v", err)
|
||||
}
|
||||
|
||||
return config, false, nil
|
||||
|
||||
}
|
||||
|
||||
func UploadConfig(storage Storage, config *Config, password string) bool {
|
||||
func UploadConfig(storage Storage, config *Config, password string, iterations int) bool {
|
||||
|
||||
// This is the key to encrypt the config file.
|
||||
var masterKey []byte
|
||||
salt := make([]byte, CONFIG_SALT_LENGTH)
|
||||
|
||||
if len(password) > 0 {
|
||||
|
||||
@@ -356,7 +397,13 @@ func UploadConfig(storage Storage, config *Config, password string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
masterKey = GenerateKeyFromPassword(password)
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
LOG_ERROR("CONFIG_KEY", "Failed to generate random salt: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
masterKey = GenerateKeyFromPassword(password, salt, iterations)
|
||||
}
|
||||
|
||||
description, err := json.MarshalIndent(config, "", " ")
|
||||
@@ -373,11 +420,26 @@ func UploadConfig(storage Storage, config *Config, password string) bool {
|
||||
if len(password) > 0 {
|
||||
// Encrypt the config file with masterKey. If masterKey is nil then no encryption is performed.
|
||||
err = chunk.Encrypt(masterKey, "")
|
||||
|
||||
if err != nil {
|
||||
LOG_ERROR("CONFIG_CREATE", "Failed to create the config file: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// The new encrypted format for config is CONFIG_HEADER + salt + #iterations + encrypted content
|
||||
encryptedLength := len(chunk.GetBytes()) + CONFIG_SALT_LENGTH + 4
|
||||
|
||||
// Copy to a temporary buffer to replace the header and add the salt and the number of iterations
|
||||
var encrypted bytes.Buffer
|
||||
encrypted.Write([]byte(CONFIG_HEADER))
|
||||
encrypted.Write(salt)
|
||||
binary.Write(&encrypted, binary.LittleEndian, uint32(iterations))
|
||||
encrypted.Write(chunk.GetBytes()[len(ENCRYPTION_HEADER):])
|
||||
|
||||
chunk.Reset(false)
|
||||
chunk.Write(encrypted.Bytes())
|
||||
if len(chunk.GetBytes()) != encryptedLength {
|
||||
LOG_ERROR("CONFIG_CREATE", "Encrypted config has %d bytes instead of expected %d bytes", len(chunk.GetBytes()), encryptedLength)
|
||||
}
|
||||
}
|
||||
|
||||
err = storage.UploadFile(0, "config", chunk.GetBytes())
|
||||
@@ -403,7 +465,7 @@ func UploadConfig(storage Storage, config *Config, password string) bool {
|
||||
// ConfigStorage makes the general storage space available for storing duplicacy format snapshots. In essence,
|
||||
// it simply creates a file named 'config' that stores various parameters as well as a set of keys if encryption
|
||||
// is enabled.
|
||||
func ConfigStorage(storage Storage, compressionLevel int, averageChunkSize int, maximumChunkSize int,
|
||||
func ConfigStorage(storage Storage, iterations int, compressionLevel int, averageChunkSize int, maximumChunkSize int,
|
||||
minimumChunkSize int, password string, copyFrom *Config) bool {
|
||||
|
||||
exist, _, _, err := storage.GetFileInfo(0, "config")
|
||||
@@ -423,5 +485,5 @@ func ConfigStorage(storage Storage, compressionLevel int, averageChunkSize int,
|
||||
return false
|
||||
}
|
||||
|
||||
return UploadConfig(storage, config, password)
|
||||
return UploadConfig(storage, config, password, iterations)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user