mirror of https://gogs.blitter.com/RLabs/xs
167 lines
4.3 KiB
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)
|
||
|
}
|
||
|
}
|