package main import ( "bufio" "crypto/rand" "encoding/base64" "errors" "fmt" "io" "os" "strings" _ "filippo.io/age" "filippo.io/age/armor" ) //var cert *x509.Certificate //var key *rsa.PrivateKey // //func encryptBytes(data []byte) []byte { // if cert == nil || key == nil { // loadCerts() // } // // encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, cert.PublicKey.(*rsa.PublicKey), data) // if err != nil { // fmt.Println("Error encrypting data,", err) // os.Exit(1) // } // return encrypted //} // //func decryptBytes(data []byte) []byte { // if cert == nil || key == nil { // loadCerts() // } // // decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, key, data) // if err != nil { // fmt.Println("Error decrypting data,", err) // os.Exit(1) // } // return decrypted //} // //func loadCerts() { // var err error // certBytes, err := os.ReadFile(config.GetAsString("Crypto.cert_path")) // keyBytes, err := os.ReadFile(config.GetAsString("Crypto.key_path")) // if err != nil { // fmt.Println("Error reading cert or key,", err) // os.Exit(1) // } // // cert, err = x509.ParseCertificate(certBytes) // if err != nil { // fmt.Println("Error parsing certificate,", err) // os.Exit(1) // } // key, err = x509.ParsePKCS1PrivateKey(keyBytes) // if err != nil { // fmt.Println("Error parsing private key,", err) // } //} // GenerateKey returns a base64-encoded 32-byte random key suitable to use as the // symmetric passphrase for age scrypt mode. Store this securely (never in Git). func GenerateKey() (string, error) { k := make([]byte, 32) if _, err := rand.Read(k); err != nil { return "", err } out := make([]byte, base64.StdEncoding.EncodedLen(len(k))) base64.StdEncoding.Encode(out, k) return string(out), nil } // LoadKeyFromFile reads a key file that contains either a raw base64 string or // "AGE_SYM_KEY=" (handy for dotenv). Whitespace is trimmed. func LoadKeyFromFile(path string) (string, error) { b, err := os.ReadFile(path) if err != nil { return "", err } s := strings.TrimSpace(string(b)) if i := strings.Index(s, "AGE_SYM_KEY="); i >= 0 { s = strings.TrimSpace(strings.TrimPrefix(s, "AGE_SYM_KEY=")) } if s == "" { return "", errors.New("empty symmetric key") } // Quick sanity check that it’s base64 and ~32 bytes after decode. if _, err := base64.StdEncoding.DecodeString(s); err != nil { return "", fmt.Errorf("invalid base64 key: %w", err) } return s, nil } // Encrypt streams plaintext from r to w using a symmetric passphrase. // If armorOut is true, output is ASCII-armored (BEGIN AGE ENCRYPTED FILE). func Encrypt(r io.Reader, w io.Writer, passphrase string, armorOut bool) error { passphrase = strings.TrimSpace(passphrase) if passphrase == "" { return errors.New("missing passphrase") } var out io.WriteCloser var err error if armorOut { aw := armor.NewWriter(w) defer aw.Close() //out, err = age.Encrypt(aw, age.NewScryptRecipient(passphrase)) } else { //out, err = age.Encrypt(w, age.NewScryptRecipient(passphrase)) } if err != nil { return err } _, copyErr := io.Copy(out, bufio.NewReader(r)) closeErr := out.Close() if copyErr != nil { return copyErr } return closeErr } // Decrypt streams ciphertext from r to w using the same symmetric passphrase. // It auto-detects armored vs binary ciphertext. func Decrypt(r io.Reader, w io.Writer, passphrase string) error { passphrase = strings.TrimSpace(passphrase) if passphrase == "" { return errors.New("missing passphrase") } br := bufio.NewReader(r) peek, _ := br.Peek(32) //var in io.Reader = br if strings.HasPrefix(string(peek), "-----BEGIN AGE ENCRYPTED FILE-----") { // in = armor.NewReader(br) } //dr, err := age.Decrypt(in, age.NewScryptIdentity(passphrase)) //if err != nil { // return err //} //_, err = io.Copy(w, bufio.NewWriter(wrap0600(w))) //return err return nil } // wrap0600 ensures that when w is an *os.File newly created by caller, // its perms are 0600. If it’s not an *os.File, it’s returned unchanged. func wrap0600(w io.Writer) io.Writer { if f, ok := w.(*os.File); ok { _ = f.Chmod(0600) } return w }