From 42b54e4394977c34c8cf738a1d19f9b37a6a8c5e Mon Sep 17 00:00:00 2001 From: Sina Ghaderi Date: Sun, 23 Jan 2022 07:17:19 +0330 Subject: add source code --- go.mod | 3 ++ io_test.go | 105 ++++++++++++++++++++++++++++++++++++++++++++ iowrp.go | 26 +++++++++++ rabbit.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 go.mod create mode 100644 io_test.go create mode 100644 iowrp.go create mode 100644 rabbit.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9c472c0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module snix.ir/rabbitio + +go 1.17 diff --git a/io_test.go b/io_test.go new file mode 100644 index 0000000..582dd1e --- /dev/null +++ b/io_test.go @@ -0,0 +1,105 @@ +package rabbitio_test + +import ( + "bytes" + "encoding/hex" + "io" + "strings" + "testing" + + "snix.ir/rabbitio" +) + +func TestNewWriterCipher(t *testing.T) { + key, ivt := []byte("12345678abcdefgh"), []byte("1234qwer") + txt := "dummy text to test NewWriterCipher" + twr := strings.NewReader(txt) + + iw := new(bytes.Buffer) + + t.Logf("encrypting plain text ---") + cw, err := rabbitio.NewWriterCipher(key, ivt, iw) + if err != nil { + t.Fatal(err) + } + + if _, err := io.Copy(cw, twr); err != nil { + t.Fatal(err) + } + + t.Logf("cipher-text: %v", hex.EncodeToString(iw.Bytes())) + t.Logf("decrypting cipher text ---") + + ir := new(bytes.Buffer) + cr, err := rabbitio.NewWriterCipher(key, ivt, ir) + if err != nil { + t.Fatal(err) + } + + if _, err := io.Copy(cr, iw); err != nil { + t.Fatal(err) + } + + if ir.String() != txt { + t.Error("error: ir.String() is not equal to txt") + } +} + +func TestNewReaderCipher(t *testing.T) { + key, ivt := []byte("12345678abcdefgh"), []byte("1234qwer") + txt := "test NewReadercipher text dummy tx" + twr := strings.NewReader(txt) + + iw := new(bytes.Buffer) + + t.Logf("encrypting plain text ---") + cw, err := rabbitio.NewReaderCipher(key, ivt, twr) + if err != nil { + t.Fatal(err) + } + + if _, err := io.Copy(iw, cw); err != nil { + t.Fatal(err) + } + + t.Logf("cipher-text: %v", hex.EncodeToString(iw.Bytes())) + t.Logf("decrypting cipher text ---") + + ir := new(bytes.Buffer) + cr, err := rabbitio.NewReaderCipher(key, ivt, iw) + if err != nil { + t.Fatal(err) + } + + if _, err := io.Copy(ir, cr); err != nil { + t.Fatal(err) + } + + if ir.String() != txt { + t.Error("error: ir.String() is not equal to txt") + } +} + +func TestNewCipher(t *testing.T) { + key, ivt := []byte("12345678abcdefgh"), []byte("1234qwer") + txt := "test NewReadercipher text dummy tx" + cph, err := rabbitio.NewCipher(key, ivt) + if err != nil { + t.Fatal(err) + } + dst := make([]byte, len(txt)) + cph.XORKeyStream(dst, []byte(txt)) + t.Logf("cipher-text: %v", hex.EncodeToString(dst)) + + cph, err = rabbitio.NewCipher(key, ivt) + if err != nil { + t.Fatal(err) + } + + nds := make([]byte, len(dst)) + cph.XORKeyStream(nds, dst) + if string(nds) != txt { + t.Error("error: nds is not equal to txt") + } + +} diff --git a/iowrp.go b/iowrp.go new file mode 100644 index 0000000..212d658 --- /dev/null +++ b/iowrp.go @@ -0,0 +1,26 @@ +package rabbitio + +import ( + "crypto/cipher" + "io" +) + +// NewWriterCipher warp a rabbit cipher stream with an io.Writer, returned StreamWriter +// interface witch can be used to encrypt or decrypting data +func NewWriterCipher(key []byte, iv []byte, wr io.Writer) (*cipher.StreamWriter, error) { + stream, err := NewCipher(key, iv) + if err != nil { + return nil, err + } + return &cipher.StreamWriter{S: stream, W: wr}, err +} + +// NewWriterCipher warp a rabbit cipher stream with an io.Reader, returned StreamReader +// interface witch can be used to encrypt or decrypting data +func NewReaderCipher(key []byte, iv []byte, re io.Reader) (*cipher.StreamReader, error) { + stream, err := NewCipher(key, iv) + if err != nil { + return nil, err + } + return &cipher.StreamReader{S: stream, R: re}, err +} diff --git a/rabbit.go b/rabbit.go new file mode 100644 index 0000000..d70d835 --- /dev/null +++ b/rabbit.go @@ -0,0 +1,144 @@ +package rabbitio + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + "math/bits" +) + +const ( + invalidKeyLen = "rabbitio: key must be 16 byte len, not more not less" + invalidIVXLen = "rabbitio: iv must be 8 byte len or nothing (zero) at all" +) + +var aro = []uint32{ + 0x4D34D34D, 0xD34D34D3, 0x34D34D34, 0x4D34D34D, 0xD34D34D3, 0x34D34D34, 0x4D34D34D, 0xD34D34D3, +} + +type rabbitCipher struct { + xbit [8]uint32 + cbit [8]uint32 + ks []byte + carry uint32 + sbit [16]byte +} + +// NewCipher returns a chpher.Stream interface that implemented an XORKeyStream method +// according to RFC 4503, key must be 16 byte len, iv on the other hand is optional but +// must be either zero len or 8 byte len, error will be returned on wrong key/iv len +func NewCipher(key []byte, iv []byte) (cipher.Stream, error) { + if len(key) != 0x10 { + return nil, errors.New(invalidKeyLen) + } + if len(iv) != 0x00 && len(iv) != 0x08 { + return nil, errors.New(invalidIVXLen) + } + var k [0x04]uint32 + for i := range k { + k[i] = binary.LittleEndian.Uint32(key[i*0x04:]) + } + var r rabbitCipher + r.setupKey(k[:]) + if len(iv) != 0x00 { + var v [0x04]uint16 + for i := range v { + v[i] = binary.LittleEndian.Uint16(iv[i*0x02:]) + } + r.setupIV(v[:]) + } + return &r, nil +} + +func (r *rabbitCipher) setupKey(key []uint32) { + r.xbit[0] = key[0] + r.xbit[1] = key[3]<<16 | key[2]>>16 + r.xbit[2] = key[1] + r.xbit[3] = key[0]<<16 | key[3]>>16 + r.xbit[4] = key[2] + r.xbit[5] = key[1]<<16 | key[0]>>16 + r.xbit[6] = key[3] + r.xbit[7] = key[2]<<16 | key[1]>>16 + r.cbit[0] = bits.RotateLeft32(key[2], 0x10) + r.cbit[1] = key[0]&0xffff0000 | key[1]&0xffff + r.cbit[2] = bits.RotateLeft32(key[3], 0x10) + r.cbit[3] = key[1]&0xffff0000 | key[2]&0xffff + r.cbit[4] = bits.RotateLeft32(key[0], 0x10) + r.cbit[5] = key[2]&0xffff0000 | key[3]&0xffff + r.cbit[6] = bits.RotateLeft32(key[1], 0x10) + r.cbit[7] = key[3]&0xffff0000 | key[0]&0xffff + for i := 0; i < 4; i++ { + r.nextState() + } + r.cbit[0] ^= r.xbit[4] + r.cbit[1] ^= r.xbit[5] + r.cbit[2] ^= r.xbit[6] + r.cbit[3] ^= r.xbit[7] + r.cbit[4] ^= r.xbit[0] + r.cbit[5] ^= r.xbit[1] + r.cbit[6] ^= r.xbit[2] + r.cbit[7] ^= r.xbit[3] +} + +func (r *rabbitCipher) setupIV(iv []uint16) { + r.cbit[0] ^= uint32(iv[1])<<0x10 | uint32(iv[0]) + r.cbit[1] ^= uint32(iv[3])<<0x10 | uint32(iv[1]) + r.cbit[2] ^= uint32(iv[3])<<0x10 | uint32(iv[2]) + r.cbit[3] ^= uint32(iv[2])<<0x10 | uint32(iv[0]) + r.cbit[4] ^= uint32(iv[1])<<0x10 | uint32(iv[0]) + r.cbit[5] ^= uint32(iv[3])<<0x10 | uint32(iv[1]) + r.cbit[6] ^= uint32(iv[3])<<0x10 | uint32(iv[2]) + r.cbit[7] ^= uint32(iv[2])<<0x10 | uint32(iv[0]) + for i := 0; i < 4; i++ { + r.nextState() + } +} + +func (r *rabbitCipher) nextState() { + var GRX [0x08]uint32 + for i := range r.cbit { + r.carry, r.cbit[i] = bits.Sub32(aro[i], r.cbit[i], r.carry) + } + for i := range GRX { + GRX[i] = gfunction(r.xbit[i], r.cbit[i]) + } + r.xbit[0x00] = GRX[0] + bits.RotateLeft32(GRX[7], 0x10) + bits.RotateLeft32(GRX[6], 0x10) + r.xbit[0x01] = GRX[1] + bits.RotateLeft32(GRX[0], 0x08) + GRX[7] + r.xbit[0x02] = GRX[2] + bits.RotateLeft32(GRX[1], 0x10) + bits.RotateLeft32(GRX[0], 0x10) + r.xbit[0x03] = GRX[3] + bits.RotateLeft32(GRX[2], 0x08) + GRX[1] + r.xbit[0x04] = GRX[4] + bits.RotateLeft32(GRX[3], 0x10) + bits.RotateLeft32(GRX[2], 0x10) + r.xbit[0x05] = GRX[5] + bits.RotateLeft32(GRX[4], 0x08) + GRX[3] + r.xbit[0x06] = GRX[6] + bits.RotateLeft32(GRX[5], 0x10) + bits.RotateLeft32(GRX[4], 0x10) + r.xbit[0x07] = GRX[7] + bits.RotateLeft32(GRX[6], 0x08) + GRX[5] +} + +func (r *rabbitCipher) extract() { + var sw [0x04]uint32 + r.nextState() + sw[0] = r.xbit[0] ^ (r.xbit[5]>>0x10 | r.xbit[3]<<0x10) + sw[1] = r.xbit[2] ^ (r.xbit[7]>>0x10 | r.xbit[5]<<0x10) + sw[2] = r.xbit[4] ^ (r.xbit[1]>>0x10 | r.xbit[7]<<0x10) + sw[3] = r.xbit[6] ^ (r.xbit[3]>>0x10 | r.xbit[1]<<0x10) + for i := range sw { + binary.LittleEndian.PutUint32(r.sbit[i*0x04:], sw[i]) + } + r.ks = r.sbit[:] +} + +// XORKeyStream read from src and perform xor on every elemnt of src and +// write result on dst +func (r *rabbitCipher) XORKeyStream(dst, src []byte) { + for i := range src { + if len(r.ks) == 0x00 { + r.extract() + } + dst[i] = src[i] ^ r.ks[0x00] + r.ks = r.ks[0x01:] + } +} + +func gfunction(u, v uint32) uint32 { + uv := uint64(u + v) + uv *= uv + return uint32(uv>>0x20) ^ uint32(uv) +} -- cgit v1.2.3