Add decryption tool for auth encrypted export
This commit is contained in:
parent
50ebcdd1f0
commit
286940a5d1
6 changed files with 248 additions and 0 deletions
70
migration-guides/decrypt/crypt.go
Normal file
70
migration-guides/decrypt/crypt.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/jamesruan/sodium"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deriveArgonKey generates a 32-bit cryptographic key using the Argon2id algorithm.
|
||||||
|
// Parameters:
|
||||||
|
// - password: The plaintext password to be hashed.
|
||||||
|
// - salt: The salt as a base64 encoded string.
|
||||||
|
// - memLimit: The memory limit in bytes.
|
||||||
|
// - opsLimit: The number of iterations.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A byte slice representing the derived key.
|
||||||
|
// - An error object, which is nil if no error occurs.
|
||||||
|
func deriveArgonKey(password, salt string, memLimit, opsLimit int) ([]byte, error) {
|
||||||
|
if memLimit < 1024 || opsLimit < 1 {
|
||||||
|
return nil, fmt.Errorf("invalid memory or operation limits")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode salt from base64
|
||||||
|
saltBytes, err := base64.StdEncoding.DecodeString(salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid salt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate key using Argon2id
|
||||||
|
// Note: We're assuming a fixed key length of 32 bytes and changing the threads
|
||||||
|
key := argon2.IDKey([]byte(password), saltBytes, uint32(opsLimit), uint32(memLimit/1024), 1, 32)
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptChaCha20poly1305 decrypts the given data using the ChaCha20-Poly1305 algorithm.
|
||||||
|
// Parameters:
|
||||||
|
// - data: The encrypted data as a byte slice.
|
||||||
|
// - key: The key for decryption as a byte slice.
|
||||||
|
// - nonce: The nonce for decryption as a byte slice.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A byte slice representing the decrypted data.
|
||||||
|
// - An error object, which is nil if no error occurs.
|
||||||
|
func decryptChaCha20poly1305(data []byte, key []byte, nonce []byte) ([]byte, error) {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
header := sodium.SecretStreamXCPHeader{Bytes: nonce}
|
||||||
|
decoder, err := sodium.MakeSecretStreamXCPDecoder(
|
||||||
|
sodium.SecretStreamXCPKey{Bytes: key},
|
||||||
|
reader,
|
||||||
|
header)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to make secret stream decoder", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Buffer to store the decrypted data
|
||||||
|
decryptedData := make([]byte, len(data))
|
||||||
|
n, err := decoder.Read(decryptedData)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
log.Println("Failed to read from decoder", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return decryptedData[:n], nil
|
||||||
|
}
|
53
migration-guides/decrypt/crypt_test.go
Normal file
53
migration-guides/decrypt/crypt_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
password = "test_password"
|
||||||
|
kdfSalt = "vd0dcYMGNLKn/gpT6uTFTw=="
|
||||||
|
memLimit = 64 * 1024 * 1024 // 64MB
|
||||||
|
opsLimit = 2
|
||||||
|
cipherText = "kBXQ2PuX6y/aje5r22H0AehRPh6sQ0ULoeAO"
|
||||||
|
cipherNonce = "v7wsI+BFZsRMIjDm3rTxPhmi/CaUdkdJ"
|
||||||
|
expectedPlainText = "plain_text"
|
||||||
|
expectedDerivedKey = "vp8d8Nee0BbIML4ab8Cp34uYnyrN77cRwTl920flyT0="
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeriveArgonKey(t *testing.T) {
|
||||||
|
derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to derive key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if base64.StdEncoding.EncodeToString(derivedKey) != expectedDerivedKey {
|
||||||
|
t.Fatalf("Derived key does not match expected key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecryptChaCha20poly1305(t *testing.T) {
|
||||||
|
derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to derive key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedCipherText, err := base64.StdEncoding.DecodeString(cipherText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode cipher text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedCipherNonce, err := base64.StdEncoding.DecodeString(cipherNonce)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode cipher nonce: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedText, err := decryptChaCha20poly1305(decodedCipherText, derivedKey, decodedCipherNonce)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decrypt: %v", err)
|
||||||
|
}
|
||||||
|
if string(decryptedText) != expectedPlainText {
|
||||||
|
t.Fatalf("Decrypted text : %s does not match the expected text: %s", string(decryptedText), expectedPlainText)
|
||||||
|
}
|
||||||
|
}
|
BIN
migration-guides/decrypt/decrypt
Executable file
BIN
migration-guides/decrypt/decrypt
Executable file
Binary file not shown.
105
migration-guides/decrypt/decrypt.go
Normal file
105
migration-guides/decrypt/decrypt.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Export struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
KDFParams KDF `json:"kdfParams"`
|
||||||
|
EncryptedData string `json:"encryptedData"`
|
||||||
|
EncryptionNonce string `json:"encryptionNonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KDF struct {
|
||||||
|
MemLimit int `json:"memLimit"`
|
||||||
|
OpsLimit int `json:"opsLimit"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePath(path string) (string, error) {
|
||||||
|
if path[:2] != "~/" {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return home + path[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if len(os.Args) != 4 {
|
||||||
|
fmt.Println("Usage: ./decrypt <export_file> <password> <output_file>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exportFile, err := resolvePath(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error resolving exportFile path:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password := os.Args[2]
|
||||||
|
outputFile, err := resolvePath(os.Args[3])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error resolving outputFile path:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(exportFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var export Export
|
||||||
|
if err := json.Unmarshal(data, &export); err != nil {
|
||||||
|
fmt.Println("Error parsing JSON:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if export.Version != 1 {
|
||||||
|
fmt.Println("Unsupported version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := base64.StdEncoding.DecodeString(export.EncryptedData)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error decoding encrypted data:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, err := base64.StdEncoding.DecodeString(export.EncryptionNonce)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error decoding nonce:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := deriveArgonKey(password, export.KDFParams.Salt, export.KDFParams.MemLimit, export.KDFParams.OpsLimit)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error deriving key:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedData, err := decryptChaCha20poly1305(encryptedData, key, nonce)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error decrypting data:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputFile, decryptedData, 0644); err != nil {
|
||||||
|
fmt.Println("Error writing decrypted data to file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Decrypted data written to %s\n", outputFile)
|
||||||
|
}
|
12
migration-guides/decrypt/go.mod
Normal file
12
migration-guides/decrypt/go.mod
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module decrypt
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jamesruan/sodium v1.0.14
|
||||||
|
golang.org/x/crypto v0.11.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
|
)
|
8
migration-guides/decrypt/go.sum
Normal file
8
migration-guides/decrypt/go.sum
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
github.com/GoKillers/libsodium-go v0.0.0-20171022220152-dd733721c3cb h1:ilqSFSbR1fq6x88heeHrvAqlg+ES+tZk2ZcaCmiH1gI=
|
||||||
|
github.com/GoKillers/libsodium-go v0.0.0-20171022220152-dd733721c3cb/go.mod h1:72TQeEkiDH9QMXZa5nJJvZre0UjqqO67X2QEIoOwCRU=
|
||||||
|
github.com/jamesruan/sodium v1.0.14 h1:JfOHobip/lUWouxHV3PwYwu3gsLewPrDrZXO3HuBzUU=
|
||||||
|
github.com/jamesruan/sodium v1.0.14/go.mod h1:GK2+LACf7kuVQ9k7Irk0MB2B65j5rVqkz+9ylGIggZk=
|
||||||
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
Loading…
Reference in a new issue