added encryption
This commit is contained in:
parent
a83f507ed8
commit
ea3524614d
86 changed files with 8704 additions and 34 deletions
276
stego.go
276
stego.go
|
|
@ -3,7 +3,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
|
@ -12,55 +16,221 @@ import (
|
|||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const offset = 4
|
||||
const (
|
||||
KeySize = chacha20poly1305.KeySize
|
||||
NonceSize = chacha20poly1305.NonceSize
|
||||
Overhead = chacha20poly1305.Overhead
|
||||
|
||||
ChunkSize = 512
|
||||
FullSize = ChunkSize + Overhead
|
||||
)
|
||||
|
||||
func deriveKey(key, nonce []byte, info string, length int) (k []byte) {
|
||||
h := hkdf.New(sha256.New, key, nonce, []byte(info))
|
||||
k = make([]byte, length)
|
||||
if _, err := io.ReadFull(h, k); err != nil {
|
||||
panic("crypto: internal error: failed to read from HKDF: " + err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const encodedSizeLen = 4
|
||||
|
||||
func encodeLen(l int) []byte {
|
||||
b := make([]byte, offset)
|
||||
b := make([]byte, encodedSizeLen)
|
||||
binary.LittleEndian.PutUint32(b, uint32(l))
|
||||
return b
|
||||
}
|
||||
|
||||
func getLen(p image.Image) int {
|
||||
z := make([]byte, offset)
|
||||
for i := 0; i < offset; i++ {
|
||||
pix := color.NRGBAModel.Convert(p.At(i, 0)).(color.NRGBA)
|
||||
z[i] = (pix.R << 6)
|
||||
z[i] |= (pix.G << 6) >> 2
|
||||
z[i] |= (pix.B << 6) >> 4
|
||||
z[i] |= (pix.A << 6) >> 6
|
||||
}
|
||||
return int(binary.LittleEndian.Uint32(z))
|
||||
}
|
||||
|
||||
func unseal(p image.Image) (b []byte) {
|
||||
func extract(p image.Image, i, j, size int) (int, int, []byte) {
|
||||
b := make([]byte, size)
|
||||
rect := p.Bounds()
|
||||
l := getLen(p)
|
||||
c := 0
|
||||
for i := 0; i < rect.Max.Y; i++ {
|
||||
for j := 0; j < rect.Max.X; j++ {
|
||||
if i == 0 && j == 0 {
|
||||
j = offset
|
||||
for ; i < rect.Max.Y; i++ {
|
||||
for ; j < rect.Max.X; j++ {
|
||||
if c == size {
|
||||
return i, j, b
|
||||
}
|
||||
pix := color.NRGBAModel.Convert(p.At(j, i)).(color.NRGBA)
|
||||
z := (pix.R << 6)
|
||||
z |= (pix.G << 6) >> 2
|
||||
z |= (pix.B << 6) >> 4
|
||||
z |= (pix.A << 6) >> 6
|
||||
if c == l {
|
||||
return
|
||||
}
|
||||
b = append(b, z)
|
||||
b[c] = z
|
||||
c++
|
||||
}
|
||||
j = 0
|
||||
}
|
||||
return i, j, b
|
||||
}
|
||||
|
||||
func bytesToLen(b []byte) int {
|
||||
return int(binary.LittleEndian.Uint32(b))
|
||||
}
|
||||
|
||||
func Size(s int) int {
|
||||
return s + int(math.Ceil(float64(s)/ChunkSize)*Overhead)
|
||||
}
|
||||
|
||||
func unseal(p image.Image, key []byte) (b []byte) {
|
||||
if key != nil {
|
||||
i, j, nonce := extract(p, 0, 0, NonceSize)
|
||||
k := deriveKey(key, nonce, infoEncryption, KeySize)
|
||||
s := NewAEADStream(k).Decrypt()
|
||||
i, j, firstBlock := extract(p, i, j, FullSize)
|
||||
dec := make([]byte, ChunkSize)
|
||||
s.XORKeyStream(dec, firstBlock)
|
||||
l := bytesToLen(dec[:encodedSizeLen]) + encodedSizeLen - ChunkSize
|
||||
_, _, data := extract(p, i, j, Size(l))
|
||||
b = make([]byte, l)
|
||||
s.XORKeyStream(b, data)
|
||||
return append(dec[encodedSizeLen:], b...)
|
||||
}
|
||||
i, j, lbuf := extract(p, 0, 0, encodedSizeLen)
|
||||
_, _, b = extract(p, i, j, bytesToLen(lbuf))
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
infoEncryption = "ChaCha20-Poly1305 encryption key"
|
||||
infoFiller = "ChaCha20-Poly1305 randomness filler"
|
||||
infoFillerNonce = "ChaCha20-Poly1305 filler nonce"
|
||||
infoFillerStart = "RandomnessFiller"
|
||||
)
|
||||
|
||||
func filler(key, nonce []byte, l int) (r []byte) {
|
||||
k := deriveKey(key, nonce, infoFiller, KeySize)
|
||||
n := deriveKey(key, nonce, infoFillerNonce, NonceSize)
|
||||
c, err := chacha20.NewUnauthenticatedCipher(k, n)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var a, b [ChunkSize]byte
|
||||
copy(a[:], []byte(infoFillerStart))
|
||||
for len(r) < l {
|
||||
c.XORKeyStream(b[:], a[:])
|
||||
r = append(r, b[:]...)
|
||||
a = b
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func seal(p image.Image, s []byte) (image.Image, error) {
|
||||
type AEADStream struct {
|
||||
c cipher.AEAD
|
||||
nonce [NonceSize]byte
|
||||
counter uint64
|
||||
}
|
||||
|
||||
func NewAEADStream(key []byte) *AEADStream {
|
||||
c, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &AEADStream{c: c}
|
||||
}
|
||||
func (aead *AEADStream) Encrypt() cipher.Stream {
|
||||
return &Encrypt{aead}
|
||||
}
|
||||
func (aead *AEADStream) Decrypt() cipher.Stream {
|
||||
return &Decrypt{aead}
|
||||
}
|
||||
func (aead *AEADStream) NonceSize() int {
|
||||
return aead.c.NonceSize()
|
||||
}
|
||||
func (aead *AEADStream) Overhead() int {
|
||||
return aead.c.Overhead()
|
||||
}
|
||||
func (aead *AEADStream) Seal(dst, _, plaintext, additionalData []byte) []byte {
|
||||
aead.inc()
|
||||
b := aead.c.Seal(dst, aead.nonce[:], plaintext, additionalData)
|
||||
return b
|
||||
}
|
||||
func (aead *AEADStream) Open(dst, _, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
aead.inc()
|
||||
b, err := aead.c.Open(dst, aead.nonce[:], ciphertext, additionalData)
|
||||
return b, err
|
||||
}
|
||||
func (aead *AEADStream) inc() {
|
||||
put(&aead.nonce, aead.counter)
|
||||
aead.counter++
|
||||
}
|
||||
|
||||
func put(nonce *[NonceSize]byte, n uint64) {
|
||||
binary.BigEndian.PutUint64((*nonce)[3:11], n)
|
||||
}
|
||||
|
||||
type Encrypt struct {
|
||||
cipher.AEAD
|
||||
}
|
||||
|
||||
func h(b []byte, v ...any) {
|
||||
fmt.Print(hex.EncodeToString(b) + " ")
|
||||
fmt.Println(v...)
|
||||
}
|
||||
|
||||
func (e *Encrypt) XORKeyStream(dst, src []byte) {
|
||||
size := ChunkSize
|
||||
var buffer []byte
|
||||
var a [FullSize]byte
|
||||
for len(src) > 0 {
|
||||
if len(src) < size {
|
||||
size = len(src)
|
||||
}
|
||||
b := src[:size]
|
||||
src = src[size:]
|
||||
buf := e.Seal(a[:0], nil, b, nil)
|
||||
buffer = append(buffer, buf...)
|
||||
}
|
||||
copy(dst, buffer)
|
||||
}
|
||||
|
||||
type Decrypt struct {
|
||||
cipher.AEAD
|
||||
}
|
||||
|
||||
func (d *Decrypt) XORKeyStream(dst, src []byte) {
|
||||
size := FullSize
|
||||
var buffer []byte
|
||||
var a [ChunkSize]byte
|
||||
for len(src) > 0 {
|
||||
if len(src) < size {
|
||||
size = len(src)
|
||||
}
|
||||
b := src[:size]
|
||||
src = src[size:]
|
||||
buf, err := d.Open(a[:0], nil, b, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buffer = append(buffer, buf...)
|
||||
}
|
||||
copy(dst, buffer)
|
||||
}
|
||||
|
||||
func encrypt(data, key []byte, max int) (enc []byte, err error) {
|
||||
nonce := make([]byte, NonceSize)
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
k := deriveKey(key, nonce, infoEncryption, KeySize)
|
||||
s := NewAEADStream(k).Encrypt()
|
||||
enc = make([]byte, Size(len(data)))
|
||||
s.XORKeyStream(enc, data)
|
||||
enc = append(append(nonce, enc...), filler(key, nonce, max)...)[:max]
|
||||
return
|
||||
}
|
||||
|
||||
func seal(p image.Image, s, key []byte) (img image.Image, err error) {
|
||||
rect := p.Bounds()
|
||||
n := image.NewNRGBA(rect)
|
||||
c := 0
|
||||
|
|
@ -70,14 +240,22 @@ func seal(p image.Image, s []byte) (image.Image, error) {
|
|||
if l > max {
|
||||
return nil, errors.New("not enough space in image")
|
||||
}
|
||||
if key != nil {
|
||||
arr, err = encrypt(arr, key, max)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
l = max
|
||||
}
|
||||
for i := 0; i < rect.Max.Y; i++ {
|
||||
for j := 0; j < rect.Max.X; j++ {
|
||||
pix := color.NRGBAModel.Convert(p.At(j, i)).(color.NRGBA)
|
||||
if c < l {
|
||||
r := (pix.R &^ 3) | (arr[c] >> 6)
|
||||
g := (pix.G &^ 3) | (arr[c]<<2)>>6
|
||||
b := (pix.B &^ 3) | (arr[c]<<4)>>6
|
||||
a := (pix.A &^ 3) | (arr[c]<<6)>>6
|
||||
z := arr[c]
|
||||
r := (pix.R &^ 3) | (z >> 6)
|
||||
g := (pix.G &^ 3) | (z<<2)>>6
|
||||
b := (pix.B &^ 3) | (z<<4)>>6
|
||||
a := (pix.A &^ 3) | (z<<6)>>6
|
||||
n.Set(j, i, color.NRGBA{r, g, b, a})
|
||||
c++
|
||||
} else {
|
||||
|
|
@ -91,21 +269,51 @@ func seal(p image.Image, s []byte) (image.Image, error) {
|
|||
var (
|
||||
inFile string
|
||||
outFile string
|
||||
keyFile string
|
||||
secret string
|
||||
)
|
||||
|
||||
func readKey() (b []byte, err error) {
|
||||
if keyFile != "" {
|
||||
if keyFile == "-" {
|
||||
b, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
b, err = os.ReadFile(keyFile)
|
||||
}
|
||||
} else {
|
||||
if k := os.Getenv(EnvKey); k != "" {
|
||||
b = []byte(k)
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return hex.DecodeString(string(b))
|
||||
}
|
||||
|
||||
func errf(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
const EnvKey = "STEGO_KEY"
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&inFile, "in", "-", "input file (default: stdin)")
|
||||
flag.StringVar(&keyFile, "key", "", "key file; used for encryption")
|
||||
flag.StringVar(&outFile, "out", "-", "output file, always png (default: stdout)")
|
||||
flag.StringVar(&secret, "secret", "", "secret file to hide")
|
||||
flag.Parse()
|
||||
if inFile == "-" && keyFile == "-" {
|
||||
errf(errors.New("can not read input and key files from stdin"))
|
||||
}
|
||||
key, err := readKey()
|
||||
if err != nil {
|
||||
errf(err)
|
||||
}
|
||||
var (
|
||||
err error
|
||||
in = os.Stdin
|
||||
out = os.Stdout
|
||||
)
|
||||
|
|
@ -126,11 +334,11 @@ func main() {
|
|||
errf(err)
|
||||
}
|
||||
if secret != "" {
|
||||
b, err := ioutil.ReadFile(secret)
|
||||
b, err := os.ReadFile(secret)
|
||||
if err != nil {
|
||||
errf(err)
|
||||
}
|
||||
n, err := seal(p, b)
|
||||
n, err := seal(p, b, key)
|
||||
if err != nil {
|
||||
errf(err)
|
||||
}
|
||||
|
|
@ -140,7 +348,7 @@ func main() {
|
|||
}
|
||||
return
|
||||
}
|
||||
_, err = out.Write(unseal(p))
|
||||
_, err = out.Write(unseal(p, key))
|
||||
if err != nil {
|
||||
errf(err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue