xs/vendor/blitter.com/go/hopscotch/hopscotch.go

167 lines
4.3 KiB
Go

// Package hopscotch - a crypto doodle that uses multiple hash
// algorithm outputs as dynamic sbox/pbox material
//
// Properties visualized using https://github.com/circulosmeos/circle
package hopscotch
// TODOs:
// -define s-box rotation/shuffle schema
// -devise p-box schema
// ...
import (
"errors"
"fmt"
"hash"
"io"
"time"
mtwist "blitter.com/go/mtwist" // Used to derive hash fodder after seeding w/key
// hash algos must be manually imported thusly:
// (Would be nice if the golang pkg docs were more clear
// on this...)
"crypto/sha512"
_ "crypto/sha512"
b2b "golang.org/x/crypto/blake2b"
)
const (
maxResched = 10 // above 20 starts to show outlines in 'tuxtest' ... so 10 max
)
type Cipher struct {
resched int // lower (1) == stronger encryption; weakest (10) == weakest
rounds int
prng *mtwist.MT19937_64 // used to gen initial hash fodder from key
h []hash.Hash
hs []byte
r io.Reader
w io.Writer
idx int
ctr int
rekeyCtr int // must be min of len( c.h[] )
bTmp byte
k []byte
}
func New(r io.Reader, w io.Writer, resched int, key []byte) (c *Cipher) {
if resched < 1 || resched > maxResched {
resched = 4
}
c = &Cipher{}
c.resched = resched
c.rounds = 1
c.prng = mtwist.New()
c.r = r
c.w = w
if len(key) == 0 {
c.k = []byte(fmt.Sprintf("%s", time.Now()))
} else {
c.k = key
}
c.prng.SeedFullState(c.k)
// Discard first 64 bytes of MT output
for idx := 0; idx < 64; idx++ {
_ = c.prng.Int63()
}
// Init all the hash algs we're going to 'hop' around with initial keystream
c.h = make([]hash.Hash, 2)
c.h[0] = sha512.New()
c.h[1], _ = b2b.New512(c.k)
c.keyUpdate(c.k)
c.rekeyCtr = len(c.hs) * c.resched // lower multiplier == greater security, lower speed
//fmt.Fprintf(os.Stderr, "rekeyCtr = %v\n", c.rekeyCtr)
return c
}
func (c *Cipher) Read(p []byte) (n int, err error) {
n, err = c.r.Read(p)
if err == nil {
for idx := 0; idx < n; idx++ {
p[idx] = c.yield(p[idx])
}
}
return n, err
}
func (c *Cipher) Write(p []byte) (n int, err error) {
n, err = c.w.Write(p)
return n, err
}
// Mutate the session key (intended to be called as encryption proceeds)
func (c *Cipher) keyUpdate(data []byte) {
//fmt.Fprintln(os.Stderr, "--rekey--")
{
c.h[0].Write(data)
sliceTmp := sha512.Sum512(data)
c.hs = sliceTmp[:]
}
{
c.h[1].Write(data)
sliceTmp := b2b.Sum512(data)
c.hs = append(c.hs, sliceTmp[:]...)
}
}
func (c *Cipher) yield(ib byte) (ob byte) {
c.idx = (c.ctr + c.idx + int(c.bTmp)) % len(c.hs)
c.bTmp = c.hs[c.idx]
c.ctr = c.ctr + 1
//fmt.Fprintf(os.Stderr, "[c.hidx:%v c.idx:%v]\n", c.hidx, c.idx)
// NOTE: using a non-prime modulus degrades CV % from ~ 0.055 to ~ 0.07
switch c.ctr % 3 {
case 0:
ob = c.bTmp ^ ib ^ byte(c.ctr) ^ byte(c.idx) ^
c.hs[len(c.hs)-19] ^ c.hs[len(c.hs)-2] ^ c.hs[len(c.hs)-3] ^ c.hs[len(c.hs)-5] ^
c.hs[len(c.hs)-7] ^ c.hs[len(c.hs)-11] ^ c.hs[len(c.hs)-13] ^ c.hs[len(c.hs)-17]
case 1:
ob = c.bTmp ^ ib ^ byte(c.ctr) ^ byte(c.idx) ^
c.hs[len(c.hs)-5] ^ c.hs[len(c.hs)-7] ^ c.hs[len(c.hs)-11] ^ c.hs[len(c.hs)-13] ^
c.hs[len(c.hs)-17] ^ c.hs[len(c.hs)-19] ^ c.hs[len(c.hs)-23] ^ c.hs[len(c.hs)-29]
case 2:
ob = c.bTmp ^ ib ^ byte(c.ctr) ^ byte(c.idx) ^
c.hs[len(c.hs)-13] ^ c.hs[len(c.hs)-17] ^ c.hs[len(c.hs)-23] ^ c.hs[len(c.hs)-27] ^
c.hs[len(c.hs)-29] ^ c.hs[len(c.hs)-31] ^ c.hs[len(c.hs)-2] ^ c.hs[len(c.hs)-3]
}
if c.ctr%c.rekeyCtr == 0 {
bufTmp := make([]byte, 32)
_, _ = c.prng.Read(bufTmp)
c.keyUpdate(bufTmp)
}
return
}
// XORKeyStream XORs each byte in the given slice with a byte from the
// cipher's key stream. Dst and src must overlap entirely or not at all.
//
// If len(dst) < len(src), XORKeyStream should panic. It is acceptable
// to pass a dst bigger than src, and in that case, XORKeyStream will
// only update dst[:len(src)] and will not touch the rest of dst.
//
// Multiple calls to XORKeyStream behave as if the concatenation of
// the src buffers was passed in a single run. That is, Stream
// maintains state and does not reset at each XORKeyStream call.
func (c *Cipher) XORKeyStream(dst, src []byte) {
//fmt.Printf("len dst:%d len src:%d\n", len(dst), len(src))
if len(dst) < len(src) {
panic(errors.New("len(dst) < len(src)"))
}
for idx, v := range src {
dst[idx] = c.yield(v)
}
}