From 480e8df133abf399dbbbceb18583ca6435428558 Mon Sep 17 00:00:00 2001 From: lukechampine Date: Sun, 24 Sep 2017 03:49:35 -0400 Subject: [PATCH] add slink --- README.md | 10 +++- cmd/slink/README.md | 26 ++++++++++ cmd/slink/main.go | 121 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 cmd/slink/README.md create mode 100644 cmd/slink/main.go diff --git a/README.md b/README.md index 250c345..95f4ed6 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,17 @@ Note that the data is not demarcated in any way; the caller is responsible for determining which bytes of `hidden` it cares about. The easiest way to do this is to prepend the data with its length. -A `jsteg` command is also included, providing a simple wrapper around the +A `jsteg` command is included, providing a simple wrapper around the functions of this package. It can hide and reveal data in jpeg files and supports input/output redirection. It automatically handles length prefixes and uses a magic header to identify jpegs that were produced by `jsteg`. -Binaries can be found [here](https://github.com/lukechampine/jsteg/releases). + +A more narrowly-focused command named `slink` is also included. `slink` embeds +a public key in an jpeg, and makes it easy to sign data and verify signatures +using keypairs derived from password strings. See [cmd/slink](cmd/slink) for a +full description. + +Binaries for both commands can be found [here](https://github.com/lukechampine/jsteg/releases). --- diff --git a/cmd/slink/README.md b/cmd/slink/README.md new file mode 100644 index 0000000..c30c9be --- /dev/null +++ b/cmd/slink/README.md @@ -0,0 +1,26 @@ +slink +----- + +`slink` is a tool for proving pseudo-ownership of JPEG files. It is intended +as a way for content creators to invisibly watermark their images. + +`slink` embeds a public key in a JPEG file, and makes it easy to sign +arbitrary data with the corresponding private key, producing a signature that +`slink` can also verify. Keypairs are derived from password strings. The +intended scenario is: + +1. Content creator creates a JPEG file and embeds their public key in it +2. Content creator posts JPEG publicly +3. Someone else claims to have created the JPEG +4. Content creator proves ownership by signing arbitrary data with their key + +To strengthen the claim, the content creator should sign a piece of data +supplied by the challenger. Furthermore, they can prove that the image was +created no later than a certain date by registering it with a notary service, +such as https://opentimestamps.org. + +This is not a perfect way of assigning ownership to files. Anyone can take an +image in the public domain, embed their public key, and claim that they +created it. The only way to disprove such a claim is to find a copy of the +image that does not include the public key, or that predates it. Users should +be aware of what `slink` guarantees and what it does not. diff --git a/cmd/slink/main.go b/cmd/slink/main.go new file mode 100644 index 0000000..9b8b3af --- /dev/null +++ b/cmd/slink/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "image/jpeg" + "log" + "os" + "path/filepath" + "strings" + + "golang.org/x/crypto/ed25519" + + "github.com/lukechampine/jsteg" +) + +const magic = "slink" + +func usage() { + log.Fatalf(`Usage: %s [command] [args] + +Commands: + claim img.jpg password Embed a public key + prove data password Sign arbitrary data + verify img.jpg data signature Verify a signature + +Use claim to embed your public key in an image. Later, you can +use prove to demonstrate that you control the private key paired +with the public key. Use verify to verify that a signature was +generated from the same keypair embedded in the image. +`, os.Args[0]) +} + +func requireArgs(n int) { + if len(os.Args[2:]) != n { + usage() + } +} + +func keypair(password string) (ed25519.PublicKey, ed25519.PrivateKey) { + h := sha256.Sum256([]byte(password)) + pk, sk, _ := ed25519.GenerateKey(bytes.NewReader(h[:])) + return pk, sk +} + +func main() { + log.SetFlags(0) + if len(os.Args) < 2 { + usage() + } + switch os.Args[1] { + default: + usage() + + case "claim": + requireArgs(2) + + infile, err := os.Open(os.Args[2]) + if err != nil { + log.Fatal(err) + } + defer infile.Close() + img, err := jpeg.Decode(infile) + if err != nil { + log.Fatal(err) + } + + outPath := strings.TrimSuffix(os.Args[2], filepath.Ext(os.Args[2])) + ".claimed.jpg" + outfile, err := os.Create(outPath) + if err != nil { + log.Fatal(err) + } + defer outfile.Close() + + data := make([]byte, len(magic)+ed25519.PublicKeySize) + copy(data, magic) + pk, _ := keypair(os.Args[3]) + copy(data[len(magic):], pk[:]) + + err = jsteg.Hide(outfile, img, data, nil) + if err != nil { + log.Fatal(err) + } + os.Stdout.WriteString("Wrote claimed jpeg to " + outPath + "\n") + + case "prove": + requireArgs(2) + + _, sk := keypair(os.Args[3]) + sig := ed25519.Sign(sk, []byte(os.Args[2])) + os.Stdout.WriteString(base64.StdEncoding.EncodeToString(sig) + "\n") + + case "verify": + requireArgs(3) + + infile, err := os.Open(os.Args[2]) + if err != nil { + log.Fatal(err) + } + defer infile.Close() + data, err := jsteg.Reveal(infile) + if err != nil { + log.Fatal(err) + } + + if len(data) < len(magic)+ed25519.PublicKeySize || string(data[:len(magic)]) != magic { + log.Fatal("Image was not signed with slink") + } + pk := ed25519.PublicKey(data[len(magic):][:ed25519.PublicKeySize]) + sig, err := base64.StdEncoding.DecodeString(os.Args[4]) + if err != nil { + log.Fatal("Invalid signature") + } + if ed25519.Verify(pk, []byte(os.Args[3]), sig) { + log.Println("Verified OK") + } else { + log.Fatal("Bad signature") + } + } +}