Source

stego / demo / sshuf.go

// sshuf: The sneaky shuffler; similar to shuf command, but sneakier

package main

import (
	"../../stego"
	"../../stego/permute"
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"sort"
)

var (
	decryption    = flag.Bool("d", false, "Extract and decrypt hidden data.")
	plaintextFile = flag.String("p", "", "Optional source of plaintext data.")
	outfile       = flag.String("o", "", "Optional output file.")
	// TODO:
	// numeric = flag.Bool("n", false, "Compare according to string numerical value.")
	// inputRange = flag.Bool("i", false, "(LO-HI) Treat each number LO HI as input line")
	// delim = flag.String("e", "\n", "Line delimiter")
)

const delim = '\n'

func usage() {
	fmt.Fprintln(os.Stderr, "Usage: sshuf [-d] [-p plaintext] FILE")
	flag.PrintDefaults()
	os.Exit(1)
}

func fail(m interface{}) {
	fmt.Fprintln(os.Stderr, "error:", m)
	os.Exit(1)
}

// getInputList reads the input to memory, then returns a slice of lineslices
// pointing into this list.
func getInputList() sort.Interface {
	if flag.NArg() != 1 {
		usage()
	}
	input, err := ioutil.ReadFile(flag.Arg(0))
	if err != nil {
		fail(err)
	}

	// Assemble slice of lineslices
	var lines [][]byte
	prev := 0
	for i := 0; i < len(input); i++ {
		if input[i] == delim {
			lines = append(lines, input[prev:i])
			prev = i + 1
		}
	}
	if prev != len(input) {
		lines = append(lines, input[prev:len(input)])
	}
	return byteslice(lines)
}

func getPlaintext() (plaintext []byte) {
	var err error
	if *plaintextFile != "" {
		if *decryption {
			usage()
		}
		plaintext, err = ioutil.ReadFile(*plaintextFile)
		if err != nil {
			fail(err)
		}
	} else if !*decryption {
		fmt.Fprint(os.Stderr, "Plaintext: ")
		fmt.Scanln(&plaintext)
	}
	return
}

func getPasswd() (passphrase []byte) {
	// Using the most portable tool I could find: python
	fmt.Fprint(os.Stderr, "Passphrase: ")
	cmd := exec.Command("python", "-c",
		"import getpass; print(getpass.getpass(''))")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		fail(err)
	}
	passphrase = out.Bytes()
	if passphrase[len(passphrase)-1] == '\n' {
		passphrase = passphrase[:len(passphrase)-1]
	}
	return
}

func main() {
	flag.Parse()

	lines := getInputList()
	bh := permute.NewHider(lines)
	var plaintext []byte
	var err error
	plaintext = getPlaintext()
	passphrase := getPasswd()

	var outstream io.Writer
	if *outfile == "" {
		outstream = os.Stdout
	} else {
		f, err := os.Create(*outfile)
		if err != nil {
			fail(err)
		}
		defer f.Close()
		outstream = f
	}

	if *decryption {
		plaintext, err = stego.PassphraseFindData(bh, passphrase)
		if err != nil {
			fail("nothing found with that passphrase")
		}
		outstream.Write(plaintext)
	} else {
		err = stego.PassphraseHideData(bh, plaintext, passphrase)
		if err != nil {
			fail(err)
		}
		b := bufio.NewWriter(outstream)
		defer b.Flush()

		endline := []byte{delim}
		for _, line := range lines.(byteslice) {
			b.Write(line)
			b.Write(endline)
		}
	}
}

type byteslice [][]byte

func (b byteslice) Len() int           { return len(b) }
func (b byteslice) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b byteslice) Less(i, j int) bool { return bytes.Compare(b[i], b[j]) < 0 }