// 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) } }