diff --git a/internal/age/age.go b/internal/age/age.go index db49a7f..1413375 100644 --- a/internal/age/age.go +++ b/internal/age/age.go @@ -4,7 +4,16 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd -// Package age implements file encryption according to age-encryption.org/v1. +// Package age implements file encryption according to the age-encryption.org/v1 +// specification. +// +// For most use cases, use the Encrypt and Decrypt functions with +// X25519Recipient and X25519Identity. If passphrase encryption is required, use +// ScryptRecipient and ScryptIdentity. For compatibility with existing SSH keys +// use the filippo.io/age/internal/agessh package. +// +// Age encrypted files are binary and not malleable, for encoding them as text, +// use the filippo.io/age/internal/armor package. package age import ( diff --git a/internal/age/age_test.go b/internal/age/age_test.go index bfe1bfa..7149faf 100644 --- a/internal/age/age_test.go +++ b/internal/age/age_test.go @@ -9,14 +9,89 @@ package age_test import ( "bytes" "crypto/rand" + "encoding/hex" + "fmt" "io" "io/ioutil" + "log" "testing" "filippo.io/age/internal/age" "golang.org/x/crypto/curve25519" ) +func ExampleEncrypt() { + publicKey := "age1cy0su9fwf3gf9mw868g5yut09p6nytfmmnktexz2ya5uqg9vl9sss4euqm" + recipient, err := age.ParseX25519Recipient(publicKey) + if err != nil { + log.Fatalf("Failed to parse public key %q: %v", publicKey, err) + } + + out := &bytes.Buffer{} + + w, err := age.Encrypt(out, recipient) + if err != nil { + log.Fatalf("Failed to create encrypted file: %v", err) + } + if _, err := io.WriteString(w, "Black lives matter."); err != nil { + log.Fatalf("Failed to write to encrypted file: %v", err) + } + if err := w.Close(); err != nil { + log.Fatalf("Failed to close encrypted file: %v", err) + } + + fmt.Printf("Encrypted file size: %d\n", out.Len()) + // Output: + // Encrypted file size: 219 +} + +var fileContents, _ = hex.DecodeString("6167652d656e6372797074696f6e2e6f72" + + "672f76310a2d3e20583235353139203868726c4d2b5a4247334464346646322b61353" + + "8337a64544957446b382f5234316b43595a7376775457340a794f345059646c4d5744" + + "4a2b437867554e527159355a30542f6d2b6733464368356a4978474c62435658630a2" + + "d2d2d20492f696d65765a7a79383132304a537a6d4a6e6d6e2f4b4d6b337035413131" + + "5638334e6b34316d394e50450a70c5e53624a1520753f92c5ad10ecab273ba4d61178" + + "07713e83820417a1df2ca08182272c8f85c857734a1311a3b75e98d0eaf") + +var privateKey = "AGE-SECRET-KEY-184JMZMVQH3E6U0PSL869004Y3U2NYV7R30EU99CSEDNPH02YUVFSZW44VU" + +func ExampleDecrypt() { + // DO NOT hardcode the private key. Store it in a secret storage solution, + // on disk if the local machine is trusted, or have the user provide it. + identity, err := age.ParseX25519Identity(privateKey) + if err != nil { + log.Fatalf("Failed to parse private key %q: %v", privateKey, err) + } + + out := &bytes.Buffer{} + f := bytes.NewReader(fileContents) + + r, err := age.Decrypt(f, identity) + if err != nil { + log.Fatalf("Failed to open encrypted file: %v", err) + } + if _, err := io.Copy(out, r); err != nil { + log.Fatalf("Failed to read encrypted file: %v", err) + } + + fmt.Printf("File contents: %q\n", out.Bytes()) + // Output: + // File contents: "Black lives matter." +} + +func ExampleGenerateX25519Identity() { + identity, err := age.GenerateX25519Identity() + if err != nil { + log.Fatalf("Failed to generate key pair: %v", err) + } + + fmt.Printf("Public key: %s...\n", identity.Recipient().String()[:4]) + fmt.Printf("Private key: %s...\n", identity.String()[:16]) + // Output: + // Public key: age1... + // Private key: AGE-SECRET-KEY-1... +} + const helloWorld = "Hello, Twitch!" func TestEncryptDecryptX25519(t *testing.T) { diff --git a/internal/armor/armor_test.go b/internal/armor/armor_test.go index a89466b..3317d80 100644 --- a/internal/armor/armor_test.go +++ b/internal/armor/armor_test.go @@ -9,12 +9,83 @@ package armor_test import ( "bytes" "encoding/pem" + "fmt" + "io" "io/ioutil" + "log" + "strings" "testing" + "filippo.io/age/internal/age" "filippo.io/age/internal/armor" ) +func ExampleNewWriter() { + publicKey := "age1cy0su9fwf3gf9mw868g5yut09p6nytfmmnktexz2ya5uqg9vl9sss4euqm" + recipient, err := age.ParseX25519Recipient(publicKey) + if err != nil { + log.Fatalf("Failed to parse public key %q: %v", publicKey, err) + } + + buf := &bytes.Buffer{} + armorWriter := armor.NewWriter(buf) + + w, err := age.Encrypt(armorWriter, recipient) + if err != nil { + log.Fatalf("Failed to create encrypted file: %v", err) + } + if _, err := io.WriteString(w, "Black lives matter."); err != nil { + log.Fatalf("Failed to write to encrypted file: %v", err) + } + if err := w.Close(); err != nil { + log.Fatalf("Failed to close encrypted file: %v", err) + } + + if err := armorWriter.Close(); err != nil { + log.Fatalf("Failed to close armor: %v", err) + } + + fmt.Printf("%s[...]", buf.Bytes()[:35]) + // Output: + // -----BEGIN AGE ENCRYPTED FILE----- + // [...] +} + +var privateKey = "AGE-SECRET-KEY-184JMZMVQH3E6U0PSL869004Y3U2NYV7R30EU99CSEDNPH02YUVFSZW44VU" + +func ExampleNewReader() { + fileContents := `-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4YWdhZHZ0WG1PZldDT1hD +K3RPRzFkUlJnWlFBQlUwemtjeXFRMFp6V1VFCnRzZFV3a3Vkd1dSUWw2eEtrRkVv +SHcvZnp6Q3lqLy9HMkM4ZjUyUGdDZjQKLS0tIDlpVUpuVUQ5YUJyUENFZ0lNSTB2 +ekUvS3E5WjVUN0F5ZWR1ejhpeU5rZUUKsvPGYt7vf0o1kyJ1eVFMz1e4JnYYk1y1 +kB/RRusYjn+KVJ+KTioxj0THtzZPXcjFKuQ1 +-----END AGE ENCRYPTED FILE-----` + + // DO NOT hardcode the private key. Store it in a secret storage solution, + // on disk if the local machine is trusted, or have the user provide it. + identity, err := age.ParseX25519Identity(privateKey) + if err != nil { + log.Fatalf("Failed to parse private key %q: %v", privateKey, err) + } + + out := &bytes.Buffer{} + f := strings.NewReader(fileContents) + armorReader := armor.NewReader(f) + + r, err := age.Decrypt(armorReader, identity) + if err != nil { + log.Fatalf("Failed to open encrypted file: %v", err) + } + if _, err := io.Copy(out, r); err != nil { + log.Fatalf("Failed to read encrypted file: %v", err) + } + + fmt.Printf("File contents: %q\n", out.Bytes()) + // Output: + // File contents: "Black lives matter." +} + func TestArmor(t *testing.T) { buf := &bytes.Buffer{} w := armor.NewWriter(buf)