package rabaead import ( "bytes" "crypto/cipher" "encoding/binary" "io" "snix.ir/rabbitio" ) const cmrk = 0x08 // chunk size indicator, // without this reader cannot calculate actual size of plaintext // additional data func, return value is used as AD in Seal and Open // nil AdFunc is harmless and equal to func()[]byte{return nil} type AdditionalFunc func() []byte type chunkReader struct { aead cipher.AEAD csize int rader io.Reader buff []byte nonce []byte adexe AdditionalFunc } type chunkWriter struct { aead cipher.AEAD csize int writer io.Writer buff []byte nonce []byte adexe AdditionalFunc } // NewChunkReader returns a chunkReader data type, this reader reads and open() aead // ciphertext, each chunk has its own tag and cmrk value. // this reader has a chunk size in-memory buffer, large chunk size can make application to runs // out of memory, thus is most suitable for sliced data, like network data transmit and so.. func NewChunkReader(r io.Reader, chnk int, a cipher.AEAD, nonce []byte, f AdditionalFunc) (*chunkReader, error) { if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 { return nil, rabbitio.ErrInvalidIVX } s := &chunkReader{ aead: a, buff: []byte{}, nonce: make([]byte, len(nonce)), csize: chnk, rader: r, adexe: f, } if s.adexe == nil { s.adexe = func() []byte { return nil } } copy(s.nonce, nonce) return s, nil } // NewChunkWriter returns a chunkWriter data type, this writer sale() and write aead // plaintext, each chunk has its own tag and cmrk value. // this writer has a chunk size in-memory buffer, large chunk size can make application to // runs out of memory, thus is most suitable for sliced data, like network data transmit and so.. func NewChunkWriter(w io.Writer, chnk int, a cipher.AEAD, nonce []byte, f AdditionalFunc) (*chunkWriter, error) { if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 { return nil, rabbitio.ErrInvalidIVX } s := &chunkWriter{ aead: a, buff: []byte{}, nonce: make([]byte, len(nonce)), csize: chnk, writer: w, adexe: f, } if s.adexe == nil { s.adexe = func() []byte { return nil } } copy(s.nonce, nonce) return s, nil } // Close method, if there is any func (w *chunkWriter) Close() error { if c, ok := w.writer.(io.Closer); ok { return c.Close() } return nil } // Write writes plaintext chunk into the sale() and underlying writer // write would not report overhead data (chunk size marker and poly1305 tag) in // written return value. for each chunk there is 8+16 byte overhead data. // AdFunc will be triggered for each chunk of data func (w *chunkWriter) Write(b []byte) (n int, err error) { w.buff = b for len(w.buff) > 0 { s, err := w.write() if err != nil { return n, err } n += s } return } func (w *chunkWriter) write() (int, error) { size := cmrk + w.csize + w.aead.Overhead() chnk := make([]byte, size) var n int var err error if len(w.buff) > 0 { s := copy(chnk[cmrk:len(chnk)-w.aead.Overhead()], w.buff) w.buff = w.buff[s:] copy(chnk[0:cmrk], uint64Little(uint64(s))) w.aead.Seal(chnk[:0], w.nonce, chnk[:cmrk+w.csize], w.adexe()) _, err = w.writer.Write(chnk) if err != nil { return n, err } n += s } return n, err } // Read reads and open() ciphertext chunk from underlying reader // read would not report overhead data (chunk size marker and poly1305 tag) in its // return value. if the read data from underlying reader is corrupted, ErrAuthMsg // error will be returned. for each chunk there is 8+16 byte overhead data. // AdFunc will be triggered for each chunk of data func (r *chunkReader) Read(b []byte) (int, error) { if len(b) <= r.csize { return r.readTo(b) } n := 0 for { if n+r.csize > len(b) { sr, err := r.readTo(b[n:]) n += sr if err != nil { return n, err } break } sr, err := r.readTo(b[n : n+r.csize]) n += sr if err != nil { return n, err } } return n, nil } func (r *chunkReader) readTo(b []byte) (int, error) { var n int if len(r.buff) > 0 { n = copy(b, r.buff) r.buff = r.buff[n:] return n, nil } sr, err := r.read() n = copy(b, r.buff[:sr]) r.buff = r.buff[n:] return n, err } func (r *chunkReader) read() (int, error) { var n int size := cmrk + r.csize + r.aead.Overhead() chnk := make([]byte, size) chLE := uint64Little(uint64(r.csize)) si, err := io.ReadFull(r.rader, chnk) if err != nil { return n, err } if si > 0 { _, err = r.aead.Open(chnk[:0], r.nonce, chnk, r.adexe()) if err != nil { return n, err } if bytes.Equal(chnk[0:cmrk], chLE) { n += r.csize r.buff = append(r.buff, chnk[cmrk:cmrk+r.csize]...) } else { f := binary.LittleEndian.Uint64(chnk[0:cmrk]) n += int(f) r.buff = append(r.buff, chnk[cmrk:cmrk+f]...) } } return n, err } func uint64Little(n uint64) []byte { b := make([]byte, cmrk) binary.LittleEndian.PutUint64(b, n) return b }
package rabaead import ( "crypto/cipher" "errors" "snix.ir/poly1305" "snix.ir/rabbitio" ) const polykeylen = 0x20 // poly1305 key len: 32byte var ErrAuthMsg = errors.New("rabaead: message authentication failed") var erroverlap = errors.New("rabaead: invalid buffer memory overlap") type rabbitPoly1305 struct { key []byte // rabbit cipher key noncesize int // rabbit iv size } // NewAEAD returns a rabbit aead data-type // key must be 16 byte len func NewAEAD(key []byte) (cipher.AEAD, error) { return newRabbitAead(key) } func newRabbitAead(key []byte) (cipher.AEAD, error) { if len(key) != rabbitio.KeyLen { return nil, rabbitio.ErrInvalidKey } rabbitAead := &rabbitPoly1305{ noncesize: rabbitio.IVXLen, key: make([]byte, rabbitio.KeyLen), } copy(rabbitAead.key[:], key) return rabbitAead, nil } // Overhead returns poly1305 tag size: 16byte func (c *rabbitPoly1305) Overhead() int { return poly1305.TagSize } // NonceSize returns rabbit iv len: 8byte func (c *rabbitPoly1305) NonceSize() int { return c.noncesize } func (c *rabbitPoly1305) sealRabbit(dst, nonce, plaintext, ad []byte) []byte { ret, out := headtail(dst, len(plaintext)+poly1305.TagSize) ciphertext, tag := out[:len(plaintext)], out[len(plaintext):] if inexactOverlap(out, plaintext) { panic(erroverlap) //should never happen } var polyKey [polykeylen]byte s, err := rabbitio.NewCipher(c.key, nonce) if err != nil { panic(err) } s.XORKeyStream(polyKey[:], polyKey[:]) p := poly1305.New(&polyKey) writePadding(p, ad) s, err = rabbitio.NewCipher(c.key, nonce) if err != nil { panic(err) } s.XORKeyStream(ciphertext, plaintext) writePadding(p, ciphertext) writeUint64(p, len(ad)) writeUint64(p, len(plaintext)) p.Sum(tag[:0x00]) return ret } func (c *rabbitPoly1305) openRabbit(dst, nonce, ciphertext, ad []byte) ([]byte, error) { tag := ciphertext[len(ciphertext)-poly1305.TagSize:] ciphertext = ciphertext[:len(ciphertext)-poly1305.TagSize] var polyKey [polykeylen]byte s, err := rabbitio.NewCipher(c.key, nonce) if err != nil { panic(err) } s.XORKeyStream(polyKey[:], polyKey[:]) p := poly1305.New(&polyKey) writePadding(p, ad) writePadding(p, ciphertext) writeUint64(p, len(ad)) writeUint64(p, len(ciphertext)) ret, out := headtail(dst, len(ciphertext)) if inexactOverlap(out, ciphertext) { panic(erroverlap) //should never happen } // check data integrity if !p.Verify(tag) { return nil, ErrAuthMsg } s, err = rabbitio.NewCipher(c.key, nonce) if err != nil { panic(err) } s.XORKeyStream(out, ciphertext) return ret, nil } // Open opens a rabbit aead ciphertext. // panic occurs if nonce len is not equal to IVXLen (8byte) or zero // if data is not verified, ErrAuthMsg will be returned func (c *rabbitPoly1305) Open(dst, nonce, ciphertext, ad []byte) ([]byte, error) { if len(ciphertext) < poly1305.TagSize { return nil, ErrAuthMsg } return c.openRabbit(dst, nonce, ciphertext, ad) } // Seal seals a plaintext into the rabbit aead ciphertext. // panic occurs if nonce len is not equal to IVXLen (8byte) or zero func (c *rabbitPoly1305) Seal(dst, nonce, plaintext, ad []byte) []byte { return c.sealRabbit(dst, nonce, plaintext, ad) }
//go:build !appengine package rabaead import "unsafe" func anyOverlap(x, y []byte) bool { return len(x) > 0 && len(y) > 0 && uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) && uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1])) } func inexactOverlap(x, y []byte) bool { if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] { return false } return anyOverlap(x, y) }
package rabaead import ( "encoding/binary" "snix.ir/poly1305" ) func headtail(in []byte, n int) (head, tail []byte) { total := len(in) + n if cap(in) >= total { head = in[:total] } else { head = make([]byte, total) copy(head, in) } tail = head[len(in):] return } func writePadding(p *poly1305.MAC, b []byte) { p.Write(b) if rem := len(b) % 16; rem != 0 { var buf [16]byte padLen := 16 - rem p.Write(buf[:padLen]) } } func writeUint64(p *poly1305.MAC, n int) { var buf [8]byte binary.LittleEndian.PutUint64(buf[:], uint64(n)) p.Write(buf[:]) }
package rabaead import ( "crypto/cipher" "errors" "io" "snix.ir/poly1305" "snix.ir/rabbitio" ) type streamReader struct { ie *ioaead cip cipher.Stream firstRead bool nwr int read io.Reader buff []byte temp []byte tagc []byte } type ioaead struct { key []byte // rabbit cipher key nonce []byte poly *poly1305.MAC adlen int additionalData AdditionalFunc } type streamWriter struct { ie *ioaead writer io.Writer plainWriter io.Writer nwr int firstWrite bool } var errunderio = errors.New("underlying io reader returns wrong read value, which is not supposed to happen") func makeioaead(key, iv []byte, adfunc AdditionalFunc) *ioaead { if adfunc == nil { adfunc = func() []byte { return nil } } str := &ioaead{ key: make([]byte, rabbitio.KeyLen), nonce: make([]byte, len(iv)), } str.additionalData = adfunc copy(str.key, key) copy(str.nonce, iv) var poly [polykeylen]byte cph, _ := rabbitio.NewCipher(str.key, str.nonce) cph.XORKeyStream(poly[:], poly[:]) str.poly = poly1305.New(&poly) return str } func (s *ioaead) execAdFunc() { additionalData := s.additionalData() s.adlen = len(additionalData) writePadding(s.poly, additionalData) } func newCipherReader(r io.Reader, key, nonce []byte, f AdditionalFunc) (*streamReader, error) { if len(key) != rabbitio.KeyLen { return nil, rabbitio.ErrInvalidKey } if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 { return nil, rabbitio.ErrInvalidIVX } v := &streamReader{ ie: makeioaead(key, nonce, f), read: r, buff: []byte{}, tagc: make([]byte, 16), temp: make([]byte, 16), } v.cip, _ = rabbitio.NewCipher(v.ie.key, v.ie.nonce) return v, nil } // NewStreamReader returns streamReader data type, this reader open() and read aead // ciphertext which have 16-byte poly1305 tag overhead. // read data cannot be authenticated until underlying reader returns EOF // so you should use this reader only if you can undo your read. // AdFunc will be triggered at first call to read method func NewStreamReader(r io.Reader, key, nonce []byte, f AdditionalFunc) (*streamReader, error) { return newCipherReader(r, key, nonce, f) } // NewStreamWriter returns streamWriter data type, this writer sale() and write aead // plaintext which have 16-byte poly1305 tag overhead, running Close() is necessary // in order to calculate and write tag at the end of the write. // AdFunc will be triggered at first call to write method func NewStreamWriter(w io.Writer, key, nonce []byte, f AdditionalFunc) (*streamWriter, error) { return newChipherWriter(w, key, nonce, f) } func newChipherWriter(w io.Writer, key, nonce []byte, f AdditionalFunc) (*streamWriter, error) { if len(key) != rabbitio.KeyLen { return nil, rabbitio.ErrInvalidKey } if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 { return nil, rabbitio.ErrInvalidIVX } v := &streamWriter{ ie: makeioaead(key, nonce, f), plainWriter: w, } v.writer, _ = rabbitio.NewWriterCipher( v.ie.key, v.ie.nonce, io.MultiWriter(w, v.ie.poly), ) return v, nil } func (r *streamReader) readTo(b []byte) (int, error) { var n int if len(r.buff) > 0 { return r.copyBuff(b), nil } sr, err := r.readBuff() if err != nil { if err == io.EOF { n = r.copyUntil(b, sr) return n, r.verify() } return n, err } return r.copyUntil(b, sr), err } // Read reads and open ciphertext. // read data is unreliable until underlying reader returns EOF // after that Read return EOF or ErrAuthMsg if integrity of data has been compromised. // in such a case, you need to unread data. a simple demonstration would be to delete // or truncate the file if ErrAuthMsg is returned func (r *streamReader) Read(b []byte) (int, error) { if len(b) <= 16 { return r.readTo(b) } n := 0 for { if n+16 > len(b) { sr, err := r.readTo(b[n:]) n += sr if err != nil { return n, err } break } sr, err := r.readTo(b[n : n+16]) n += sr if err != nil { return n, err } } return n, nil } func (r *streamReader) verify() error { r.ie.ioPaddingTo(r.nwr) if r.ie.poly.Verify(r.tagc) { return io.EOF } return ErrAuthMsg } func (r *streamReader) copyUntil(b []byte, sr int) int { n := copy(b, r.buff[:sr]) r.buff = r.buff[n:] r.nwr += n return n } func (r *streamReader) copyBuff(b []byte) int { n := copy(b, r.buff) r.buff = r.buff[n:] r.nwr += n return n } func (r *streamReader) readBuff() (int, error) { if !r.firstRead { r.ie.execAdFunc() _, err := io.ReadFull(r.read, r.temp) if err != nil { return 0, err } r.firstRead = true } var buff = make([]byte, 16) n, err := r.read.Read(buff) if err != nil { return 0, err } if n > len(buff) { return 0, errunderio } copy(r.tagc, append(r.temp[n:], buff[:n]...)) r.buff = append(r.buff, r.temp[:n]...) r.buffAndXor() if n < 16 { return n, err } copy(r.temp, buff) return n, err } func (r *streamReader) buffAndXor() { r.ie.poly.Write(r.buff) r.cip.XORKeyStream(r.buff, r.buff) } // Write writes plaintext data, in order to calculate and write tag // at the end of the write, running Close() is necessary func (w *streamWriter) Write(b []byte) (int, error) { if !w.firstWrite { w.ie.execAdFunc() w.firstWrite = true } n, err := w.writer.Write(b) if err != nil { return n, err } w.nwr += n return n, err } func (p *ioaead) ioPaddingTo(nb int) { if rem := nb % 16; rem != 0 { var buf [16]byte padLen := 16 - rem p.poly.Write(buf[:padLen]) } writeUint64(p.poly, p.adlen) writeUint64(p.poly, nb) } // Close calculate and write poly1305 tag before closing the writer // if underlying writer does not have a Close() method, Close only // calculate and write poly1305 tag func (w *streamWriter) Close() error { w.ie.ioPaddingTo(w.nwr) if _, err := w.plainWriter.Write(w.ie.poly.Sum(nil)); err != nil { return err } if c, ok := w.plainWriter.(io.Closer); ok { return c.Close() } return nil }