pax_global_header00006660000000000000000000000064143337462310014520gustar00rootroot0000000000000052 comment=52aad68c5c918fc113408dee9b3d7848b027fe2d golang-github-secure-io-sio-go-0.3.1/000077500000000000000000000000001433374623100173345ustar00rootroot00000000000000golang-github-secure-io-sio-go-0.3.1/.cirrus.yml000066400000000000000000000010111433374623100214350ustar00rootroot00000000000000task: matrix: - name: golang-ci container: image: golang:latest modules_cache: fingerprint_script: cat go.sum folder: $GOPATH/pkg/mod check_script: - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.20.0 - $(go env GOPATH)/bin/golangci-lint run --config ./.golangci.yml build_script: - go build -tags gofuzz ./... test_script: - go test -tags gofuzz ./... golang-github-secure-io-sio-go-0.3.1/.github/000077500000000000000000000000001433374623100206745ustar00rootroot00000000000000golang-github-secure-io-sio-go-0.3.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001433374623100230575ustar00rootroot00000000000000golang-github-secure-io-sio-go-0.3.1/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000010501433374623100254630ustar00rootroot00000000000000--- name: Bug report about: Please use this template if you want to report a bug. title: '' labels: '' assignees: '' --- #### Bug describtion #### Expected behavior #### Additional context 1. What version of Go are you using (`go version`)? 2. What operating system and processor architecture are you using (`go env`)? 3. Anything else that is important? golang-github-secure-io-sio-go-0.3.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000007571433374623100266150ustar00rootroot00000000000000--- name: Feature request about: Please use this template if you want to suggest an idea. title: '' labels: feature-request assignees: '' --- #### What is the problem you want to solve? #### How do you want to solve it? #### Additional context 1. Are there alternative solutions? 2. Would your solution cause a major breaking API change? 3. Anything else that is important? golang-github-secure-io-sio-go-0.3.1/.github/pull_request_template.md000066400000000000000000000011271433374623100256360ustar00rootroot00000000000000 #### What does the PR do? #### What problem does it solve? golang-github-secure-io-sio-go-0.3.1/.github/workflows/000077500000000000000000000000001433374623100227315ustar00rootroot00000000000000golang-github-secure-io-sio-go-0.3.1/.github/workflows/go.yml000066400000000000000000000013051433374623100240600ustar00rootroot00000000000000name: Go on: pull_request: branches: - master push: branches: - master jobs: build: name: Build Go ${{ matrix.go-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: go-version: [1.14.x] os: [ubuntu-latest, windows-latest, macos-latest] steps: - name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }} uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v1 - name: Build on ${{ matrix.os }} env: GO111MODULE: on run: | go test -tags gofuzz ./... golang-github-secure-io-sio-go-0.3.1/.gitignore000066400000000000000000000003001433374623100213150ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out golang-github-secure-io-sio-go-0.3.1/.golangci.yml000066400000000000000000000010051433374623100217140ustar00rootroot00000000000000linters-settings: golint: min-confidence: 0 misspell: locale: US linters: disable-all: true enable: - typecheck - goimports - misspell - govet - golint - ineffassign - gosimple - deadcode - unparam - unused - structcheck issues: exclude-use-default: false exclude: - should have a package comment - don't use ALL_CAPS in Go names service: golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly golang-github-secure-io-sio-go-0.3.1/LICENSE000066400000000000000000000020511433374623100203370ustar00rootroot00000000000000MIT License Copyright (c) 2019 SecureIO Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-secure-io-sio-go-0.3.1/README.md000066400000000000000000000037321433374623100206200ustar00rootroot00000000000000[![Godoc Reference](https://godoc.org/github.com/secure-io/sio-go?status.svg)](https://godoc.org/github.com/secure-io/sio-go) [![Build Status](https://api.cirrus-ci.com/github/secure-io/sio-go.svg?branch=master)](https://cirrus-ci.com/github/secure-io/sio-go) # Secure IO The `sio` package implements provable secure authenticated encryption for continuous byte streams. It splits a data stream into `L` bytes long fragments and en/decrypts each fragment with an unique key-nonce combination using an [AEAD](https://golang.org/pkg/crypto/cipher/#AEAD). For the last fragment the construction prefixes the associated data with the `0x80` byte (instead of `0x00`) to prevent truncation attacks. ![`sio` encryption scheme](https://github.com/secure-io/sio/blob/master/img/channel_construction.svg) The `sio` package follows semantic versioning and hasn't reached a stable v1.0.0, yet. So newer versions may cause major breaking API changes. However, we try to avoid such changes - if not really needed. ### How to use `sio`? ``` import ( "github.com/secure-io/sio-go" ) ``` The `sio` package provides APIs for en/decrypting an [`io.Reader`](https://golang.org/pkg/io#Reader) or an [`io.Writer`](https://golang.org/pkg/io/#Writer). First, you have to create a [`Stream`](https://godoc.org/github.com/secure-io/sio#Stream) instance from a [`cipher.AEAD`](https://golang.org/pkg/crypto/cipher/#AEAD) and a buffer size. (The buffer size determines the fragment size `L`). You may want to take a look at [this example](https://godoc.org/github.com/secure-io/sio-go#example-NewStream--AESGCM). Then you can use the `Stream` to encrypt resp. decrypt an `io.Reader` or `io.Writer` using e.g. the [`EncryptReader`](https://godoc.org/github.com/secure-io/sio-go#Stream.EncryptReader) or [`DecryptWriter`](https://godoc.org/github.com/secure-io/sio-go#Stream.DecryptWriter) methods. For a comprehensive overview of the API please take a look at [godoc.org](https://godoc.org/github.com/secure-io/sio-go). golang-github-secure-io-sio-go-0.3.1/benchmark_test.go000066400000000000000000000206141433374623100226570ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( "bytes" "io" "testing" ) func BenchmarkEncrypt(b *testing.B) { s, err := AES_128_GCM.Stream(make([]byte, 16)) if err != nil { b.Fatalf("Failed to create Stream: %v", err) } b.Run("Write", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchEncryptWrite(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchEncryptWrite(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchEncryptWrite(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchEncryptWrite(b, s, 1024*1024) }) }) b.Run("WriteTo", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchEncryptWriteTo(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchEncryptWriteTo(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchEncryptWriteTo(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchEncryptWriteTo(b, s, 1024*1024) }) }) b.Run("Read", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchEncryptRead(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchEncryptRead(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchEncryptRead(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchEncryptRead(b, s, 1024*1024) }) }) b.Run("ReadFrom", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchEncryptReadFrom(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchEncryptReadFrom(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchEncryptReadFrom(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchEncryptReadFrom(b, s, 1024*1024) }) }) } func BenchmarkDecrypt(b *testing.B) { s, err := AES_128_GCM.Stream(make([]byte, 16)) if err != nil { b.Fatalf("Failed to create Stream: %v", err) } b.Run("Write", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchDecryptWrite(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchDecryptWrite(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchDecryptWrite(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchDecryptWrite(b, s, 1024*1024) }) }) b.Run("WriteTo", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchDecryptWriteTo(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchDecryptWriteTo(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchDecryptWriteTo(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchDecryptWriteTo(b, s, 1024*1024) }) }) b.Run("Read", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchDecryptRead(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchDecryptRead(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchDecryptRead(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchDecryptRead(b, s, 1024*1024) }) }) b.Run("ReadFrom", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchDecryptReadFrom(b, s, 1024) }) b.Run("64K", func(b *testing.B) { benchDecryptReadFrom(b, s, 64*1024) }) b.Run("512K", func(b *testing.B) { benchDecryptReadFrom(b, s, 512*1024) }) b.Run("1M", func(b *testing.B) { benchDecryptReadFrom(b, s, 1024*1024) }) }) b.Run("ReadAt", func(b *testing.B) { b.Run("1K", func(b *testing.B) { benchDecryptReadAt(b, s, 0, 1024) }) b.Run("64K", func(b *testing.B) { benchDecryptReadAt(b, s, 0, 64*1024) }) b.Run("512K", func(b *testing.B) { benchDecryptReadAt(b, s, 0, 512*1024) }) b.Run("1M", func(b *testing.B) { benchDecryptReadAt(b, s, 0, 1024*1024) }) b.Run("512_1K", func(b *testing.B) { benchDecryptReadAt(b, s, 512, 1024) }) b.Run("1K_64K", func(b *testing.B) { benchDecryptReadAt(b, s, 1024, 64*1024) }) b.Run("65K_512K", func(b *testing.B) { benchDecryptReadAt(b, s, 65*1024, 512*1024) }) b.Run("129K_1M", func(b *testing.B) { benchDecryptReadAt(b, s, 129*1024, 1024*1024) }) }) } func benchEncryptWrite(b *testing.B, s *Stream, size int64) { w := s.EncryptWriter(DevNull, make([]byte, s.NonceSize()), nil) plaintext := make([]byte, size) b.SetBytes(size) b.ResetTimer() for i := 0; i < b.N; i++ { w.Write(plaintext) w.Close() w.seqNum = 1 w.offset = 0 w.closed = false w.associatedData[0] = 0 } } func benchDecryptWrite(b *testing.B, s *Stream, size int64) { plaintext := make([]byte, size) nonce := make([]byte, s.NonceSize()) ow := s.DecryptWriter(DevNull, nonce, nil) w := s.EncryptWriter(ow, nonce, nil) b.SetBytes(2*size + s.Overhead(size)) b.ResetTimer() for i := 0; i < b.N; i++ { w.Write(plaintext) w.Close() w.seqNum, ow.seqNum = 1, 1 w.offset, ow.offset = 0, 0 w.closed, ow.closed = false, false w.associatedData[0], ow.associatedData[0] = 0, 0 } } func benchEncryptReadFrom(b *testing.B, s *Stream, size int64) { plaintext := &io.LimitedReader{R: DevNull, N: size} w := s.EncryptWriter(DevNull, make([]byte, s.NonceSize()), nil) b.SetBytes(size) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := w.ReadFrom(plaintext); err != nil { panic(err) } if err := w.Close(); err != nil { panic(err) } w.seqNum = 1 w.offset = 0 w.closed = false w.associatedData[0] = 0 plaintext.N = size } } func benchDecryptReadFrom(b *testing.B, s *Stream, size int64) { plaintext := &io.LimitedReader{R: DevNull, N: size} nonce := make([]byte, s.NonceSize()) ow := s.DecryptWriter(DevNull, nonce, nil) w := s.EncryptWriter(ow, nonce, nil) b.SetBytes(2*size + s.Overhead(size)) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := w.ReadFrom(plaintext); err != nil { panic(err) } if err := w.Close(); err != nil { panic(err) } w.seqNum, ow.seqNum = 1, 1 w.offset, ow.offset = 0, 0 w.closed, ow.closed = false, false w.associatedData[0], ow.associatedData[0] = 0, 0 plaintext.N = size } } func benchEncryptRead(b *testing.B, s *Stream, size int64) { nonce := make([]byte, s.NonceSize()) buffer := make([]byte, s.bufSize+s.cipher.Overhead()) plaintext := &io.LimitedReader{R: DevNull, N: size} r := s.EncryptReader(plaintext, nonce, nil) b.SetBytes(size) b.ResetTimer() for i := 0; i < b.N; i++ { for { if _, err := readFrom(r, buffer); err == io.EOF { break } else if err != nil { panic(err) } } r.seqNum = 1 r.offset = 0 r.closed = false r.firstRead = true r.associatedData[0] = 0 plaintext.N = size } } func benchDecryptRead(b *testing.B, s *Stream, size int64) { nonce := make([]byte, s.NonceSize()) buffer := make([]byte, s.bufSize) plaintext := &io.LimitedReader{R: DevNull, N: size} sr := s.EncryptReader(plaintext, nonce, nil) r := s.DecryptReader(sr, nonce, nil) b.SetBytes(2*size + s.Overhead(size)) b.ResetTimer() for i := 0; i < b.N; i++ { for { if _, err := readFrom(r, buffer); err == io.EOF { break } else if err != nil { panic(err) } } r.seqNum, sr.seqNum = 1, 1 r.offset, sr.offset = 0, 0 r.closed, sr.closed = false, false r.firstRead, sr.firstRead = true, true r.associatedData[0], sr.associatedData[0] = 0, 0 plaintext.N = size } } func benchDecryptReadAt(b *testing.B, s *Stream, offset, size int64) { nonce := make([]byte, s.NonceSize()) data := make([]byte, size) ciphertext := bytes.NewBuffer(nil) w := s.EncryptWriter(ciphertext, nonce, nil) w.Write(data) w.Close() r := s.DecryptReaderAt(bytes.NewReader(ciphertext.Bytes()), nonce, nil) b.SetBytes(size - offset + s.Overhead(size-offset)) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := r.ReadAt(data, offset); err != nil { if err != io.EOF { panic(err) } } } } func benchEncryptWriteTo(b *testing.B, s *Stream, size int64) { nonce := make([]byte, s.NonceSize()) plaintext := &io.LimitedReader{R: DevNull, N: size} r := s.EncryptReader(plaintext, nonce, nil) b.SetBytes(size) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := r.WriteTo(DevNull); err != nil { panic(err) } r.seqNum = 1 r.offset = 0 r.closed = false r.firstRead = true r.associatedData[0] = 0 plaintext.N = size } } func benchDecryptWriteTo(b *testing.B, s *Stream, size int64) { nonce := make([]byte, s.NonceSize()) plaintext := &io.LimitedReader{R: DevNull, N: size} sr := s.EncryptReader(plaintext, nonce, nil) r := s.DecryptReader(sr, nonce, nil) b.SetBytes(size) b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := r.WriteTo(DevNull); err != nil { panic(err) } r.seqNum, sr.seqNum = 1, 1 r.offset, sr.offset = 0, 0 r.closed, sr.closed = false, false r.firstRead, sr.firstRead = true, true r.associatedData[0], sr.associatedData[0] = 0, 0 plaintext.N = size } } golang-github-secure-io-sio-go-0.3.1/examples_test.go000066400000000000000000000244161433374623100225470ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio_test import ( "bytes" "crypto/aes" "crypto/cipher" "encoding/hex" "fmt" "io" "io/ioutil" "strings" "github.com/secure-io/sio-go" ) func ExampleNewStream_aES128GCM() { // Load your secret key from a safe place. You should use a unique key // per data stream since the nonce size of `Stream` (with AES-128-GCM) is // to short for chosing a nonce at random (risk of repeating the // nonce is too high). // Obviously don't use this example key for anything real. // If you want to convert a passphrase to a key, use a suitable // package like argon2 or scrypt. key, _ := hex.DecodeString("4c4d737f2199f3ccb13d2c81dfe38eb8") stream, err := sio.AES_128_GCM.Stream(key) if err != nil { panic(err) // TODO: error handling } // Print the nonce size for Stream (with AES-128-GCM) and the overhead added // when encrypting a 1 MiB data stream. fmt.Printf("NonceSize: %d, Overhead: %d", stream.NonceSize(), stream.Overhead(1024*1024)) //Output: NonceSize: 8, Overhead: 1024 } func ExampleNewStream_aES192GCM() { // Load your secret key from a safe place. You should use a unique key // per data stream since the nonce size of `Stream` (with AES-192-GCM) is // to short for chosing a nonce at random (risk of repeating the // nonce is too high). // Obviously don't use this example key for anything real. // If you want to convert a passphrase to a key, use a suitable // package like argon2 or scrypt. key, _ := hex.DecodeString("fe6165e714125dc3d84d3349f9e3020430ce9d77e0a1f2c0") block, err := aes.NewCipher(key) if err != nil { panic(err) // TODO: error handling } gcm, _ := cipher.NewGCM(block) stream := sio.NewStream(gcm, sio.BufSize) // Print the nonce size for Stream (with AES-192-GCM) and the overhead added // when encrypting a 1 MiB data stream. fmt.Printf("NonceSize: %d, Overhead: %d", stream.NonceSize(), stream.Overhead(1024*1024)) //Output: NonceSize: 8, Overhead: 1024 } func ExampleNewStream_aES256GCM() { // Load your secret key from a safe place. You should use a unique key // per data stream since the nonce size of `Stream` (with AES-256-GCM) is // to short for chosing a nonce at random (risk of repeating the // nonce is too high). // Obviously don't use this example key for anything real. // If you want to convert a passphrase to a key, use a suitable // package like argon2 or scrypt. key, _ := hex.DecodeString("5b48c6945ae03a93ecc20e38305d2cbe4a177133d83bf4773f1f3be636e2cc4b") stream, err := sio.AES_256_GCM.Stream(key) if err != nil { panic(err) // TODO: error handling } // Print the nonce size for Stream (with AES-256-GCM) and the overhead added // when encrypting a 1 MiB data stream. fmt.Printf("NonceSize: %d, Overhead: %d", stream.NonceSize(), stream.Overhead(1024*1024)) //Output: NonceSize: 8, Overhead: 1024 } func ExampleNewStream_xChaCha20Poly1305() { // Load your secret key from a safe place. You may reuse it for // en/decrypting multiple data streams. (XChaCha20-Poly1305 nonce // values are large enough to be chosen at random without risking // to select a nonce twice - probability is negligible). // // Obviously don't use this example key for anything real. // If you want to convert a passphrase to a key, use a suitable // package like argon2 or scrypt - or take a look at the sio/sioutil // package. key, _ := hex.DecodeString("f230e700c4f120b623b84ac26cbcb5ae926f44f36589e63745a46ae0ca47137d") stream, err := sio.XChaCha20Poly1305.Stream(key) if err != nil { panic(err) // TODO: error handling } // Print the nonce size for Stream (with XChaCha20-Poly1305) and the // overhead added when encrypting a 1 MiB data stream. fmt.Printf("NonceSize: %d, Overhead: %d", stream.NonceSize(), stream.Overhead(1024*1024)) //Output: NonceSize: 20, Overhead: 1024 } func ExampleEncReader() { // Use an unique key per data stream. For example derive one // from a password using a suitable package like argon2 or // from a master key using e.g. HKDF. // Obviously don't use this example key for anything real. key, _ := hex.DecodeString("ffb0823fcab82a983e1725e003c702252ef4fc7054796b3c23d08aa189f662c9") block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) stream := sio.NewStream(gcm, sio.BufSize) var ( // Use a unique nonce per key. If you choose an unique key // you can also set the nonce to all zeros. (What to prefer // depends on the application). nonce []byte = make([]byte, stream.NonceSize()) // If you want to bind additional data to the ciphertext // (e.g. a file name to prevent renaming / moving the file) // set the associated data. But be aware that the associated // data is not encrypted (only authenticated) and must be // available when decrypting the ciphertext again. associatedData []byte = nil ) plaintext := strings.NewReader("some plaintext") r := stream.EncryptReader(plaintext, nonce, associatedData) // Reading from r returns encrypted and authenticated data. ioutil.ReadAll(r) //Output: } func ExampleDecReader() { // Use the key used to encrypt the data. (See e.g. the EncReader example). // Obviously don't use this example key for anything real. key, _ := hex.DecodeString("ffb0823fcab82a983e1725e003c702252ef4fc7054796b3c23d08aa189f662c9") block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) stream := sio.NewStream(gcm, sio.BufSize) var ( // Use the nonce value using during encryption. // (See e.g. the EncWriter example). nonce []byte = make([]byte, stream.NonceSize()) // Use the associated data using during encryption. // (See e.g. the EncWriter example). associatedData []byte = nil ) ciphertext := hex.NewDecoder(strings.NewReader("9f54ed8df9cffaff02eddb479b95fd3bed9391758a4f81376cfadd7f8c00")) r := stream.DecryptReader(ciphertext, nonce, associatedData) // Reading from r returns the original plaintext (or an error). if _, err := ioutil.ReadAll(r); err != nil { if err == sio.NotAuthentic { // Read data is not authentic -> ciphertext has been modified. // TODO: error handling panic(err) } } //Output: } func ExampleDecReaderAt() { // Use the key used to encrypt the data. (See e.g. the EncReader example). // Obviously don't use this example key for anything real. key, _ := hex.DecodeString("ffb0823fcab82a983e1725e003c702252ef4fc7054796b3c23d08aa189f662c9") block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) stream := sio.NewStream(gcm, sio.BufSize) var ( // Use the nonce value using during encryption. // (See e.g. the EncWriter example). nonce []byte = make([]byte, stream.NonceSize()) // Use the associated data using during encryption. // (See e.g. the EncWriter example). associatedData []byte = nil ) rawBytes, _ := hex.DecodeString("9f54ed8df9cffaff02eddb479b95fd3bed9391758a4f81376cfadd7f8c00") ciphertext := bytes.NewReader(rawBytes) r := stream.DecryptReaderAt(ciphertext, nonce, associatedData) section := io.NewSectionReader(r, 5, 9) // Read the 'plaintext' substring from 'some plaintext' // Reading from section returns the original plaintext (or an error). if _, err := ioutil.ReadAll(section); err != nil { if err == sio.NotAuthentic { // Read data is not authentic -> ciphertext has been modified. // TODO: error handling panic(err) } } //Output: } func ExampleEncWriter() { // Use an unique key per data stream. For example derive one // from a password using a suitable package like argon2 or // from a master key using e.g. HKDF. // Obviously don't use this example key for anything real. key, _ := hex.DecodeString("ffb0823fcab82a983e1725e003c702252ef4fc7054796b3c23d08aa189f662c9") block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) stream := sio.NewStream(gcm, sio.BufSize) var ( // Use a unique nonce per key. If you choose an unique key // you can also set the nonce to all zeros. (What to prefer // depends on the application). nonce []byte = make([]byte, stream.NonceSize()) // If you want to bind additional data to the ciphertext // (e.g. a file name to prevent renaming / moving the file) // set the associated data. But be aware that the associated // data is not encrypted (only authenticated) and must be // available when decrypting the ciphertext again. associatedData []byte = nil ) ciphertext := bytes.NewBuffer(nil) // You can also use the plaintext size and stream.Overhead() to avoid re-allocation. w := stream.EncryptWriter(ciphertext, nonce, associatedData) defer func() { if err := w.Close(); err != nil { // The EncWriter must be closed to complete the encryption. panic(err) // TODO: error handling } }() // Writing plaintext to w writes encrypted and authenticated data to // the underlying io.Writer (i.e. the ciphertext *bytes.Buffer) if _, err := io.WriteString(w, "some plaintext"); err != nil { // TODO: error handling } //Output: } func ExampleDecWriter() { // Use the key used to encrypt the data. (See e.g. the EncWriter example). // Obviously don't use this example key for anything real. key, _ := hex.DecodeString("ffb0823fcab82a983e1725e003c702252ef4fc7054796b3c23d08aa189f662c9") block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) stream := sio.NewStream(gcm, sio.BufSize) var ( // Use the nonce value using during encryption. // (See e.g. the EncWriter example). nonce []byte = make([]byte, stream.NonceSize()) // Use the associated data using during encryption. // (See e.g. the EncWriter example). associatedData []byte = nil ) plaintext := bytes.NewBuffer(nil) w := stream.DecryptWriter(plaintext, nonce, associatedData) defer func() { if err := w.Close(); err != nil { if err == sio.NotAuthentic { // During Close() the DecWriter may detect unauthentic data -> decryption error. panic(err) // TODO: error handling } panic(err) // TODO: error handling } }() ciphertext, _ := hex.DecodeString("9f54ed8df9cffaff02eddb479b95fd3bed9391758a4f81376cfadd7f8c00") // Writing ciphertext to w writes decrypted and verified data to // the underlying io.Writer (i.e. the plaintext *bytes.Buffer) or // returns an error. if _, err := w.Write(ciphertext); err != nil { if err == sio.NotAuthentic { // Read data is not authentic -> ciphertext has been modified. // TODO: error handling panic(err) } } //Output: } golang-github-secure-io-sio-go-0.3.1/fuzz-corpus.sh000077500000000000000000000012661433374623100222070ustar00rootroot00000000000000#!/bin/sh if [ -n "$PWD/corpus" ]; then rm -rf "$PWD/corpus" fi mkdir "$PWD/corpus" yes | head -c 16 > "$PWD/corpus/0" yes | head -c 1024 > "$PWD/corpus/1" yes | head -c 65536 > "$PWD/corpus/2" yes n | head -c 32768 > "$PWD/corpus/3" cat /dev/urandom | head -c 16 > "$PWD/corpus/4" cat /dev/urandom | head -c 1024 > "$PWD/corpus/5" cat /dev/urandom | head -c 65536 > "$PWD/corpus/6" cat /dev/urandom | head -c 524288 > "$PWD/corpus/7" cat /dev/urandom | head -c 16 | base64 > "$PWD/corpus/8" cat /dev/urandom | head -c 1024 | base64 > "$PWD/corpus/9" cat /dev/urandom | head -c 65536 | base64 > "$PWD/corpus/10" cat /dev/urandom | head -c 524288 | base64 > "$PWD/corpus/11" golang-github-secure-io-sio-go-0.3.1/fuzz.go000066400000000000000000000242701433374623100206660ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. // +build gofuzz package sio import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "fmt" "io" "io/ioutil" mrand "math/rand" "sync" "golang.org/x/crypto/chacha20poly1305" ) var BufferPool = sync.Pool{ New: func() interface{} { return bytes.NewBuffer(nil) }, } var ( wStream *Stream rStream *Stream ) func init() { // AES-128-GCM key := make([]byte, 16) _, err := io.ReadFull(rand.Reader, key) if err != nil { panic(err) } block, err := aes.NewCipher(key) if err != nil { panic(err) } gcm, err := cipher.NewGCM(block) if err != nil { panic(err) } rStream = NewStream(gcm, BufSize) // ChaCha20-Poly1305 key = make([]byte, 32) _, err = io.ReadFull(rand.Reader, key) if err != nil { panic(err) } c20p1305, err := chacha20poly1305.New(key) if err != nil { panic(err) } wStream = NewStream(c20p1305, BufSize) } var maxDataLen int func FuzzAll(data []byte) int { v := FuzzReader(data) v += FuzzReadByte(data) v += FuzzReaderAt(data) v += FuzzWriteTo(data) v += FuzzWrite(data) v += FuzzWriteByte(data) v += FuzzReadFrom(data) if len(data) > maxDataLen { // Prefer longer inputs maxDataLen = len(data) v++ } return v } func FuzzReader(data []byte) int { nonce := make([]byte, rStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) dLen := int64(len(data)) buffer := make([]byte, (1+mrand.Intn(64))*1024) plaintext.Reset() dec := rStream.DecryptReader(rStream.EncryptReader(bytes.NewReader(data), nonce, data), nonce, data) if n, err := copyBuffer(plaintext, dec, buffer); int(n) != len(data) || err != nil { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } ciphertext.Reset() enc := rStream.EncryptReader(bytes.NewReader(data), nonce, data) if _, err := copyBuffer(ciphertext, enc, buffer); err != nil { panic(err) } plaintext.Reset() dec = rStream.DecryptReader(bytes.NewReader(ciphertext.Bytes()), nonce, data) if _, err := copyBuffer(plaintext, io.LimitReader(dec, dLen/2), buffer); err != nil { panic(err) } if _, err := copyBuffer(plaintext, io.LimitReader(dec, dLen-(dLen/2)), buffer); err != nil { panic(err) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } dec = rStream.DecryptReader(bytes.NewReader(data), nonce, data) if n, err := copyBuffer(ioutil.Discard, dec, buffer); n != 0 || err != NotAuthentic { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } return 0 } func FuzzReaderAt(data []byte) int { nonce := make([]byte, rStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) buffer := make([]byte, (1+mrand.Intn(64))*1024) ciphertext.Reset() enc := rStream.EncryptReader(bytes.NewReader(data), nonce, data) if _, err := copyBuffer(ciphertext, enc, buffer); err != nil { panic(err) } plaintext.Reset() dec := rStream.DecryptReader(bytes.NewReader(ciphertext.Bytes()), nonce, data) if _, err := copyBuffer(plaintext, dec, buffer); err != nil { panic(err) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } if len(data) > 0 { r := io.NewSectionReader(rStream.DecryptReaderAt(bytes.NewReader(data), nonce, data), 0, int64(len(data))) if n, err := copyBuffer(ioutil.Discard, r, buffer); n != 0 || err != NotAuthentic { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } } return 0 } func FuzzWriteTo(data []byte) int { nonce := make([]byte, rStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) plaintext.Reset() dec := rStream.DecryptReader(rStream.EncryptReader(bytes.NewReader(data), nonce, data), nonce, data) if n, err := dec.WriteTo(plaintext); int(n) != len(data) || err != nil { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } ciphertext.Reset() enc := rStream.EncryptReader(bytes.NewReader(data), nonce, data) if _, err := enc.WriteTo(ciphertext); err != nil { panic(err) } plaintext.Reset() dec = rStream.DecryptReader(bytes.NewReader(ciphertext.Bytes()), nonce, data) if _, err := dec.WriteTo(plaintext); err != nil { panic(err) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } dec = rStream.DecryptReader(bytes.NewReader(data), nonce, data) if n, err := dec.WriteTo(ioutil.Discard); n != 0 || err != NotAuthentic { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } return 0 } func FuzzWrite(data []byte) int { nonce := make([]byte, wStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) buffer := make([]byte, (1+mrand.Intn(64))*1024) plaintext.Reset() enc := wStream.EncryptWriter(wStream.DecryptWriter(plaintext, nonce, data), nonce, data) if n, err := copyBuffer(enc, bytes.NewReader(data), buffer); n != int64(len(data)) || err != nil { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } if err := enc.Close(); err != nil { panic(err) } ciphertext.Reset() enc = wStream.EncryptWriter(ciphertext, nonce, data) if _, err := copyBuffer(enc, bytes.NewReader(data), buffer); err != nil { panic(err) } if err := enc.Close(); err != nil { panic(err) } plaintext.Reset() dec := wStream.DecryptWriter(plaintext, nonce, data) if _, err := copyBuffer(dec, bytes.NewReader(ciphertext.Bytes()), buffer); err != nil { panic(err) } if err := dec.Close(); err != nil { panic(err) } dec = wStream.DecryptWriter(ioutil.Discard, nonce, data) if _, err := copyBuffer(dec, bytes.NewReader(data), buffer); err != NotAuthentic { if cErr := dec.Close(); err != nil || cErr != NotAuthentic { panic(fmt.Sprintf("Write: %v, Close: %v", err, cErr)) } } return 0 } func FuzzReadFrom(data []byte) int { nonce := make([]byte, wStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) plaintext.Reset() enc := wStream.EncryptWriter(wStream.DecryptWriter(plaintext, nonce, data), nonce, data) if n, err := enc.ReadFrom(bytes.NewReader(data)); n != int64(len(data)) || err != nil { panic(fmt.Sprintf("N: %d, Err: %v", n, err)) } if err := enc.Close(); err != nil { panic(err) } ciphertext.Reset() enc = wStream.EncryptWriter(ciphertext, nonce, data) if _, err := enc.ReadFrom(bytes.NewReader(data)); err != nil { panic(err) } if err := enc.Close(); err != nil { panic(err) } plaintext.Reset() dec := wStream.DecryptWriter(plaintext, nonce, data) if _, err := dec.ReadFrom(bytes.NewReader(ciphertext.Bytes())); err != nil { panic(err) } if err := dec.Close(); err != nil { panic(err) } dec = wStream.DecryptWriter(ioutil.Discard, nonce, data) if _, err := dec.ReadFrom(bytes.NewReader(data)); err != NotAuthentic { if cErr := dec.Close(); err != nil || cErr != NotAuthentic { panic(fmt.Sprintf("Write: %v, Close: %v", err, cErr)) } } return 0 } func FuzzReadByte(data []byte) int { nonce := make([]byte, rStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) plaintext.Reset() dec := rStream.DecryptReader(rStream.EncryptReader(bytes.NewReader(data), nonce, data), nonce, data) if err := copySingleBytes(plaintext, dec); err != nil { panic(err) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } dec = rStream.DecryptReader(bytes.NewReader(data), nonce, data) if err := copySingleBytes(discard{}, dec); err != NotAuthentic { panic(err) } return 0 } func FuzzWriteByte(data []byte) int { nonce := make([]byte, wStream.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err) } plaintext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(plaintext) ciphertext := BufferPool.Get().(*bytes.Buffer) defer BufferPool.Put(ciphertext) plaintext.Reset() enc := wStream.EncryptWriter(wStream.DecryptWriter(plaintext, nonce, data), nonce, data) if err := copySingleBytes(enc, bytes.NewReader(data)); err != nil { panic(err) } if err := enc.Close(); err != nil { panic(err) } if !bytes.Equal(plaintext.Bytes(), data) { panic("plaintext does not match origin data") } dec := rStream.DecryptWriter(ioutil.Discard, nonce, data) if err := copySingleBytes(dec, bytes.NewReader(data)); err != NotAuthentic { if cErr := dec.Close(); err != nil || cErr != NotAuthentic { panic(err) } } return 0 } func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) { var written int64 var err error for { nr, er := src.Read(buf) if nr > 0 { nw, ew := dst.Write(buf[:nr]) if nw > 0 { written += int64(nw) } if ew != nil { err = ew break } if nr != nw { err = io.ErrShortWrite break } } if er != nil { if er != io.EOF { err = er } break } } return written, err } func copySingleBytes(dst io.ByteWriter, src io.ByteReader) error { for { b, err := src.ReadByte() if err == io.EOF { return nil } if err != nil { return err } if err = dst.WriteByte(b); err != nil { return err } } } type discard struct{} func (discard) WriteByte(p byte) error { return nil } golang-github-secure-io-sio-go-0.3.1/fuzzbuzz.yaml000066400000000000000000000004421433374623100221310ustar00rootroot00000000000000base: ubuntu:16.04 targets: - name: sio language: go version: "1.11" setup: - ./fuzz-corpus.sh corpus: ./corpus harness: function: FuzzAll build_tags: gofuzz package: github.com/secure-io/sio-go checkout: github.com/secure-io/sio-go golang-github-secure-io-sio-go-0.3.1/go.mod000066400000000000000000000002461433374623100204440ustar00rootroot00000000000000module github.com/secure-io/sio-go go 1.13 require ( golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 ) golang-github-secure-io-sio-go-0.3.1/go.sum000066400000000000000000000016431433374623100204730ustar00rootroot00000000000000golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang-github-secure-io-sio-go-0.3.1/helper_test.go000066400000000000000000000041361433374623100222050ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( "crypto/rand" "encoding/hex" "encoding/json" "io" "io/ioutil" mrand "math/rand" ) var DevNull = devNull{} type devNull struct{} func (devNull) Read(p []byte) (int, error) { return len(p), nil } func (devNull) Write(p []byte) (int, error) { return len(p), nil } func random(size int) []byte { key := make([]byte, size) if _, err := io.ReadFull(rand.Reader, key); err != nil { panic(err) } return key } func randomN(size int) []byte { key := make([]byte, mrand.Intn(size)) if _, err := io.ReadFull(rand.Reader, key); err != nil { panic(err) } return key } func copyBytes(dst io.ByteWriter, src io.ByteReader) error { for { b, err := src.ReadByte() if err == io.EOF { return nil } if err != nil { return err } if err = dst.WriteByte(b); err != nil { return err } } } func loadTestVectors(filename string) []TestVector { data, err := ioutil.ReadFile(filename) if err != nil { panic(err) } var vec []struct { Algorithm Algorithm BufSize int Key string Nonce string AssociatedData string Plaintext string Ciphertext string } if err = json.Unmarshal(data, &vec); err != nil { panic(err) } testVectors := make([]TestVector, len(vec)) for i, v := range vec { key, err := hex.DecodeString(v.Key) if err != nil { panic(err) } nonce, err := hex.DecodeString(v.Nonce) if err != nil { panic(err) } associatedData, err := hex.DecodeString(v.AssociatedData) if err != nil { panic(err) } plaintext, err := hex.DecodeString(v.Plaintext) if err != nil { panic(err) } ciphertext, err := hex.DecodeString(v.Ciphertext) if err != nil { panic(err) } testVectors[i] = TestVector{ Algorithm: v.Algorithm, BufSize: v.BufSize, Key: key, Nonce: nonce, AssociatedData: associatedData, Plaintext: plaintext, Ciphertext: ciphertext, } } return testVectors } golang-github-secure-io-sio-go-0.3.1/reader.go000066400000000000000000000312171433374623100211310ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( "crypto/cipher" "encoding/binary" "io" "io/ioutil" "math" "sync" ) // An EncReader encrypts and authenticates everything it reads // from an underlying io.Reader. type EncReader struct { r io.Reader cipher cipher.AEAD bufSize int seqNum uint32 nonce []byte associatedData []byte buffer []byte ciphertextBuffer []byte offset int err error carry byte firstRead, closed bool } // Read behaves as specified by the io.Reader interface. // In particular, Read reads up to len(p) encrypted bytes // into p. It returns the number of bytes read (0 <= n <= len(p)) // and any error encountered while reading from the underlying // io.Reader. // // When Read cannot encrypt any more bytes securely it returns // ErrExceeded. func (r *EncReader) Read(p []byte) (n int, err error) { if r.err != nil { return n, r.err } if r.firstRead { r.firstRead = false n, err = r.readFragment(p, 0) if err != nil { return n, err } p = p[n:] } if r.offset > 0 { nn := copy(p, r.ciphertextBuffer[r.offset:]) n += nn if nn == len(p) { r.offset += nn return n, nil } p = p[nn:] r.offset = 0 } if r.closed { return n, io.EOF } nn, err := r.readFragment(p, 1) return n + nn, err } // ReadByte behaves as specified by the io.ByteReader // interface. In particular, ReadByte returns the next // encrypted byte or any error encountered. // // When ReadByte cannot encrypt one more byte // securely it returns ErrExceeded. func (r *EncReader) ReadByte() (byte, error) { if r.err != nil { return 0, r.err } if r.firstRead { r.firstRead = false if _, err := r.readFragment(nil, 0); err != nil { return 0, err } b := r.ciphertextBuffer[0] r.offset = 1 return b, nil } if r.offset > 0 && r.offset < len(r.ciphertextBuffer) { b := r.ciphertextBuffer[r.offset] r.offset++ return b, nil } if r.closed { return 0, io.EOF } r.offset = 0 if _, err := r.readFragment(nil, 1); err != nil { return 0, err } b := r.ciphertextBuffer[0] r.offset = 1 return b, nil } // WriteTo behaves as specified by the io.WriterTo // interface. In particular, WriteTo writes encrypted // data to w until either there's no more data to write, // an error occurs or no more data can be encrypted // securely. When WriteTo cannot encrypt more data // securely it returns ErrExceeded. func (r *EncReader) WriteTo(w io.Writer) (int64, error) { var n int64 if r.firstRead { r.firstRead = false nn, err := r.readFragment(r.buffer, 0) if err != nil && err != io.EOF { return n, err } nn, err = writeTo(w, r.buffer[:nn]) if err != nil { return n, err } n += int64(nn) if r.closed { return n, nil } } if r.err != nil { return n, r.err } if r.offset > 0 { nn, err := writeTo(w, r.ciphertextBuffer[r.offset:]) if err != nil { r.err = err return n, err } r.offset = 0 n += int64(nn) } if r.closed { return n, io.EOF } for { nn, err := r.readFragment(r.buffer, 1) if err != nil && err != io.EOF { return n, err } nn, err = writeTo(w, r.buffer[:nn]) if err != nil { r.err = err return n, err } n += int64(nn) if r.closed { return n, nil } } } func (r *EncReader) readFragment(p []byte, firstReadOffset int) (int, error) { if r.seqNum == 0 { r.err = ErrExceeded return 0, r.err } binary.LittleEndian.PutUint32(r.nonce[r.cipher.NonceSize()-4:], r.seqNum) r.seqNum++ r.buffer[0] = r.carry n, err := readFrom(r.r, r.buffer[firstReadOffset:1+r.bufSize]) switch { default: r.carry = r.buffer[r.bufSize] if len(p) < r.bufSize+r.cipher.Overhead() { r.ciphertextBuffer = r.cipher.Seal(r.buffer[:0], r.nonce, r.buffer[:r.bufSize], r.associatedData) r.offset = copy(p, r.ciphertextBuffer) return r.offset, nil } r.cipher.Seal(p[:0], r.nonce, r.buffer[:r.bufSize], r.associatedData) return r.bufSize + r.cipher.Overhead(), nil case err == io.EOF: r.closed = true r.associatedData[0] = 0x80 if len(p) < firstReadOffset+n+r.cipher.Overhead() { r.ciphertextBuffer = r.cipher.Seal(r.buffer[:0], r.nonce, r.buffer[:firstReadOffset+n], r.associatedData) r.offset = copy(p, r.ciphertextBuffer) return r.offset, nil } r.cipher.Seal(p[:0], r.nonce, r.buffer[:firstReadOffset+n], r.associatedData) return firstReadOffset + n + r.cipher.Overhead(), io.EOF case err != nil: r.err = err return 0, r.err } } // A DecReader decrypts and verifies everything it reads // from an underlying io.Reader. A DecReader never returns // invalid (i.e. not authentic) data. type DecReader struct { r io.Reader cipher cipher.AEAD bufSize int seqNum uint32 nonce []byte associatedData []byte buffer []byte plaintextBuffer []byte offset int err error carry byte firstRead, closed bool } // Read behaves like specified by the io.Reader interface. // In particular, Read reads up to len(p) decrypted bytes // into p. It returns the number of bytes read (0 <= n <= len(p)) // and any error encountered while reading from the underlying // io.Reader. // // When Read fails to decrypt some data returned by the underlying // io.Reader it returns NotAuthentic. This error indicates // that the encrypted data has been (maliciously) modified. // // When Read cannot decrypt more bytes securely it returns // ErrExceeded. However, this can only happen when the // underlying io.Reader returns valid but too many // encrypted bytes. Therefore, ErrExceeded indicates // a misbehaving producer of encrypted data. func (r *DecReader) Read(p []byte) (n int, err error) { if r.err != nil { return n, r.err } if r.firstRead { r.firstRead = false n, err = r.readFragment(p, 0) if err != nil { return n, err } p = p[n:] } if r.offset > 0 { nn := copy(p, r.plaintextBuffer[r.offset:]) n += nn if nn == len(p) { r.offset += nn return n, nil } p = p[nn:] r.offset = 0 } if r.closed { return n, io.EOF } nn, err := r.readFragment(p, 1) return n + nn, err } // ReadByte behaves as specified by the io.ByteReader // interface. In particular, ReadByte returns the next // decrypted byte or any error encountered. // // When ReadByte fails to decrypt the next byte returned by // the underlying io.Reader it returns NotAuthentic. This // error indicates that the encrypted byte has been // (maliciously) modified. // // When Read cannot decrypt one more byte securely it // returns ErrExceeded. However, this can only happen // when the underlying io.Reader returns valid but too // many encrypted bytes. Therefore, ErrExceeded indicates // a misbehaving producer of encrypted data. func (r *DecReader) ReadByte() (byte, error) { if r.err != nil { return 0, r.err } if r.firstRead { r.firstRead = false if _, err := r.readFragment(nil, 0); err != nil { return 0, err } b := r.plaintextBuffer[0] r.offset = 1 return b, nil } if r.offset > 0 && r.offset < len(r.plaintextBuffer) { b := r.plaintextBuffer[r.offset] r.offset++ return b, nil } if r.closed { return 0, io.EOF } r.offset = 0 if _, err := r.readFragment(nil, 1); err != nil { return 0, err } b := r.plaintextBuffer[0] r.offset = 1 return b, nil } // WriteTo behaves as specified by the io.WriterTo // interface. In particular, WriteTo writes decrypted // data to w until either there's no more data to write, // an error occurs or the encrypted data is invalid. // // When WriteTo fails to decrypt some data it returns // NotAuthentic. This error indicates that the encrypted // bytes has been (maliciously) modified. // // When WriteTo cannot decrypt any more bytes securely it // returns ErrExceeded. However, this can only happen // when the underlying io.Reader returns valid but too // many encrypted bytes. Therefore, ErrExceeded indicates // a misbehaving producer of encrypted data. func (r *DecReader) WriteTo(w io.Writer) (int64, error) { var n int64 if r.err != nil { return n, r.err } if r.firstRead { r.firstRead = false nn, err := r.readFragment(r.buffer, 0) if err != nil && err != io.EOF { return n, err } nn, err = writeTo(w, r.buffer[:nn]) if err != nil { return n, err } n += int64(nn) if r.closed { return n, nil } } if r.offset > 0 { nn, err := writeTo(w, r.plaintextBuffer[r.offset:]) if err != nil { r.err = err return n, err } r.offset = 0 n += int64(nn) } if r.closed { return n, io.EOF } for { nn, err := r.readFragment(r.buffer, 1) if err != nil && err != io.EOF { return n, err } nn, err = writeTo(w, r.buffer[:nn]) if err != nil { r.err = err return n, err } n += int64(nn) if r.closed { return n, nil } } } func (r *DecReader) readFragment(p []byte, firstReadOffset int) (int, error) { if r.seqNum == 0 { r.err = ErrExceeded return 0, r.err } binary.LittleEndian.PutUint32(r.nonce[r.cipher.NonceSize()-4:], r.seqNum) r.seqNum++ ciphertextLen := r.bufSize + r.cipher.Overhead() r.buffer[0] = r.carry n, err := readFrom(r.r, r.buffer[firstReadOffset:1+ciphertextLen]) switch { default: r.carry = r.buffer[ciphertextLen] if len(p) < r.bufSize { r.plaintextBuffer, err = r.cipher.Open(r.buffer[:0], r.nonce, r.buffer[:ciphertextLen], r.associatedData) if err != nil { r.err = NotAuthentic return 0, r.err } r.offset = copy(p, r.plaintextBuffer) return r.offset, nil } if _, err = r.cipher.Open(p[:0], r.nonce, r.buffer[:ciphertextLen], r.associatedData); err != nil { r.err = NotAuthentic return 0, r.err } return r.bufSize, nil case err == io.EOF: if firstReadOffset+n < r.cipher.Overhead() { r.err = NotAuthentic return 0, r.err } r.closed = true r.associatedData[0] = 0x80 if len(p) < firstReadOffset+n-r.cipher.Overhead() { r.plaintextBuffer, err = r.cipher.Open(r.buffer[:0], r.nonce, r.buffer[:firstReadOffset+n], r.associatedData) if err != nil { r.err = NotAuthentic return 0, r.err } r.offset = copy(p, r.plaintextBuffer) return r.offset, nil } if _, err = r.cipher.Open(p[:0], r.nonce, r.buffer[:firstReadOffset+n], r.associatedData); err != nil { r.err = NotAuthentic return 0, r.err } return firstReadOffset + n - r.cipher.Overhead(), io.EOF case err != nil: r.err = err return 0, r.err } } // A DecReaderAt decrypts and verifies everything it reads // from an underlying io.ReaderAt. A DecReaderAt never returns // invalid (i.e. not authentic) data. type DecReaderAt struct { r io.ReaderAt cipher cipher.AEAD bufPool sync.Pool bufSize int nonce []byte associatedData []byte } // ReadAt behaves like specified by the io.ReaderAt interface. // In particular, ReadAt reads len(p) decrypted bytes into p. // It returns the number of bytes read (0 <= n <= len(p)) // and any error encountered while reading from the underlying // io.Reader. When ReadAt returns n < len(p), it returns a non-nil // error explaining why more bytes were not returned. // // When ReadAt fails to decrypt some data returned by the underlying // io.ReaderAt it returns NotAuthentic. This error indicates // that the encrypted data has been (maliciously) modified. // // When ReadAt cannot decrypt more bytes securely it returns // ErrExceeded. However, this can only happen when the // underlying io.ReaderAt returns valid but too many // encrypted bytes. Therefore, ErrExceeded indicates // a misbehaving producer of encrypted data. func (r *DecReaderAt) ReadAt(p []byte, offset int64) (int, error) { if offset < 0 { return 0, errorType("sio: DecReaderAt.ReadAt: offset is negative") } t := offset / int64(r.bufSize) if t+1 > math.MaxUint32 { return 0, ErrExceeded } buffer := r.bufPool.Get().(*[]byte) defer r.bufPool.Put(buffer) decReader := DecReader{ r: §ionReader{r: r.r, off: t * int64(r.bufSize+r.cipher.Overhead())}, cipher: r.cipher, bufSize: r.bufSize, nonce: make([]byte, r.cipher.NonceSize()), associatedData: make([]byte, 1+r.cipher.Overhead()), seqNum: 1 + uint32(t), buffer: *buffer, firstRead: true, } copy(decReader.nonce, r.nonce) copy(decReader.associatedData, r.associatedData) if k := offset % int64(r.bufSize); k > 0 { if _, err := io.CopyN(ioutil.Discard, &decReader, k); err != nil { return 0, err } } return readFrom(&decReader, p) } // Use a custom sectionReader since io.SectionReader // demands a read limit. type sectionReader struct { r io.ReaderAt off int64 err error } func (r *sectionReader) Read(p []byte) (int, error) { if r.err != nil { return 0, r.err } var n int n, r.err = r.r.ReadAt(p, r.off) r.off += int64(n) return n, r.err } golang-github-secure-io-sio-go-0.3.1/reader_test.go000066400000000000000000000225471433374623100221760ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( "bytes" "io" "math" "testing" ) func TestVectorRead(t *testing.T) { for i, test := range TestVectors { stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } pLen := int64(len(test.Plaintext)) plaintext := make([]byte, pLen) dr := stream.DecryptReader(bytes.NewReader(test.Ciphertext), test.Nonce, test.AssociatedData) if _, err = io.ReadFull(dr, plaintext); err != nil { t.Fatalf("Test: %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext, test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } ciphertext := make([]byte, pLen+stream.Overhead(pLen)) er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if _, err = io.ReadFull(er, ciphertext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if !bytes.Equal(ciphertext, test.Ciphertext) { t.Fatalf("Test %d: ciphertext does not match original ciphertext", i) } } } func TestVectorReadByte(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range TestVectors { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } dr := stream.DecryptReader(bytes.NewReader(test.Ciphertext), test.Nonce, test.AssociatedData) if err = copyBytes(plaintext, dr); err != nil { t.Fatalf("Test: %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if err = copyBytes(ciphertext, er); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if !bytes.Equal(ciphertext.Bytes(), test.Ciphertext) { t.Fatalf("Test %d: ciphertext does not match original plaintext", i) } } } func TestVectorWriteTo(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range TestVectors { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } dr := stream.DecryptReader(bytes.NewReader(test.Ciphertext), test.Nonce, test.AssociatedData) if _, err = dr.WriteTo(plaintext); err != nil { t.Fatalf("Test: %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if _, err = er.WriteTo(ciphertext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if !bytes.Equal(ciphertext.Bytes(), test.Ciphertext) { t.Fatalf("Test %d: ciphertext does not match original plaintext", i) } } } func TestVectorReadAt(t *testing.T) { for i, test := range TestVectors { stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } pLen := int64(len(test.Plaintext)) plaintext := make([]byte, pLen) dr := stream.DecryptReaderAt(bytes.NewReader(test.Ciphertext), test.Nonce, test.AssociatedData) if _, err = dr.ReadAt(plaintext, 0); err != nil { t.Fatalf("Test: %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext, test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestVectorReadAtSection(t *testing.T) { plaintext := bytes.NewBuffer(nil) for i, test := range TestVectors { plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } dr := stream.DecryptReaderAt(bytes.NewReader(test.Ciphertext), test.Nonce, test.AssociatedData) r := io.NewSectionReader(dr, 0, math.MaxInt64) // Use max. int64 to ensure we reach the EOF of the underlying ciphertext stream if _, err = io.Copy(plaintext, r); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleRead(t *testing.T) { for i, test := range SimpleTests { stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } pLen := int64(len(test.Plaintext)) ciphertext := make([]byte, pLen+stream.Overhead(pLen)) er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if _, err = io.ReadFull(er, ciphertext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } plaintext := make([]byte, pLen) dr := stream.DecryptReader(bytes.NewReader(ciphertext), test.Nonce, test.AssociatedData) if _, err = io.ReadFull(dr, plaintext); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext, test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleReadByte(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range SimpleTests { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if err = copyBytes(ciphertext, er); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } dr := stream.DecryptReader(bytes.NewReader(ciphertext.Bytes()), test.Nonce, test.AssociatedData) if err = copyBytes(plaintext, dr); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleWriteTo(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range SimpleTests { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if _, err = er.WriteTo(ciphertext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } dr := stream.DecryptReader(bytes.NewReader(ciphertext.Bytes()), test.Nonce, test.AssociatedData) if _, err = dr.WriteTo(plaintext); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleReadAt(t *testing.T) { for i, test := range SimpleTests { stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } pLen := int64(len(test.Plaintext)) ciphertext := make([]byte, pLen+stream.Overhead(pLen)) er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if _, err = io.ReadFull(er, ciphertext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } plaintext := make([]byte, pLen) dr := stream.DecryptReaderAt(bytes.NewReader(ciphertext), test.Nonce, test.AssociatedData) if _, err = dr.ReadAt(plaintext, 0); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext, test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleReadAtSection(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range SimpleTests { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } er := stream.EncryptReader(bytes.NewReader(test.Plaintext), test.Nonce, test.AssociatedData) if _, err = io.Copy(ciphertext, er); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } dr := stream.DecryptReaderAt(bytes.NewReader(ciphertext.Bytes()), test.Nonce, test.AssociatedData) r := io.NewSectionReader(dr, 0, math.MaxInt64) // Use max. int64 to ensure we reach the EOF of the underlying ciphertext stream if _, err = io.Copy(plaintext, r); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } golang-github-secure-io-sio-go-0.3.1/sio.go000066400000000000000000000267631433374623100204730ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. // Package sio implements a provable secure authenticated encryption // scheme for continuous byte streams. package sio import ( "crypto/aes" "crypto/cipher" "encoding/binary" "io" "math" "sync" "golang.org/x/crypto/chacha20poly1305" ) const ( // MaxBufSize is the maximum buffer size for streams. MaxBufSize = (1 << 24) - 1 // BufSize is the recommended buffer size for streams. BufSize = 1 << 14 ) const ( // NotAuthentic is returned when the decryption of a data stream fails. // It indicates that the encrypted data is invalid - i.e. it has been // (maliciously) modified. NotAuthentic errorType = "sio: data is not authentic" // ErrExceeded is returned when no more data can be encrypted / // decrypted securely. It indicates that the data stream is too // large to be encrypted / decrypted with a single key-nonce // combination. // // It depends on the buffer size how many bytes can be // encrypted / decrypted securely using the same key-nonce // combination. For BufSize the limit is ~64 TiB. ErrExceeded errorType = "sio: data limit exceeded" ) type errorType string func (e errorType) Error() string { return string(e) } // The constants above specify concrete AEAD algorithms // that can be used to encrypt and decrypt data streams. // // For example, you can create a new Stream using AES-GCM like this: // stream, err := sio.AES_128_GCM.Stream(key) const ( AES_128_GCM Algorithm = "AES-128-GCM" // The secret key must be 16 bytes long. See: https://golang.org/pkg/crypto/cipher/#NewGCM AES_256_GCM Algorithm = "AES-256-GCM" // The secret key must be 32 bytes long. See: https://golang.org/pkg/crypto/cipher/#NewGCM ChaCha20Poly1305 Algorithm = "ChaCha20-Poly1305" // The secret key must be 32 bytes long. See: https://godoc.org/golang.org/x/crypto/chacha20poly1305#New XChaCha20Poly1305 Algorithm = "XChaCha20-Poly1305" // The secret key must be 32 bytes long. See: https://godoc.org/golang.org/x/crypto/chacha20poly1305#NewX ) // Algorithm specifies an AEAD algorithm that // can be used to en/decrypt data streams. // // Its main purpose is to simplify code that // wants to use commonly used AEAD algorithms, // like AES-GCM, by providing a way to directly // create Streams from secret keys. type Algorithm string // String returns the string representation of an // AEAD algorithm. func (a Algorithm) String() string { return string(a) } // Stream returns a new Stream using the given // secret key and AEAD algorithm. // The returned Stream uses the default buffer size: BufSize. func (a Algorithm) Stream(key []byte) (*Stream, error) { return a.streamWithBufSize(key, BufSize) } func (a Algorithm) streamWithBufSize(key []byte, bufSize int) (*Stream, error) { var ( aead cipher.AEAD err error ) switch a { case AES_128_GCM: if len(key) != 128/8 { return nil, aes.KeySizeError(len(key)) } aead, err = newAESGCM(key) case AES_256_GCM: if len(key) != 256/8 { return nil, aes.KeySizeError(len(key)) } aead, err = newAESGCM(key) case ChaCha20Poly1305: aead, err = chacha20poly1305.New(key) case XChaCha20Poly1305: aead, err = chacha20poly1305.NewX(key) default: return nil, errorType("sio: invalid algorithm name") } if err != nil { return nil, err } return NewStream(aead, bufSize), nil } func newAESGCM(key []byte) (cipher.AEAD, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } return cipher.NewGCM(block) } // NewStream creates a new Stream that encrypts or decrypts data // streams with the cipher using bufSize large chunks. Therefore, // the bufSize must be the same for encryption and decryption. If // you don't have special requirements just use the default BufSize. // // The cipher must support a NonceSize() >= 4 and the // bufSize must be between 1 (inclusive) and MaxBufSize (inclusive). func NewStream(cipher cipher.AEAD, bufSize int) *Stream { if cipher.NonceSize() < 4 { panic("sio: NonceSize() of cipher is too small") } if bufSize > MaxBufSize { panic("sio: bufSize is too large") } if bufSize <= 0 { panic("sio: bufSize is too small") } return &Stream{ cipher: cipher, bufSize: bufSize, } } // A Stream encrypts or decrypts continuous byte streams. type Stream struct { cipher cipher.AEAD bufSize int } // NonceSize returns the size of the unique nonce that must be // provided when encrypting or decrypting a data stream. func (s *Stream) NonceSize() int { return s.cipher.NonceSize() - 4 } // Overhead returns the overhead added when encrypting a // data stream. For a plaintext stream of a non-negative // size, the size of an encrypted data stream will be: // // encSize = size + stream.Overhead(size) // 0 <= size <= (2³² - 1) * bufSize // 0 = stream.Overhead(size) // size > (2³² - 1) * bufSize // -1 = stream.Overhead(size) // size < 0 // // In general, the size of an encrypted data stream is // always greater than the size of the corresponding // plaintext stream. If size is too large (i.e. // greater than (2³² - 1) * bufSize) then Overhead // returns 0. If size is negative Overhead returns -1. func (s *Stream) Overhead(size int64) int64 { if size < 0 { return -1 } bufSize := int64(s.bufSize) if size > (bufSize * math.MaxUint32) { return 0 } overhead := int64(s.cipher.Overhead()) if size == 0 { return overhead } t := size / bufSize if r := size % bufSize; r > 0 { return (t * overhead) + overhead } return t * overhead } // EncryptWriter returns a new EncWriter that wraps w and // encrypts and authenticates everything before writing // it to w. // // The nonce must be Stream.NonceSize() bytes long and unique // for the same key. The same nonce must be provided when // decrypting the data stream. // // The associatedData is only authenticated but not encrypted // and not written to w. Instead, the same associatedData must // be provided when decrypting the data stream again. It is // safe to set: // associatedData = nil func (s *Stream) EncryptWriter(w io.Writer, nonce, associatedData []byte) *EncWriter { if len(nonce) != s.NonceSize() { panic("sio: nonce has invalid length") } ew := &EncWriter{ w: w, cipher: s.cipher, bufSize: s.bufSize, nonce: make([]byte, s.cipher.NonceSize()), associatedData: make([]byte, 1+s.cipher.Overhead()), buffer: make([]byte, s.bufSize+s.cipher.Overhead()), } copy(ew.nonce, nonce) nextNonce, _ := ew.nextNonce() ew.associatedData[0] = 0x00 ew.cipher.Seal(ew.associatedData[1:1], nextNonce, nil, associatedData) return ew } // DecryptWriter returns a new DecWriter that wraps w and // decrypts and verifies everything before writing // it to w. // // The nonce must be Stream.NonceSize() bytes long and // must match the value used when encrypting the data stream. // // The associatedData must match the value used when encrypting // the data stream. func (s *Stream) DecryptWriter(w io.Writer, nonce, associatedData []byte) *DecWriter { if len(nonce) != s.NonceSize() { panic("sio: nonce has invalid length") } dw := &DecWriter{ w: w, cipher: s.cipher, bufSize: s.bufSize, nonce: make([]byte, s.cipher.NonceSize()), associatedData: make([]byte, 1+s.cipher.Overhead()), buffer: make([]byte, s.bufSize+s.cipher.Overhead(), 1+s.bufSize+s.cipher.Overhead()), } copy(dw.nonce, nonce) nextNonce, _ := dw.nextNonce() dw.associatedData[0] = 0x00 dw.cipher.Seal(dw.associatedData[1:1], nextNonce, nil, associatedData) return dw } // EncryptReader returns a new EncReader that wraps r and // encrypts and authenticates it reads from r. // // The nonce must be Stream.NonceSize() bytes long and unique // for the same key. The same nonce must be provided when // decrypting the data stream. // // The associatedData is only authenticated but not encrypted // and not written to w. Instead, the same associatedData must // be provided when decrypting the data stream again. It is // safe to set: // associatedData = nil func (s *Stream) EncryptReader(r io.Reader, nonce, associatedData []byte) *EncReader { if len(nonce) != s.NonceSize() { panic("sio: nonce has invalid length") } er := &EncReader{ r: r, cipher: s.cipher, bufSize: s.bufSize, nonce: make([]byte, s.cipher.NonceSize()), associatedData: make([]byte, 1+s.cipher.Overhead()), buffer: make([]byte, 1+s.bufSize+s.cipher.Overhead()), firstRead: true, } copy(er.nonce, nonce) er.associatedData[0] = 0x00 binary.LittleEndian.PutUint32(er.nonce[er.cipher.NonceSize()-4:], er.seqNum) er.cipher.Seal(er.associatedData[1:1], er.nonce, nil, associatedData) er.seqNum = 1 return er } // DecryptReader returns a new DecReader that wraps r and // decrypts and verifies everything it reads from r. // // The nonce must be Stream.NonceSize() bytes long and // must match the value used when encrypting the data stream. // // The associatedData must match the value used when encrypting // the data stream. func (s *Stream) DecryptReader(r io.Reader, nonce, associatedData []byte) *DecReader { if len(nonce) != s.NonceSize() { panic("sio: nonce has invalid length") } dr := &DecReader{ r: r, cipher: s.cipher, bufSize: s.bufSize, nonce: make([]byte, s.cipher.NonceSize()), associatedData: make([]byte, 1+s.cipher.Overhead()), buffer: make([]byte, 1+s.bufSize+s.cipher.Overhead()), firstRead: true, } copy(dr.nonce, nonce) dr.associatedData[0] = 0x00 binary.LittleEndian.PutUint32(dr.nonce[dr.cipher.NonceSize()-4:], dr.seqNum) dr.cipher.Seal(dr.associatedData[1:1], dr.nonce, nil, associatedData) dr.seqNum = 1 return dr } // DecryptReaderAt returns a new DecReaderAt that wraps r and // decrypts and verifies everything it reads from r. // // The nonce must be Stream.NonceSize() bytes long and // must match the value used when encrypting the data stream. // // The associatedData must match the value used when encrypting // the data stream. func (s *Stream) DecryptReaderAt(r io.ReaderAt, nonce, associatedData []byte) *DecReaderAt { if len(nonce) != s.NonceSize() { panic("sio: nonce has invalid length") } dr := &DecReaderAt{ r: r, cipher: s.cipher, bufSize: s.bufSize, nonce: make([]byte, s.cipher.NonceSize()), associatedData: make([]byte, 1+s.cipher.Overhead()), } copy(dr.nonce, nonce) dr.associatedData[0] = 0x00 binary.LittleEndian.PutUint32(dr.nonce[s.NonceSize():], 0) dr.cipher.Seal(dr.associatedData[1:1], dr.nonce, nil, associatedData) dr.bufPool = sync.Pool{ New: func() interface{} { b := make([]byte, 1+dr.bufSize+dr.cipher.Overhead()) return &b }, } return dr } // writeTo writes p to w. It returns the first error that occurs during // writing, if any. If w violates the io.Writer contract and returns less than // len(p) bytes but no error then writeTo returns io.ErrShortWrite. func writeTo(w io.Writer, p []byte) (int, error) { n, err := w.Write(p) if err != nil { return n, err } if n != len(p) { return n, io.ErrShortWrite } return n, nil } // readFrom reads len(p) bytes from r into p. It returns the first error that // occurs during reading, if any. If the returned n < len(p) than the returned // error is not nil. func readFrom(r io.Reader, p []byte) (n int, err error) { for n < len(p) && err == nil { var nn int nn, err = r.Read(p[n:]) n += nn } if err == io.EOF && n == len(p) { err = nil } return n, err } golang-github-secure-io-sio-go-0.3.1/sio_test.go000066400000000000000000000054561433374623100215260ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( mrand "math/rand" ) type TestVector struct { Algorithm Algorithm BufSize int Key []byte Nonce []byte AssociatedData []byte Plaintext []byte Ciphertext []byte } var TestVectors []TestVector = loadTestVectors("./test_vectors.json") type SimpleTest struct { Algorithm Algorithm BufSize int Key []byte Nonce []byte AssociatedData []byte Plaintext []byte } var SimpleTests = []SimpleTest{ SimpleTest{ // 0 Algorithm: AES_128_GCM, BufSize: BufSize, Key: make([]byte, 128/8), Nonce: make([]byte, 64/8), AssociatedData: nil, Plaintext: randomN(1 << 20), }, SimpleTest{ // 1 Algorithm: AES_128_GCM, BufSize: 1 + mrand.Intn(2*BufSize), // add 1 to ensure BufSize is not 0. Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: randomN(1 << 20), }, SimpleTest{ // 2 Algorithm: AES_128_GCM, BufSize: BufSize, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: nil, }, SimpleTest{ // 3 Algorithm: AES_128_GCM, BufSize: BufSize, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: random(1), }, SimpleTest{ // 4 Algorithm: AES_128_GCM, BufSize: BufSize, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: random(BufSize - 1), }, SimpleTest{ // 5 Algorithm: AES_128_GCM, BufSize: BufSize, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: random(BufSize), }, SimpleTest{ // 6 Algorithm: AES_128_GCM, BufSize: BufSize, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: random(BufSize + 1), }, SimpleTest{ // 7 Algorithm: AES_128_GCM, BufSize: 1, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: []byte{}, }, SimpleTest{ // 8 Algorithm: AES_128_GCM, BufSize: 1, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(256), Plaintext: randomN(1 << 20), }, SimpleTest{ // 9 Algorithm: AES_128_GCM, BufSize: 2*BufSize + 1, Key: random(128 / 8), Nonce: random(64 / 8), AssociatedData: randomN(1 << 20), Plaintext: randomN(1 << 20), }, } golang-github-secure-io-sio-go-0.3.1/sioutil/000077500000000000000000000000001433374623100210245ustar00rootroot00000000000000golang-github-secure-io-sio-go-0.3.1/sioutil/aes_generic.go000066400000000000000000000004711433374623100236210ustar00rootroot00000000000000// Copyright (c) 2020 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. // +build !go1.14 !ppc64le package sioutil // An asm implementation of AES-GCM for ppc64le is not available // before Go 1.14. const ppcHasAES = false golang-github-secure-io-sio-go-0.3.1/sioutil/aes_ppc64le.go000066400000000000000000000004611433374623100234610ustar00rootroot00000000000000// Copyright (c) 2020 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. // +build go1.14,ppc64le package sioutil // An asm implementation of AES-GCM for ppc64le is available // since Go 1.14. const ppcHasAES = true golang-github-secure-io-sio-go-0.3.1/sioutil/sio.go000066400000000000000000000045761433374623100221610ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. // Package sioutil implements some I/O utility functions. package sioutil import ( "crypto/rand" "io" "golang.org/x/sys/cpu" ) type nopCloser struct { io.Writer } func (nopCloser) Close() error { return nil } // NopCloser returns a WriteCloser that wraps w // and implements Close as a no-op. func NopCloser(w io.Writer) io.WriteCloser { return nopCloser{w} } // NativeAES returns true when the executing CPU // provides AES-GCM hardware instructions and // an optimized assembler implementation is // available. // // It is strongly recommended to only use AES-GCM // when NativeAES() returns true. Otherwise, the // AES-GCM implementation may be vulnerable to // timing attacks. // See: https://golang.org/pkg/crypto/aes func NativeAES() bool { if cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ { return true } if cpu.ARM64.HasAES { return true } // Go 1.14 introduces an AES-GCM asm implementation // for PPC64le. Therefore, we have to use build tags // to determine whether we're compiling for PPC64 and // use Go 1.14 (or newer). // TODO(aead): Once we drop Go 1.13 support // (bump go version in go.mod) we can remove // pcc64-related build tags again. if ppcHasAES { return true } // On s390x, aes.NewCipher(...) returns a type // that provides AES asm implementations only // if all (CBC, CTR and GCM) AES hardware // instructions are available. // See: https://golang.org/src/crypto/aes/cipher_s390x.go#L39 return cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasAESGCM || cpu.S390X.HasGHASH) } // Random returns n randomly generated bytes if // and only if err == nil. // // Random uses crypto/rand.Reader as cryptographically // secure random number generator (CSPRNG). func Random(n int) (b []byte, err error) { b = make([]byte, n) if _, err = io.ReadFull(rand.Reader, b); err != nil { return nil, err } return b, nil } // MustRandom returns n randomly generated bytes. // It panics if it fails to read n random bytes // from its entropy source. // // MustRandom uses crypto/rand.Reader as cryptographically // secure random number generator (CSPRNG). func MustRandom(n int) []byte { b, err := Random(n) if err != nil { panic("sioutil: out of entropy: " + err.Error()) } return b } golang-github-secure-io-sio-go-0.3.1/sioutil/sio_test.go000066400000000000000000000014201433374623100232010ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sioutil import "testing" func TestRandom(t *testing.T) { b, err := Random(0) if err != nil || len(b) != 0 { t.Fatalf("Failed to generate empty slice: %v - got %d - want %d", err, len(b), 0) } b, err = Random(16) if err != nil || len(b) != 16 { t.Fatalf("Failed to generate empty slice: %v - got %d - want %d", err, len(b), 16) } } func TestMustRandom(t *testing.T) { b := MustRandom(0) if len(b) != 0 { t.Fatalf("Failed to generate empty slice: got %d - want %d", len(b), 0) } b = MustRandom(16) if len(b) != 16 { t.Fatalf("Failed to generate random slice: got %d - want %d", len(b), 16) } } golang-github-secure-io-sio-go-0.3.1/test_vectors.json000066400000000000000000000201331433374623100227520ustar00rootroot00000000000000[ {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"00000000000000000000000000000000","Nonce":"0000000000000000","AssociatedData":"","Plaintext":"","Ciphertext":"e0247a864e783cecb1f7996c13cd3f64"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"10000000000000000000000000000000","Nonce":"0000000000000000","AssociatedData":"","Plaintext":"","Ciphertext":"0ea5336db7013554c07aa43b369e5655"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"10000000000000000000000000000000","Nonce":"1000000000000000","AssociatedData":"","Plaintext":"","Ciphertext":"a52763497d2e64ee49bf4c72dc6881e7"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"10000000000000000000000000000000","Nonce":"1000000000000000","AssociatedData":"00","Plaintext":"","Ciphertext":"2d9ede532b1f5f034e22375d816f76c2"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"10000000000000000000000000000000","Nonce":"1000000000000000","AssociatedData":"00","Plaintext":"00","Ciphertext":"61ed193d12a2adf6f1c41f31de4ead6c5c"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"20000000000000000000000000000000","Nonce":"1000000000000000","AssociatedData":"00","Plaintext":"00","Ciphertext":"4b6e93acd3e4602b9c1ad49da985d1f872"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"00000000000000000000000000000000","Nonce":"0000000000000000","AssociatedData":"0000000000000000","Plaintext":"0000000000000000","Ciphertext":"4661d681f92680d7a307c7a036700edde4697a1f3ba7e01b"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"00000000000000000000000000000001","Nonce":"2000000000000000","AssociatedData":"1000000000000000","Plaintext":"00000000000000000000000000000000","Ciphertext":"8501dcfa461a0860ef9933655b1e59a9e7db1ed363b6ff80f219ca986dca9ce2"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"0a7a6d49e4ad042f1e1ffa168849d651","Nonce":"4a9ba500be169ab3","AssociatedData":"d40703848d887a01664c30734fa370581c5f8f6d0ea7bfdd","Plaintext":"424d6d60d7e08b3b41e968fc4557b93c","Ciphertext":"aa4b4ca42d587b98d0d5f009c0efbc4fcaaefdc1c79a3cfe07801ed8c708d259"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"10000000000000000000000000000000","Nonce":"0000000000000002","AssociatedData":"","Plaintext":"0000000000000000000000000000000000","Ciphertext":"691deb7b9ccbee91f48d12c7ad5fe3cd4e1e5e7d394f06b91228ec9a5dd2456ac7de9502a0b4010fb1ee1db94e940458fb"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"20000000000000000000000000000000","Nonce":"2000000000000000","AssociatedData":"00","Plaintext":"0000000000000000000000000000000000000000000000000000000000000000","Ciphertext":"f58d8fef7ac7782a4361a79c07f5e6c4672d8a45e6cb9ef67cb7177897358deb8d338d313d44f78e0b68a3723d7ca0fe5f62aa8af0a72e508f5d5f8491ecedb2"}, {"Algorithm":"AES-128-GCM","BufSize":16,"Key":"000102030405060708090a0b0c0d0e0f","Nonce":"0001020304050607","AssociatedData":"0102030405060708090a0b0c0d0e0f10","Plaintext":"0000000000000000000000000000000000000000000000000000000000000000","Ciphertext":"beffe57efbdadb693b5545d58454ba746c5362c6f37b65da457d85bfe6af8972cfea267b03f14aedf25e7501e88c0d79f5440d0ac49e5c4fdaab4dfac9aec4d3"}, {"Algorithm":"AES-128-GCM","BufSize":17,"Key":"67fc3ffd5353a432a6cbc982007606c8","Nonce":"6958a70a975e32c6","AssociatedData":"","Plaintext":"","Ciphertext":"076243570112d32362c1d9e4ab4096ca"}, {"Algorithm":"AES-128-GCM","BufSize":17,"Key":"31dd758d120fd5d30063921a21f723be","Nonce":"d3794fe387018e32","AssociatedData":"1d838b6e861ea7576de110e08818de82a27337a99db7b95aa95c3347971ad4cfa0ef0960d6645f5a64f88c7d","Plaintext":"d0ae0d85e93ed6be853ded2031643116a03215b4fda224d2986d7233039aa47e91b686f4","Ciphertext":"a6fe911b94e0b25a25bf6da71f28ad6a6b6920be01589e9194226f77c164606545d5cbdb76c99415b468c54517f5a8bc8dc8ac13b1b4285fe36f37f7884d5e4bfb32ecef56b4026bf02bfb1d4fce222e01ef9db9"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"0000000000000000000000000000000000000000000000000000000000000000","Nonce":"0000000000000000000000000000000000000000","AssociatedData":"","Plaintext":"","Ciphertext":"791a76dc50e295f58339eae57a4eed7c"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"1000000000000000000000000000000000000000000000000000000000000000","Nonce":"0000000000000000000000000000000000000000","AssociatedData":"","Plaintext":"","Ciphertext":"512c11040465f94bd75b838e2880a600"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"1000000000000000000000000000000000000000000000000000000000000000","Nonce":"1000000000000000000000000000000000000000","AssociatedData":"","Plaintext":"","Ciphertext":"f30791157c8409fda08596d32bd4e9a6"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"1000000000000000000000000000000000000000000000000000000000000000","Nonce":"1000000000000000000000000000000000000000","AssociatedData":"00","Plaintext":"","Ciphertext":"bd4ca84c8ce9d7e9d8e60d525f052f83"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"1000000000000000000000000000000000000000000000000000000000000000","Nonce":"1000000000000000000000000000000000000000","AssociatedData":"00","Plaintext":"00","Ciphertext":"1be804158fa893bd318326f4d43c87e62e"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"2000000000000000000000000000000000000000000000000000000000000000","Nonce":"1000000000000000000000000000000000000000","AssociatedData":"00","Plaintext":"00","Ciphertext":"5aff8d11efe71546a702cf20626d5e01e0"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"0000000000000000000000000000000000000000000000000000000000000000","Nonce":"0000000000000000000000000000000000000000","AssociatedData":"0000000000000000","Plaintext":"0000000000000000","Ciphertext":"6ea301b390d09b85f94c5f0eb37627733861683aa875f156"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"0000000000000000000000000000000000000000000000000000000000000001","Nonce":"2000000000000000000000000000000000000000","AssociatedData":"1000000000000000","Plaintext":"00000000000000000000000000000000","Ciphertext":"7c668b93e845151243293d985abd8c129fd67436d16bf59c2cec74c0be444062"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"86a2b5add1b1bc0c9abaa5e863e5faed86a83b71abc9dca272558e35ecb1c8e2","Nonce":"d342ae5821a42b1ccec57b851188cb0e289a757a","AssociatedData":"d40703848d887a01664c30734fa370581c5f8f6d0ea7bfdd","Plaintext":"424d6d60d7e08b3b41e968fc4557b93c","Ciphertext":"656ab521841b0db4a5b9ff424b2ca12051d6758f158c0eafdad0454d0524de18"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"1000000000000000000000000000000000000000000000000000000000000000","Nonce":"0000000000000000000000000000000000000002","AssociatedData":"","Plaintext":"0000000000000000000000000000000000","Ciphertext":"4d8a54220e4273e8d09b17cca630cec000b053cdab31b094b5f45bfd8522b09f6800419d02a4401960afb4e61e993eae43"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"2000000000000000000000000000000000000000000000000000000000000000","Nonce":"2000000000000000000000000000000000000000","AssociatedData":"00","Plaintext":"00000000000000000000000000000000000000000000000000000000","Ciphertext":"06fcbcc30e25e1bc0351343a3ddad0aeaa4ddd106ab64037dea061f44c109b12b43c7a4bd4da0ae8be8e33f1b0f627f2b7a3078c2cf59d4da6974860"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":16,"Key":"000102030405060708090a0b0c0d0e0ff0e0d0c0b0a090807060504030201000","Nonce":"000102030405060708090a0b0c0d0e0f10203040","AssociatedData":"0102030405060708090a0b0c0d0e0f10","Plaintext":"00000000000000000000000000000000000000000000000000","Ciphertext":"37ca703e35ddf34ab96e0cf6c8639445c42214f872b2e22bd23e6ad4eb3cc698552173f6c01c400b4a143de94ad10d61e305fcee57a35d0853"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":17,"Key":"c211150d9ecc4684d1c727b1a26157a9fa75da781c76a5bad2a6808ec1fd76bf","Nonce":"0c1a8df057dd7f18312819450ecd2e29faf69a8b","AssociatedData":"","Plaintext":"","Ciphertext":"55a33243cd07ebf0a5351b788bc9a5e6"}, {"Algorithm":"XChaCha20-Poly1305","BufSize":17,"Key":"d3b90c824d5b012a65be54db6367ba9932d72464e0fe6610135b769365d892e3","Nonce":"e9052f0751c616d1a2f82983f3935d6970357240","AssociatedData":"1d838b6e861ea7576de110e08818de82a27337a99db7b95aa95c3347971ad4cfa0ef0960d6645f5a64f88c7d","Plaintext":"d0ae0d85e93ed6be853ded2031643116a03215b4fda224d2986d7233039aa47e91b686f4","Ciphertext":"1a16520c62486c0a5619ed3653f682471943c5c4bb33818afd4c48af0a8e659e8c97fa927718266c25d0060f053e0eff3ce962d4d9f399494f93ac594e3ab9c13753098a73678c01f82625a103cf56eed15babf4"} ] golang-github-secure-io-sio-go-0.3.1/writer.go000066400000000000000000000322001433374623100211740ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( "crypto/cipher" "encoding/binary" "io" "math" ) // An EncWriter encrypts and authenticates everything it // writes to an underlying io.Writer. // // An EncWriter must always be closed successfully. // Otherwise, the encrypted data written to the underlying // io.Writer would be incomplete. Also, no more data must // be written to a closed EncWriter. // // Closing an EncWriter also closes the underlying io.Writer // if it implements io.Closer. type EncWriter struct { w io.Writer cipher cipher.AEAD bufSize int seqNum uint32 nonce []byte associatedData []byte buffer []byte offset int err error closed bool } // Write behaves as specified by the io.Writer interface. // In particular, Write encrypts len(p) bytes from p and // writes the encrypted bytes to the underlying data stream. It // returns the number of bytes written from p (0 <= n <= len(p)) // and any error encountered that caused the write to stop early. // // When Write cannot encrypt any more bytes securely it // returns ErrExceeded. However, the EncWriter must still // be closed to complete the encryption and flush any // remaining data to the underlying data stream. // // Write must not be called once the EncWriter has // been closed. func (w *EncWriter) Write(p []byte) (n int, err error) { if w.closed { panic("sio: EncWriter is closed") } if w.err != nil { return 0, w.err } if w.offset > 0 { n = copy(w.buffer[w.offset:w.bufSize], p) if n == len(p) { w.offset += n return n, nil } p = p[n:] w.offset = 0 nonce, err := w.nextNonce() if err != nil { w.err = err return n, w.err } ciphertext := w.cipher.Seal(w.buffer[:0], nonce, w.buffer[:w.bufSize], w.associatedData) if _, err = writeTo(w.w, ciphertext); err != nil { w.err = err return n, w.err } } for len(p) > w.bufSize { nonce, err := w.nextNonce() if err != nil { w.err = err return n, w.err } ciphertext := w.cipher.Seal(w.buffer[:0], nonce, p[:w.bufSize], w.associatedData) if _, err = writeTo(w.w, ciphertext); err != nil { w.err = err return n, w.err } p = p[w.bufSize:] n += w.bufSize } w.offset = copy(w.buffer, p) n += w.offset return n, nil } // WriteByte behaves as specified by the io.ByteWriter interface. // In particular, WriteByte encrypts b and writes the encrypted // byte to the underlying data stream. // // When WriteByte cannot encrypt one more byte securely it // returns ErrExceeded. However, the EncWriter must still // be closed to complete the encryption and flush any // remaining data to the underlying data stream. // // WriteByte must not be called once the EncWriter has // been closed. func (w *EncWriter) WriteByte(b byte) error { if w.closed { panic("sio: EncWriter is closed") } if w.err != nil { return w.err } if w.offset < w.bufSize { w.buffer[w.offset] = b w.offset++ return nil } nonce, err := w.nextNonce() if err != nil { w.err = err return w.err } ciphertext := w.cipher.Seal(w.buffer[:0], nonce, w.buffer[:w.bufSize], w.associatedData) if _, err = writeTo(w.w, ciphertext); err != nil { w.err = err return w.err } w.buffer[0] = b w.offset = 1 return nil } // Close writes any remaining encrypted bytes to the // underlying io.Writer and returns any error // encountered. If the underlying io.Writer implements // Close it closes the underlying data stream as well. // // It safe to call close multiple times. func (w *EncWriter) Close() error { if w.err != nil && w.err != ErrExceeded { return w.err } if !w.closed { w.closed = true w.associatedData[0] = 0x80 binary.LittleEndian.PutUint32(w.nonce[w.cipher.NonceSize()-4:], w.seqNum) ciphertext := w.cipher.Seal(w.buffer[:0], w.nonce, w.buffer[:w.offset], w.associatedData) if _, w.err = writeTo(w.w, ciphertext); w.err != nil { return w.err } if c, ok := w.w.(io.Closer); ok { w.err = c.Close() return w.err } } return nil } // ReadFrom behaves as specified by the io.ReadFrom interface. // In particular, ReadFrom reads data from r until io.EOF or any // error occurs, encrypts the data and writes the encrypted data // to the underlying io.Writer. ReadFrom does not close the // EncWriter nor the underlying data stream. // // ReadFrom returns the number of bytes read and any error except // io.EOF. // // When ReadFrom cannot encrypt any more data securely it // returns ErrExceeded. However, the EncWriter must still // be closed to complete the encryption and flush any // remaining data to the underlying data stream. // // ReadFrom must not be called once the EncWriter has // been closed. func (w *EncWriter) ReadFrom(r io.Reader) (int64, error) { if w.closed { panic("sio: EncWriter is closed") } if w.err != nil { return 0, w.err } nn, err := readFrom(r, w.buffer[:w.bufSize+1]) if err == io.EOF { w.offset = nn return int64(nn), nil } if err != nil { w.err = err return int64(nn), err } carry := w.buffer[w.bufSize] nonce, err := w.nextNonce() if err != nil { w.err = err return int64(nn), w.err } ciphertext := w.cipher.Seal(w.buffer[:0], nonce, w.buffer[:w.bufSize], w.associatedData) if _, err = writeTo(w.w, ciphertext); err != nil { w.err = err return int64(nn), w.err } n := int64(nn) for { w.buffer[0] = carry nn, err = readFrom(r, w.buffer[1:1+w.bufSize]) n += int64(nn) if err == io.EOF { w.offset = 1 + nn return n, nil } if err != nil { w.err = err return n, w.err } carry = w.buffer[w.bufSize] nonce, err = w.nextNonce() if err != nil { w.err = err return n, w.err } ciphertext = w.cipher.Seal(w.buffer[:0], nonce, w.buffer[:w.bufSize], w.associatedData) if _, err = writeTo(w.w, ciphertext); err != nil { w.err = err return n, w.err } } } func (w *EncWriter) nextNonce() ([]byte, error) { if w.seqNum == math.MaxUint32 { return nil, ErrExceeded } binary.LittleEndian.PutUint32(w.nonce[w.cipher.NonceSize()-4:], w.seqNum) w.seqNum++ return w.nonce, nil } // A DecWriter decrypts and verifies everything it // writes to an underlying io.Writer. It never writes // invalid (i.e. not authentic) data to the underlying // data stream. // // A DecWriter must always be closed and the returned // error must be checked. Otherwise, the decrypted data // written to the underlying io.Writer would be incomplete. // Also, no more data must be written to a closed DecWriter. // // Closing a DecWriter also closes the underlying io.Writer // if it implements io.Closer. type DecWriter struct { w io.Writer cipher cipher.AEAD bufSize int seqNum uint32 nonce []byte associatedData []byte buffer []byte offset int err error closed bool } // Write behaves as specified by the io.Writer interface. // In particular, Write decrypts len(p) bytes from p and // writes the decrypted bytes to the underlying data stream. It // returns the number of bytes written from p (0 <= n <= len(p)) // and any error encountered that caused the write to stop early. // // When Write fails to decrypt some data it returns NotAuthentic. // This error indicates that the encrypted bytes have been // (maliciously) modified. // // When Write cannot decrypt any more bytes securely it // returns ErrExceeded. However, the DecWriter must still // be closed to complete the decryption and flush any // remaining data to the underlying data stream. // // Write must not be called once the DecWriter has // been closed. func (w *DecWriter) Write(p []byte) (n int, err error) { if w.closed { panic("sio: DecWriter is closed") } if w.err != nil { return 0, w.err } if w.offset > 0 { n = copy(w.buffer[w.offset:], p) if n == len(p) { w.offset += n return n, nil } p = p[n:] w.offset = 0 nonce, err := w.nextNonce() if err != nil { w.err = err return n, w.err } plaintext, err := w.cipher.Open(w.buffer[:0], nonce, w.buffer, w.associatedData) if err != nil { w.err = NotAuthentic return n, w.err } if _, err = writeTo(w.w, plaintext); err != nil { w.err = err return n, w.err } } ciphertextLen := w.bufSize + w.cipher.Overhead() for len(p) > ciphertextLen { nonce, err := w.nextNonce() if err != nil { w.err = err return n, w.err } plaintext, err := w.cipher.Open(w.buffer[:0], nonce, p[:ciphertextLen], w.associatedData) if err != nil { w.err = NotAuthentic return n, w.err } if _, err = writeTo(w.w, plaintext); err != nil { w.err = err return n, w.err } p = p[ciphertextLen:] n += ciphertextLen } w.offset = copy(w.buffer, p) n += w.offset return n, nil } // WriteByte behaves as specified by the io.ByteWriter interface. // In particular, WriteByte decrypts b and writes the decrypted // byte to the underlying data stream. // // When WriteByte fails to decrypt b it returns NotAuthentic. // This error indicates that some encrypted bytes have been // (maliciously) modified. // // When WriteByte cannot decrypt one more byte securely it // returns ErrExceeded. However, the DecWriter must still // be closed to complete the decryption and flush any // remaining data to the underlying data stream. // // WriteByte must not be called once the DecWriter has // been closed. func (w *DecWriter) WriteByte(b byte) error { if w.closed { panic("sio: DecWriter is closed") } if w.err != nil { return w.err } if w.offset < w.bufSize+w.cipher.Overhead() { w.buffer[w.offset] = b w.offset++ return nil } nonce, err := w.nextNonce() if err != nil { w.err = err return w.err } plaintext, err := w.cipher.Open(w.buffer[:0], nonce, w.buffer, w.associatedData) if err != nil { w.err = NotAuthentic return w.err } if _, err = writeTo(w.w, plaintext); err != nil { w.err = err return w.err } w.buffer[0] = b w.offset = 1 return nil } // Close writes any remaining decrypted bytes to the // underlying io.Writer and returns any error // encountered. If the underlying io.Writer implements // Close it closes the underlying data stream as well. // // It is important to check the returned error // since Close may return NotAuthentic indicating // that some remaining bytes are invalid encrypted // data. // // It safe to call close multiple times. func (w *DecWriter) Close() error { if w.err != nil && w.err != ErrExceeded { return w.err } if !w.closed { w.closed = true w.associatedData[0] = 0x80 binary.LittleEndian.PutUint32(w.nonce[w.cipher.NonceSize()-4:], w.seqNum) plaintext, err := w.cipher.Open(w.buffer[:0], w.nonce, w.buffer[:w.offset], w.associatedData) if err != nil { w.err = NotAuthentic return w.err } if _, w.err = writeTo(w.w, plaintext); w.err != nil { return w.err } if c, ok := w.w.(io.Closer); ok { w.err = c.Close() return w.err } } return nil } // ReadFrom behaves as specified by the io.ReadFrom interface. // In particular, ReadFrom reads data from r until io.EOF or any // error occurs, decrypts the data and writes the decrypted data // to the underlying io.Writer. ReadFrom does not close the // DecWriter nor the underlying data stream. // // ReadFrom returns the number of bytes read and any error except // io.EOF. When ReadFrom fails to decrypt some data it returns // NotAuthentic. This error indicates that some encrypted bytes // have been (maliciously) modified. // // When ReadFrom cannot decrypt any more data securely it // returns ErrExceeded. However, the DecWriter must still // be closed to complete the decryption and flush any // remaining data to the underlying data stream. // // ReadFrom must not be called once the DecWriter has // been closed. func (w *DecWriter) ReadFrom(r io.Reader) (int64, error) { if w.closed { panic("sio: DecWriter is closed") } if w.err != nil { return 0, w.err } ciphertextLen := w.bufSize + w.cipher.Overhead() buffer := w.buffer[:1+ciphertextLen] nn, err := readFrom(r, buffer[:1+ciphertextLen]) if err == io.EOF { w.offset = nn return int64(nn), nil } if err != nil { w.err = err return int64(nn), err } carry := buffer[ciphertextLen] nonce, err := w.nextNonce() if err != nil { w.err = err return int64(nn), w.err } plaintext, err := w.cipher.Open(buffer[:0], nonce, buffer[:ciphertextLen], w.associatedData) if err != nil { w.err = NotAuthentic return int64(nn), w.err } if _, err = writeTo(w.w, plaintext); err != nil { w.err = err return int64(nn), w.err } n := int64(nn) for { w.buffer[0] = carry nn, err = readFrom(r, buffer[1:1+ciphertextLen]) n += int64(nn) if err == io.EOF { w.offset = 1 + nn return n, nil } if err != nil { w.err = err return n, w.err } carry = buffer[ciphertextLen] nonce, err = w.nextNonce() if err != nil { w.err = err return n, w.err } plaintext, err = w.cipher.Open(buffer[:0], nonce, buffer[:ciphertextLen], w.associatedData) if err != nil { w.err = NotAuthentic return n, w.err } if _, err = writeTo(w.w, plaintext); err != nil { w.err = err return n, w.err } } } func (w *DecWriter) nextNonce() ([]byte, error) { if w.seqNum == math.MaxUint32 { return nil, ErrExceeded } binary.LittleEndian.PutUint32(w.nonce[w.cipher.NonceSize()-4:], w.seqNum) w.seqNum++ return w.nonce, nil } golang-github-secure-io-sio-go-0.3.1/writer_test.go000066400000000000000000000220001433374623100222300ustar00rootroot00000000000000// Copyright (c) 2019 Andreas Auernhammer. All rights reserved. // Use of this source code is governed by a license that can be // found in the LICENSE file. package sio import ( "bytes" "io" "io/ioutil" "testing" ) func TestVectorWrite(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range TestVectors { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } dw := stream.DecryptWriter(plaintext, test.Nonce, test.AssociatedData) if _, err = dw.Write(test.Ciphertext); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if err = dw.Close(); err != nil { t.Fatalf("Test: %d: Failed to close DecWriter: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } ew := stream.EncryptWriter(ciphertext, test.Nonce, test.AssociatedData) if _, err = ew.Write(test.Plaintext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if err = ew.Close(); err != nil { t.Fatalf("Test: %d: Failed to close EncWriter: %v", i, err) } if !bytes.Equal(ciphertext.Bytes(), test.Ciphertext) { t.Fatalf("Test %d: ciphertext does not match original plaintext", i) } } } func TestVectorWriteByte(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range TestVectors { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } dw := stream.DecryptWriter(plaintext, test.Nonce, test.AssociatedData) if err = copyBytes(dw, bytes.NewReader(test.Ciphertext)); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if err = dw.Close(); err != nil { t.Fatalf("Test: %d: Failed to close DecWriter: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } ew := stream.EncryptWriter(ciphertext, test.Nonce, test.AssociatedData) if err = copyBytes(ew, bytes.NewReader(test.Plaintext)); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if err = ew.Close(); err != nil { t.Fatalf("Test: %d: Failed to close EncWriter: %v", i, err) } if !bytes.Equal(ciphertext.Bytes(), test.Ciphertext) { t.Fatalf("Test %d: ciphertext does not match original plaintext", i) } } } func TestVectorReadFrom(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range TestVectors { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } dw := stream.DecryptWriter(plaintext, test.Nonce, test.AssociatedData) if _, err = dw.ReadFrom(bytes.NewReader(test.Ciphertext)); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if err = dw.Close(); err != nil { t.Fatalf("Test: %d: Failed to close DecWriter: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } ew := stream.EncryptWriter(ciphertext, test.Nonce, test.AssociatedData) if _, err = ew.ReadFrom(bytes.NewReader(test.Plaintext)); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if err = ew.Close(); err != nil { t.Fatalf("Test: %d: Failed to close EncWriter: %v", i, err) } if !bytes.Equal(ciphertext.Bytes(), test.Ciphertext) { t.Fatalf("Test %d: ciphertext does not match original plaintext", i) } } } func TestSimpleWrite(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range SimpleTests { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } ew := stream.EncryptWriter(ciphertext, test.Nonce, test.AssociatedData) if _, err = ew.Write(test.Plaintext); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if err = ew.Close(); err != nil { t.Fatalf("Test: %d: Failed to close EncWriter: %v", i, err) } dw := stream.DecryptWriter(plaintext, test.Nonce, test.AssociatedData) if _, err = dw.Write(ciphertext.Bytes()); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if err = dw.Close(); err != nil { t.Fatalf("Test: %d: Failed to close DecWriter: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleWriteByte(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range SimpleTests { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } ew := stream.EncryptWriter(ciphertext, test.Nonce, test.AssociatedData) if err = copyBytes(ew, bytes.NewReader(test.Plaintext)); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if err = ew.Close(); err != nil { t.Fatalf("Test: %d: Failed to close EncWriter: %v", i, err) } dw := stream.DecryptWriter(plaintext, test.Nonce, test.AssociatedData) if err = copyBytes(dw, ciphertext); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if err = dw.Close(); err != nil { t.Fatalf("Test: %d: Failed to close DecWriter: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestSimpleReadFrom(t *testing.T) { ciphertext := bytes.NewBuffer(nil) plaintext := bytes.NewBuffer(nil) for i, test := range SimpleTests { ciphertext.Reset() plaintext.Reset() stream, err := test.Algorithm.streamWithBufSize(test.Key, test.BufSize) if err != nil { t.Fatalf("Test %d: Failed to create new Stream: %v", i, err) } ew := stream.EncryptWriter(ciphertext, test.Nonce, test.AssociatedData) if _, err = ew.ReadFrom(bytes.NewReader(test.Plaintext)); err != nil { t.Fatalf("Test: %d: Failed to encrypt plaintext: %v", i, err) } if err = ew.Close(); err != nil { t.Fatalf("Test: %d: Failed to close EncWriter: %v", i, err) } dw := stream.DecryptWriter(plaintext, test.Nonce, test.AssociatedData) if _, err = dw.ReadFrom(ciphertext); err != nil { t.Fatalf("Test %d: Failed to decrypt ciphertext: %v", i, err) } if err = dw.Close(); err != nil { t.Fatalf("Test: %d: Failed to close DecWriter: %v", i, err) } if !bytes.Equal(plaintext.Bytes(), test.Plaintext) { t.Fatalf("Test %d: plaintext does not match original plaintext", i) } } } func TestWriteAfterClose(t *testing.T) { shouldPanicOnWrite := func(context string, w io.Writer, t *testing.T) { defer func() { if err := recover(); err == nil { t.Fatalf("%sWriter did not panic", context) } }() w.Write(nil) } s, err := AES_128_GCM.Stream(make([]byte, 16)) if err != nil { t.Fatalf("Failed to create new Stream: %v", err) } nonce := make([]byte, s.NonceSize()) dw := s.DecryptWriter(ioutil.Discard, nonce, nil) ew := s.EncryptWriter(dw, nonce, nil) if err := ew.Close(); err != nil { t.Fatalf("Close failed: %v", err) } shouldPanicOnWrite("Enc", ew, t) shouldPanicOnWrite("Dec", dw, t) } func TestWriteByteAfterClose(t *testing.T) { shouldPanicOnWrite := func(context string, w io.ByteWriter, t *testing.T) { defer func() { if err := recover(); err == nil { t.Fatalf("%sWriter did not panic", context) } }() w.WriteByte(0) } s, err := AES_128_GCM.Stream(make([]byte, 16)) if err != nil { t.Fatalf("Failed to create new Stream: %v", err) } nonce := make([]byte, s.NonceSize()) dw := s.DecryptWriter(ioutil.Discard, nonce, nil) ew := s.EncryptWriter(dw, nonce, nil) if err := ew.Close(); err != nil { t.Fatalf("Close failed: %v", err) } shouldPanicOnWrite("Enc", ew, t) shouldPanicOnWrite("Dec", dw, t) } func TestReadFromAfterClose(t *testing.T) { shouldPanicOnReadFrom := func(context string, w io.ReaderFrom, t *testing.T) { defer func() { if err := recover(); err == nil { t.Fatalf("%sWriter did not panic", context) } }() w.ReadFrom(bytes.NewReader(nil)) } s, err := AES_128_GCM.Stream(make([]byte, 16)) if err != nil { t.Fatalf("Failed to create new Stream: %v", err) } nonce := make([]byte, s.NonceSize()) dw := s.DecryptWriter(ioutil.Discard, nonce, nil) ew := s.EncryptWriter(dw, nonce, nil) if err := ew.Close(); err != nil { t.Fatalf("Close failed: %v", err) } shouldPanicOnReadFrom("Enc", ew, t) shouldPanicOnReadFrom("Dec", dw, t) }