mirror of https://gogs.blitter.com/RLabs/xs
				
				
				
			
		
			
				
	
	
		
			187 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			5.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"
 | |
| 	groestl "blitter.com/go/groestl"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	maxResched = 99 // 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, 3)
 | |
| 	c.h[0] = sha512.New()
 | |
| 	c.h[1], _ = b2b.New512(c.k)
 | |
| 	c.h[2] = groestl.New512()
 | |
| 	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[:]...)
 | |
| 	}
 | |
| 	{
 | |
| 		c.h[2].Write(data)
 | |
| 		sliceTmp := groestl.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 % 5 {
 | |
| 	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] ^
 | |
| 			c.hs[len(c.hs)-47] ^ c.hs[len(c.hs)-43] ^ c.hs[len(c.hs)-41] ^ c.hs[len(c.hs)-39]
 | |
| 
 | |
| 	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] ^
 | |
| 			c.hs[len(c.hs)-43] ^ c.hs[len(c.hs)-41] ^ c.hs[len(c.hs)-39] ^ c.hs[len(c.hs)-37]
 | |
| 
 | |
| 	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] ^
 | |
| 			c.hs[len(c.hs)-37] ^ c.hs[len(c.hs)-41] ^ c.hs[len(c.hs)-39] ^ c.hs[len(c.hs)-47]
 | |
| 	case 3:
 | |
| 		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)-5] ^ c.hs[len(c.hs)-3] ^
 | |
| 			c.hs[len(c.hs)-43] ^ c.hs[len(c.hs)-41] ^ c.hs[len(c.hs)-39] ^ c.hs[len(c.hs)-37]
 | |
| 	case 4:
 | |
| 		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)-7] ^ c.hs[len(c.hs)-3] ^
 | |
| 			c.hs[len(c.hs)-33] ^ c.hs[len(c.hs)-41] ^ c.hs[len(c.hs)-45] ^ c.hs[len(c.hs)-43]
 | |
| 	}
 | |
| 
 | |
| 	if c.ctr%c.rekeyCtr == 0 {
 | |
| 		bufTmp := make([]byte, 16*3)
 | |
| 		_, _ = 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)
 | |
| 	}
 | |
| }
 |