Source

stego / demo / sshuf.go

Full commit
// 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.")
	numeric       = flag.Bool("n", false, "Compare according to string numerical value.")
	// TODO:
	// 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)
}

func parse(line []byte) (num int64) {
	for i, b := range line {
		if b < '0' || '9' < b {
			if i == 0 {
				fail("could not find a number in a line")
			}
			break
		}
		num *= 10
		num += int64(b)
	}
	return
}

// 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
	if *numeric {
		var lines []intbytes
		prev := 0
		for i := 0; i < len(input); i++ {
			if input[i] == delim {
				line := input[prev:i]
				lines = append(lines, intbytes{parse(line), line})
				prev = i + 1
			}
		}
		if prev != len(input) {
			line := input[prev:len(input)]
			lines = append(lines, intbytes{parse(line), line})
		}
		return ibslice(lines)
	}
	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()

		switch ls := lines.(type) {
		case byteslice:
			for _, line := range ls {
				b.Write(line)
				b.WriteByte(delim)
			}
		case ibslice:
			for _, line := range ls {
				b.Write(line.b)
				b.WriteByte(delim)
			}
		}
	}
}

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 }

type intbytes struct {
	n int64
	b []byte
}
type ibslice []intbytes

func (s ibslice) Len() int           { return len(s) }
func (s ibslice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s ibslice) Less(i, j int) bool { return s[i].n < s[j].n }