feat: encrypt & decrypt tested (simple)
This commit is contained in:
parent
dd0524d680
commit
7b29d8f6cc
54
privacy/argon2.go
Normal file
54
privacy/argon2.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package privacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type argon2Params struct {
|
||||||
|
Time uint32
|
||||||
|
Memory uint32
|
||||||
|
Threads uint8
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidParameter = errors.New("invalid parameter")
|
||||||
|
|
||||||
|
const argon2KeyGenName = "argon2"
|
||||||
|
|
||||||
|
func (a argon2Params) GenerateKey(password, salt []byte) []byte {
|
||||||
|
return argon2.IDKey(password, salt, a.Time, a.Memory, a.Threads, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArgon2() KeyGen {
|
||||||
|
return argon2Params{
|
||||||
|
Time: 1,
|
||||||
|
Memory: 64 * 1024,
|
||||||
|
Threads: 4,
|
||||||
|
Name: argon2KeyGenName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArgon2WithParams(time, memory uint32, threads uint8) (k KeyGen, err error) {
|
||||||
|
if time == 0 || memory == 0 || threads == 0 {
|
||||||
|
return nil, ErrInvalidParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
return argon2Params{
|
||||||
|
Time: time,
|
||||||
|
Memory: memory,
|
||||||
|
Threads: threads,
|
||||||
|
Name: argon2KeyGenName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a argon2Params) MarshalJSON() ([]byte, error) {
|
||||||
|
m := map[string]any{
|
||||||
|
"name": a.Name,
|
||||||
|
"memory": a.Memory,
|
||||||
|
"threads": a.Threads,
|
||||||
|
"time": a.Time,
|
||||||
|
}
|
||||||
|
return json.Marshal(&m)
|
||||||
|
}
|
||||||
169
privacy/argon2_test.go
Normal file
169
privacy/argon2_test.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package privacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewArgon2(t *testing.T) {
|
||||||
|
k := NewArgon2()
|
||||||
|
if _, ok := k.(argon2Params); !ok {
|
||||||
|
t.Fatal("unexpected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewArgon2WithParams(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
time uint32
|
||||||
|
memory uint32
|
||||||
|
threads uint8
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
args: args{
|
||||||
|
time: 1,
|
||||||
|
memory: 4 * 1024,
|
||||||
|
threads: 4,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative: zero time",
|
||||||
|
args: args{
|
||||||
|
time: 0,
|
||||||
|
memory: 1 * 1024,
|
||||||
|
threads: 4,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative: zero memory",
|
||||||
|
args: args{
|
||||||
|
time: 1,
|
||||||
|
memory: 0,
|
||||||
|
threads: 4,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative: zero threads",
|
||||||
|
args: args{
|
||||||
|
time: 1,
|
||||||
|
memory: 1 * 1024,
|
||||||
|
threads: 0,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := NewArgon2WithParams(tt.args.time, tt.args.memory, tt.args.threads)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("NewArgon2WithParams() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArgon2Params_MarshalJSON(t *testing.T) {
|
||||||
|
var (
|
||||||
|
b []byte
|
||||||
|
err error
|
||||||
|
str string
|
||||||
|
ok bool
|
||||||
|
a any
|
||||||
|
)
|
||||||
|
|
||||||
|
if b, err = NewArgon2().MarshalJSON(); err != nil {
|
||||||
|
t.Fatal("unexpected", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string]any)
|
||||||
|
if err = json.Unmarshal(b, &m); err != nil {
|
||||||
|
t.Fatal("unexpected", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, ok = m["name"]; !ok {
|
||||||
|
t.Fatal("unexpected: no field name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str, ok = a.(string); !ok {
|
||||||
|
t.Fatal("unexpected: name field is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str != argon2KeyGenName {
|
||||||
|
t.Fatal("unexpected: value of the name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_argon2Params_GenerateKey(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Time uint32
|
||||||
|
Memory uint32
|
||||||
|
Threads uint8
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
password []byte
|
||||||
|
salt []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
prepKG KeyGen
|
||||||
|
prepErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
passphrase := "some passphrase"
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
if _, prepErr = rand.Read(salt); prepErr != nil {
|
||||||
|
t.Fatal("test preparation failure:", prepErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prepKG, prepErr = NewArgon2WithParams(1, 1*1024, 2); prepErr != nil {
|
||||||
|
t.Fatal("test preparation failure:", prepErr)
|
||||||
|
}
|
||||||
|
prepBytes := prepKG.GenerateKey([]byte(passphrase), salt)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
fields: fields{
|
||||||
|
Time: 1,
|
||||||
|
Memory: 1 * 1024,
|
||||||
|
Threads: 2,
|
||||||
|
Name: argon2KeyGenName,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
password: []byte(passphrase),
|
||||||
|
salt: salt,
|
||||||
|
},
|
||||||
|
want: prepBytes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a := argon2Params{
|
||||||
|
Time: tt.fields.Time,
|
||||||
|
Memory: tt.fields.Memory,
|
||||||
|
Threads: tt.fields.Threads,
|
||||||
|
Name: tt.fields.Name,
|
||||||
|
}
|
||||||
|
if got := a.GenerateKey(tt.args.password, tt.args.salt); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("GenerateKey() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,69 +0,0 @@
|
|||||||
package privacy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type chacha20 struct {
|
|
||||||
aead cipher.AEAD
|
|
||||||
privacy *Privacy
|
|
||||||
block []byte
|
|
||||||
blank bool
|
|
||||||
nonce []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newChaCha20(key []byte, p *Privacy) (c *chacha20, err error) {
|
|
||||||
c = &chacha20{
|
|
||||||
privacy: p,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.aead, err = chacha20poly1305.NewX(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.nonce = make([]byte, c.aead.NonceSize())
|
|
||||||
c.block = make([]byte, 0, c.aead.NonceSize()+int(p.GetSegmentSize())+c.aead.Overhead())
|
|
||||||
c.blank = true
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chacha20) write(wc io.WriteCloser, inwr []byte) error {
|
|
||||||
var err error
|
|
||||||
if c.blank {
|
|
||||||
_, err = rand.Read(c.nonce)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(inwr) > 0 {
|
|
||||||
c.blank = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.block)+len(inwr) < int(c.privacy.GetSegmentSize()) {
|
|
||||||
start := len(c.block)
|
|
||||||
c.block = c.block[:start+len(inwr)]
|
|
||||||
copy(c.block[start:], inwr)
|
|
||||||
return nil
|
|
||||||
} else if len(c.block)+len(inwr) == int(c.privacy.GetSegmentSize()) {
|
|
||||||
start := len(c.block)
|
|
||||||
c.block = c.block[:start+len(inwr)]
|
|
||||||
copy(c.block[start:], inwr)
|
|
||||||
|
|
||||||
lenSlot := make([]byte, 4)
|
|
||||||
blockLen := uint32(len(c.block) + c.aead.Overhead())
|
|
||||||
binary.BigEndian.PutUint32(lenSlot, blockLen)
|
|
||||||
|
|
||||||
//TODO: fix and resume from here, it was a stopping point
|
|
||||||
//result := c.aead.Seal(c.block[:0], c.nonce, c.block, lenSlot)
|
|
||||||
c.aead.Seal(c.block[:0], c.nonce, c.block, lenSlot)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -4,34 +4,42 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CipherMethodType byte
|
type CipherMethodType byte
|
||||||
|
|
||||||
|
type KeyGen interface {
|
||||||
|
json.Marshaler
|
||||||
|
GenerateKey(password, salt []byte) []byte
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
segmentSizeBytesLen int = 4
|
segmentSizeBytesLen int = 4
|
||||||
|
|
||||||
Uninitialised CipherMethodType = 0
|
Uninitialised CipherMethodType = 0
|
||||||
XChaCha20Simple CipherMethodType = 1
|
XChaCha20Simple CipherMethodType = 1
|
||||||
|
AES256GCMSimple CipherMethodType = 2
|
||||||
|
|
||||||
DefaultCipherMethod CipherMethodType = XChaCha20Simple
|
DefaultCipherMethod = XChaCha20Simple
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidSaltLen = errors.New("invalid salt length")
|
ErrInvalidSaltLen = errors.New("invalid salt length")
|
||||||
|
ErrUninitialisedSalt = errors.New("uninitialised salt")
|
||||||
ErrUninitialisedMethod = errors.New("cipher method type uninitialised")
|
ErrUninitialisedMethod = errors.New("cipher method type uninitialised")
|
||||||
ErrInvalidCipherMethod = errors.New("invalid cipher method type")
|
ErrInvalidCipherMethod = errors.New("invalid cipher method type")
|
||||||
ErrCannotReadMagicBytes = errors.New("cannot read magic bytes")
|
//ErrCannotReadMagicBytes = errors.New("cannot read magic bytes") //no usage for now
|
||||||
ErrInvalidReadFlow = errors.New("func ReadMagic should be called before calling Read")
|
ErrInvalidReadFlow = errors.New("func ReadMagic should be called before calling Read")
|
||||||
ErrInvalidKeyState = errors.New("func GenerateKey should be called first")
|
ErrInvalidKeyState = errors.New("func GenerateKey should be called first")
|
||||||
ErrInvalidSegmentLength = errors.New("segment length is too long")
|
ErrInvalidSegmentLength = errors.New("segment length is too long")
|
||||||
|
segmentLenBytes = make([]byte, segmentSizeBytesLen)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
@ -39,6 +47,7 @@ type Reader struct {
|
|||||||
reader io.Reader
|
reader io.Reader
|
||||||
buf []byte
|
buf []byte
|
||||||
bufSlice []byte
|
bufSlice []byte
|
||||||
|
isEOF bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriteCloser struct {
|
type WriteCloser struct {
|
||||||
@ -54,24 +63,39 @@ type Privacy struct {
|
|||||||
segmentSize uint32
|
segmentSize uint32
|
||||||
cmType CipherMethodType
|
cmType CipherMethodType
|
||||||
aead cipher.AEAD
|
aead cipher.AEAD
|
||||||
|
keygen KeyGen
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPrivacy() *Privacy {
|
func newPrivacy(k KeyGen) *Privacy {
|
||||||
return &Privacy{
|
return &Privacy{
|
||||||
segmentSize: 64 * 1024 * 1024,
|
segmentSize: 64 * 1024 * 1024,
|
||||||
cmType: Uninitialised,
|
cmType: Uninitialised,
|
||||||
|
keygen: k,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPrivacyReader(reader io.Reader) *Reader {
|
func NewPrivacyReader(reader io.Reader) *Reader {
|
||||||
|
return NewPrivacyReaderWithKeyGen(reader, NewArgon2())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivacyReaderWithKeyGen(reader io.Reader, keygen KeyGen) *Reader {
|
||||||
return &Reader{
|
return &Reader{
|
||||||
Privacy: newPrivacy(),
|
Privacy: newPrivacy(keygen),
|
||||||
reader: reader,
|
reader: reader,
|
||||||
|
isEOF: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPrivacyWriterCloserDefault(wc io.WriteCloser) *WriteCloser {
|
||||||
|
return NewPrivacyWriteCloser(wc, DefaultCipherMethod)
|
||||||
|
}
|
||||||
|
|
||||||
func NewPrivacyWriteCloser(wc io.WriteCloser, cmType CipherMethodType) *WriteCloser {
|
func NewPrivacyWriteCloser(wc io.WriteCloser, cmType CipherMethodType) *WriteCloser {
|
||||||
privacy := newPrivacy()
|
return NewPrivacyWriteCloserWithKeyGen(wc, cmType, NewArgon2())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivacyWriteCloserWithKeyGen(wc io.WriteCloser, cmType CipherMethodType, keygen KeyGen) *WriteCloser {
|
||||||
|
privacy := newPrivacy(keygen)
|
||||||
privacy.cmType = cmType
|
privacy.cmType = cmType
|
||||||
return &WriteCloser{
|
return &WriteCloser{
|
||||||
Privacy: privacy,
|
Privacy: privacy,
|
||||||
@ -130,7 +154,11 @@ func (p *Privacy) GenerateKey(passphrase string) error {
|
|||||||
return ErrUninitialisedMethod
|
return ErrUninitialisedMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
key = argon2.IDKey([]byte(passphrase), p.salt, 1, 16*1024, 4, 32)
|
if len(p.salt) != 16 {
|
||||||
|
return ErrUninitialisedSalt
|
||||||
|
}
|
||||||
|
|
||||||
|
key = p.keygen.GenerateKey([]byte(passphrase), p.salt)
|
||||||
switch p.cmType {
|
switch p.cmType {
|
||||||
case XChaCha20Simple:
|
case XChaCha20Simple:
|
||||||
p.aead, err = chacha20poly1305.NewX(key)
|
p.aead, err = chacha20poly1305.NewX(key)
|
||||||
@ -146,12 +174,10 @@ func (p *Privacy) GenerateKey(passphrase string) error {
|
|||||||
|
|
||||||
func (wc *WriteCloser) Write(b []byte) (n int, err error) {
|
func (wc *WriteCloser) Write(b []byte) (n int, err error) {
|
||||||
var (
|
var (
|
||||||
// segmentLen uint32
|
|
||||||
// segmentLenBytes []byte
|
|
||||||
// nonce []byte
|
|
||||||
// ciphertext []byte
|
|
||||||
// plaintext []byte
|
|
||||||
copied int
|
copied int
|
||||||
|
nonceSize int
|
||||||
|
lastMarker int
|
||||||
|
plaintext []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
if wc.aead == nil {
|
if wc.aead == nil {
|
||||||
@ -171,18 +197,72 @@ func (wc *WriteCloser) Write(b []byte) (n int, err error) {
|
|||||||
wc.magicWritten = true
|
wc.magicWritten = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nonceSize = wc.aead.NonceSize()
|
||||||
copied = 0
|
copied = 0
|
||||||
for copied < len(b) {
|
for copied < len(b) {
|
||||||
if len(wc.bufSlice) == int(wc.segmentSize) {
|
if len(wc.bufSlice) == int(wc.segmentSize) {
|
||||||
|
n, err = wc.writeSegment()
|
||||||
} else {
|
if err != nil {
|
||||||
if len(b[copied:]) <= int(wc.segmentSize)-len(wc.bufSlice) {
|
|
||||||
wc.bufSlice = wc.buf[wc.aead.NonceSize() : len(wc.bufSlice)+len(b[copied:])]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastMarker = len(wc.bufSlice)
|
||||||
|
plaintext = wc.buf[nonceSize : nonceSize+len(wc.bufSlice)]
|
||||||
|
if len(b[copied:]) <= int(wc.segmentSize)-len(wc.bufSlice) {
|
||||||
|
plaintext = plaintext[:len(plaintext)+len(b[copied:])]
|
||||||
|
copied += copy(plaintext[lastMarker:], b[copied:])
|
||||||
|
} else {
|
||||||
|
plaintext = plaintext[:int(wc.segmentSize)]
|
||||||
|
copied += copy(plaintext[lastMarker:], b[copied:])
|
||||||
|
}
|
||||||
|
wc.bufSlice = plaintext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WriteCloser) writeSegment() (n int, err error) {
|
||||||
|
var (
|
||||||
|
nonce []byte
|
||||||
|
ciphertext []byte
|
||||||
|
plaintext []byte
|
||||||
|
written int
|
||||||
|
)
|
||||||
|
|
||||||
|
written = len(wc.bufSlice)
|
||||||
|
binary.LittleEndian.PutUint32(segmentLenBytes, uint32(written))
|
||||||
|
n, err = wc.writeCloser.Write(segmentLenBytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce = wc.buf[:wc.aead.NonceSize()]
|
||||||
|
_, err = rand.Read(nonce)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
plaintext = wc.buf[wc.aead.NonceSize() : wc.aead.NonceSize()+written]
|
||||||
|
ciphertext = plaintext[:0]
|
||||||
|
|
||||||
|
wc.aead.Seal(ciphertext, nonce, plaintext, segmentLenBytes)
|
||||||
|
n, err = wc.writeCloser.Write(wc.buf[:written+wc.aead.NonceSize()+wc.aead.Overhead()])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wc.bufSlice = wc.buf[wc.aead.NonceSize():wc.aead.NonceSize()]
|
||||||
|
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WriteCloser) Close() (err error) {
|
||||||
|
if len(wc.bufSlice) > 0 {
|
||||||
|
_, err = wc.writeSegment()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wc.writeCloser.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) ReadMagic() (err error) {
|
func (r *Reader) ReadMagic() (err error) {
|
||||||
@ -217,7 +297,6 @@ func (r *Reader) ReadMagic() (err error) {
|
|||||||
func (r *Reader) Read(b []byte) (n int, err error) {
|
func (r *Reader) Read(b []byte) (n int, err error) {
|
||||||
var (
|
var (
|
||||||
segmentLen uint32
|
segmentLen uint32
|
||||||
segmentLenBytes []byte
|
|
||||||
nonce []byte
|
nonce []byte
|
||||||
ciphertext []byte
|
ciphertext []byte
|
||||||
plaintext []byte
|
plaintext []byte
|
||||||
@ -232,6 +311,10 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||||||
return 0, ErrInvalidKeyState
|
return 0, ErrInvalidKeyState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.isEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
if cap(r.buf) != int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize() {
|
if cap(r.buf) != int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize() {
|
||||||
r.buf = make([]byte, int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize())
|
r.buf = make([]byte, int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize())
|
||||||
}
|
}
|
||||||
@ -239,10 +322,17 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||||||
copied = 0
|
copied = 0
|
||||||
for copied < len(b) {
|
for copied < len(b) {
|
||||||
if len(r.bufSlice) == 0 {
|
if len(r.bufSlice) == 0 {
|
||||||
//TODO: nothing in the buffer, fill it up
|
|
||||||
segmentLenBytes = make([]byte, segmentSizeBytesLen)
|
|
||||||
n, err = r.reader.Read(segmentLenBytes)
|
n, err = r.reader.Read(segmentLenBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if copied > 0 {
|
||||||
|
r.isEOF = true
|
||||||
|
return copied, nil
|
||||||
|
} else {
|
||||||
|
r.isEOF = true
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,28 +341,28 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||||||
return 0, ErrInvalidSegmentLength
|
return 0, ErrInvalidSegmentLength
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = r.reader.Read(r.buf)
|
n, err = r.reader.Read(r.buf[:int(segmentLen)+r.aead.Overhead()+r.aead.NonceSize()])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce = r.buf[:r.aead.NonceSize()]
|
nonce = r.buf[:r.aead.NonceSize()]
|
||||||
ciphertext = r.buf[r.aead.NonceSize():]
|
ciphertext = r.buf[r.aead.NonceSize() : r.aead.NonceSize()+int(segmentLen)+r.aead.Overhead()]
|
||||||
plaintext = ciphertext[:0]
|
plaintext = ciphertext[:0]
|
||||||
|
|
||||||
r.bufSlice, err = r.aead.Open(plaintext, nonce, ciphertext, segmentLenBytes)
|
if _, err = r.aead.Open(plaintext, nonce, ciphertext, segmentLenBytes); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
plaintext = plaintext[:int(segmentLen)]
|
||||||
|
r.bufSlice = plaintext
|
||||||
} else {
|
} else {
|
||||||
if len(b)-copied <= len(r.bufSlice) {
|
if len(b[copied:]) <= len(r.bufSlice) {
|
||||||
copy(b[copied:], r.bufSlice[:len(b)-copied])
|
cp := copy(b[copied:], r.bufSlice)
|
||||||
r.bufSlice = r.bufSlice[len(b)-copied:]
|
r.bufSlice = r.bufSlice[cp:]
|
||||||
copied += len(b) - copied
|
copied += cp
|
||||||
} else {
|
} else {
|
||||||
copy(b[copied:], r.bufSlice)
|
copied += copy(b[copied:], r.bufSlice)
|
||||||
copied += len(r.bufSlice)
|
r.bufSlice = r.buf[r.aead.NonceSize():r.aead.NonceSize()]
|
||||||
r.bufSlice = r.bufSlice[:0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,7 +371,7 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadPassphraseFromTerminal() (string, error) {
|
func ReadPassphraseFromTerminal() (string, error) {
|
||||||
var inputFd int = int(os.Stdin.Fd())
|
var inputFd = int(os.Stdin.Fd())
|
||||||
if !term.IsTerminal(inputFd) {
|
if !term.IsTerminal(inputFd) {
|
||||||
return "", errors.New("not a terminal")
|
return "", errors.New("not a terminal")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,145 @@
|
|||||||
package privacy
|
package privacy
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
"io"
|
||||||
|
mr "math/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tBuffer struct {
|
||||||
|
buf []byte
|
||||||
|
rOff int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTBuf(size int) *tBuffer {
|
||||||
|
return &tBuffer{
|
||||||
|
buf: make([]byte, 0, size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *tBuffer) Read(b []byte) (n int, err error) {
|
||||||
|
if tb.rOff == len(tb.buf) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b)+tb.rOff <= len(tb.buf) {
|
||||||
|
copy(b, tb.buf[tb.rOff:])
|
||||||
|
tb.rOff += len(b)
|
||||||
|
return len(b), nil
|
||||||
|
} else {
|
||||||
|
copy(b[:len(tb.buf)-tb.rOff], tb.buf[tb.rOff:])
|
||||||
|
n = len(tb.buf) - tb.rOff
|
||||||
|
err = nil
|
||||||
|
tb.rOff = len(tb.buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *tBuffer) Write(b []byte) (n int, err error) {
|
||||||
|
if len(tb.buf)+len(b) > cap(tb.buf) {
|
||||||
|
return 0, errors.New("insufficient space")
|
||||||
|
}
|
||||||
|
|
||||||
|
wOff := len(tb.buf)
|
||||||
|
tb.buf = tb.buf[:wOff+len(b)]
|
||||||
|
copy(tb.buf[wOff:], b)
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *tBuffer) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadWriteClose(t *testing.T) {
|
||||||
|
tb := newTBuf(70 * 1024)
|
||||||
|
keygen, err := NewArgon2WithParams(1, 4*1024, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("test preparation failure:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
passphrase := "some passphrase"
|
||||||
|
|
||||||
|
writer := NewPrivacyWriteCloserWithKeyGen(tb, DefaultCipherMethod, keygen)
|
||||||
|
|
||||||
|
t.Run("uninitialised salt", func(t *testing.T) {
|
||||||
|
err = writer.GenerateKey(passphrase)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("unexpected: it should error")
|
||||||
|
}
|
||||||
|
if err != ErrUninitialisedSalt {
|
||||||
|
t.Fatal("unexpected error result:", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
writer.SetSegmentSize(uint32(16 * 1024))
|
||||||
|
|
||||||
|
if err = writer.NewSalt(); err != nil {
|
||||||
|
t.Fatal("unexpected: NewSalt failed", err)
|
||||||
|
}
|
||||||
|
if err = writer.GenerateKey(passphrase); err != nil {
|
||||||
|
t.Fatal("unexpected: failed to generate key", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sha := sha256.New()
|
||||||
|
ur := mr.New(mr.NewSource(1))
|
||||||
|
bb := make([]byte, 1048)
|
||||||
|
|
||||||
|
var bar int
|
||||||
|
for i := 0; i < 63; i++ {
|
||||||
|
bar = 1000 + ur.Intn(49)
|
||||||
|
ur.Read(bb[:bar])
|
||||||
|
sha.Write(bb[:bar])
|
||||||
|
if _, err = writer.Write(bb[:bar]); err != nil {
|
||||||
|
t.Fatal("unexpected: Write failed", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = writer.Close(); err != nil {
|
||||||
|
t.Fatal("unexpected: Close failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeHash := sha.Sum(nil)
|
||||||
|
t.Log("write hash:", hex.EncodeToString(writeHash))
|
||||||
|
|
||||||
|
reader := NewPrivacyReaderWithKeyGen(tb, keygen)
|
||||||
|
reader.SetSegmentSize(uint32(16 * 1024))
|
||||||
|
if err = reader.ReadMagic(); err != nil {
|
||||||
|
t.Fatal("unexpected: ReadMagic failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = reader.GenerateKey(passphrase); err != nil {
|
||||||
|
t.Fatal("unexpected: GenerateKey failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sha.Reset()
|
||||||
|
err = nil
|
||||||
|
for err == nil {
|
||||||
|
if _, err = reader.Read(bb); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
t.Fatal("unexpected: Read failed", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sha.Write(bb)
|
||||||
|
}
|
||||||
|
|
||||||
|
readHash := sha.Sum(nil)
|
||||||
|
t.Log("read hash:", hex.EncodeToString(readHash))
|
||||||
|
|
||||||
|
//for i := range writeHash {
|
||||||
|
// if readHash[i] != writeHash[i] {
|
||||||
|
// t.Fatal("unexpected: mismatch hash")
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrial(t *testing.T) {
|
func TestTrial(t *testing.T) {
|
||||||
x := make([]byte, 20)
|
x := make([]byte, 20)
|
||||||
@ -22,3 +161,66 @@ func TestTrial(t *testing.T) {
|
|||||||
t.Log("cap z:", cap(z))
|
t.Log("cap z:", cap(z))
|
||||||
t.Log("z:", z)
|
t.Log("z:", z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeyGen(t *testing.T) {
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected error:", err)
|
||||||
|
}
|
||||||
|
passphrase := []byte("some passphrase")
|
||||||
|
|
||||||
|
key := argon2.IDKey(passphrase, salt, 1000, 64*1024, 8, 32)
|
||||||
|
|
||||||
|
_ = key
|
||||||
|
//keyCompare := argon2Params.IDKey(passphrase, salt, 100, 64*1024, 4, 32)
|
||||||
|
//
|
||||||
|
//for i := range key {
|
||||||
|
// if key[i] != keyCompare[i] {
|
||||||
|
// t.Fatal("unexpected result")
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExample(t *testing.T) {
|
||||||
|
passphrase := []byte("some passphrase")
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error prepare salt", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := argon2.IDKey(passphrase, salt, 1, 4*1024, 2, 32)
|
||||||
|
|
||||||
|
var aead cipher.AEAD
|
||||||
|
if aead, err = chacha20poly1305.NewX(key); err != nil {
|
||||||
|
t.Fatal("chacha", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ur := mr.New(mr.NewSource(1))
|
||||||
|
bb := make([]byte, 1024+aead.NonceSize()+aead.Overhead())
|
||||||
|
|
||||||
|
if _, err = rand.Read(bb[:aead.NonceSize()]); err != nil {
|
||||||
|
t.Fatal("fill up nonce", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
additional := []byte("some additional data")
|
||||||
|
ur.Read(bb[aead.NonceSize() : aead.NonceSize()+1024])
|
||||||
|
before := sha256.Sum256(bb[aead.NonceSize() : aead.NonceSize()+1024])
|
||||||
|
|
||||||
|
aead.Seal(bb[aead.NonceSize():aead.NonceSize()], bb[:aead.NonceSize()], bb[aead.NonceSize():aead.NonceSize()+1024], additional)
|
||||||
|
|
||||||
|
if _, err = aead.Open(bb[aead.NonceSize():aead.NonceSize()],
|
||||||
|
bb[:aead.NonceSize()], bb[aead.NonceSize():aead.NonceSize()+1024+aead.Overhead()],
|
||||||
|
additional); err != nil {
|
||||||
|
t.Fatal("decrypt error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
after := sha256.Sum256(bb[aead.NonceSize() : aead.NonceSize()+1024])
|
||||||
|
|
||||||
|
for i := range before {
|
||||||
|
if before[i] != after[i] {
|
||||||
|
t.Fatal("data corruption?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user