Skip to content

Commit de05209

Browse files
authored
Merge pull request #14 from maxlaverse/release_encryption
Release encryption
2 parents e7e686f + 377df04 commit de05209

File tree

9 files changed

+185
-81
lines changed

9 files changed

+185
-81
lines changed

.github/workflows/workflow.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ jobs:
88
- uses: actions/checkout@master
99

1010
- name: run tests for library
11-
uses: cedrickring/golang-action@1.6.0
11+
uses: actions/setup-go@v2
12+
with:
13+
go-version: 1.18
14+
stable: false
1215

1316
- name: run tests for cli
14-
uses: cedrickring/golang-action@1.6.0
17+
uses: actions/setup-go@v2
18+
with:
19+
go-version: 1.18
20+
stable: false
1521
env:
1622
PROJECT_PATH: "./cli/synocrypto"
1723
CI: "true"

README.md

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Synology Cloud Sync decryption in Go
1+
# Synology Cloud Sync encryption/decryption in Go
22

33
[![GoDoc](https://godoc.org/github.com/maxlaverse/synocrypto?status.svg)](https://godoc.org/github.com/maxlaverse/synocrypto)
44
[![test](https://github.com/maxlaverse/synocrypto/actions/workflows/workflow.yaml/badge.svg)](https://github.com/maxlaverse/synocrypto/actions/workflows/workflow.yaml)
@@ -45,7 +45,7 @@ GLOBAL OPTIONS:
4545

4646
## Library
4747

48-
### Usage
48+
### Decryption Usage
4949

5050
```go
5151
encFile, err := os.Open("./my-encrypted-file.jpg")
@@ -70,18 +70,36 @@ if err != nil {
7070
}
7171
```
7272

73-
### Encryption Support
73+
### Encryption Usage
7474

75-
The library has a prototype implementation for encrypting files.
76-
Files encrypted with it can often (always?) be decrypted using the library again.
77-
However, Cloudsync sometimes fails to decrypt them with errors related to compression:
78-
```
79-
Sep 16 22:54:25 [ERROR] lz4-processor.cpp(239): LZ4F_decompress LOGIC ERROR: inbuf_consumed='0' inbuf_size='8'
80-
Sep 16 22:54:25 [ERROR] pipeline.cpp(119): Failed when read
81-
Sep 16 22:54:25 [ERROR] encrypt-file.cpp(148): Failed when reading from decryptor.
82-
Sep 16 22:54:25 [WARNING] worker.cpp(3211): Worker (15): Failed to decrypt file
75+
```go
76+
plainFile, err := os.Open("./my-plain-file.jpg")
77+
if err != nil {
78+
panic(err)
79+
}
80+
defer plainFile.Close()
81+
82+
encFile, err := os.OpenFile("./my-encrypted-file.jpg", os.O_CREATE|os.O_WRONLY, 0644)
83+
if err != nil {
84+
panic(err)
85+
}
86+
defer encFile.Close()
87+
88+
privateKey, err := ioutil.ReadFile("private.pem")
89+
if err != nil {
90+
panic(err)
91+
}
92+
93+
encrypter := synocrypto.NewEncrypter(synocrypto.EncrypterOptions{
94+
Password: "synocrypto",
95+
PrivateKey: privateKey,
96+
})
97+
98+
err = encrypter.Encrypt(plainFile, encFile)
99+
if err != nil {
100+
panic(err)
101+
}
83102
```
84103

85-
I haven't had the time to debug this further.
86104

87105
[releases]: https://github.com/maxlaverse/synocrypto/releases

cli/synocrypto/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/maxlaverse/synocrypto/cli/synocrypto
22

3-
go 1.16
3+
go 1.18
44

55
require (
66
github.com/maxlaverse/synocrypto v0.0.0-20210411154523-101d272bbf64

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/maxlaverse/synocrypto
22

3-
go 1.17
3+
go 1.18
44

55
require (
66
github.com/pierrec/lz4/v4 v4.1.14

pkg/crypto/decrypter.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"encoding/pem"
1313
"fmt"
1414
"io"
15+
16+
"github.com/maxlaverse/synocrypto/pkg/log"
1517
)
1618

1719
const (
@@ -27,6 +29,7 @@ type decrypter struct {
2729
mode cipher.BlockMode
2830
bufferedBlock []byte
2931
out io.Writer
32+
blockIndex int
3033
}
3134

3235
// PublicKeyFromPrivateKey generates the public key corresponding to a given
@@ -107,27 +110,32 @@ func newAESCBCDecrypter(key, iv []byte, out io.Writer) io.WriteCloser {
107110

108111
// Write always buffers the decrypted data and writes it on the next call to Write() or Close()
109112
func (d *decrypter) Write(p []byte) (int, error) {
110-
var n int
111113
if d.hasBufferedBlock {
112-
var err error
113-
n, err = d.out.Write(d.bufferedBlock)
114+
err := d.flushBuffer()
114115
if err != nil {
115-
return n, fmt.Errorf("error flushing buffered block: %w", err)
116+
return -1, err
116117
}
117-
d.hasBufferedBlock = false
118118
}
119119

120-
if len(p) == 0 {
121-
return n, nil
120+
if len(p) != 0 {
121+
err := d.bufferData(p)
122+
if err != nil {
123+
return -1, err
124+
}
122125
}
126+
return len(p), nil
127+
}
128+
129+
func (d *decrypter) bufferData(p []byte) error {
130+
log.Debugf("Buffering %d bytes of encrypted data", len(p))
123131

124132
if len(d.bufferedBlock) < len(p) {
125133
d.bufferedBlock = make([]byte, len(p))
126134
} else if len(d.bufferedBlock) > len(p) {
127135
d.bufferedBlock = d.bufferedBlock[:len(p)]
128136
}
129137

130-
err := func() (err error) {
138+
return func() (err error) {
131139
defer func() {
132140
if r := recover(); r != nil {
133141
err = fmt.Errorf("CryptBlocks paniced: %v", r)
@@ -138,25 +146,38 @@ func (d *decrypter) Write(p []byte) (int, error) {
138146
d.hasBufferedBlock = true
139147
return
140148
}()
141-
return n, err
142149
}
143150

144-
func (d *decrypter) flushBufferWithoutPadding() (int, error) {
151+
func (d *decrypter) flushBuffer() error {
152+
d.blockIndex += 1
153+
log.Debugf("Block #%d - Writing out %d decrypted bytes", d.blockIndex, len(d.bufferedBlock))
154+
155+
_, err := d.out.Write(d.bufferedBlock)
156+
if err != nil {
157+
return fmt.Errorf("error flushing buffered block: %w", err)
158+
}
159+
d.hasBufferedBlock = false
160+
return nil
161+
}
162+
163+
func (d *decrypter) flushBufferWithoutPadding() error {
145164
if !d.hasBufferedBlock {
146-
return -1, nil
165+
return nil
147166
}
148167

149168
var err error
169+
sizeBeforeUnpadding := len(d.bufferedBlock)
150170
d.bufferedBlock, err = pkcs7Unpad(d.bufferedBlock, d.mode.BlockSize())
171+
log.Debugf("Removing %d bytes of padding to the plain data", sizeBeforeUnpadding-len(d.bufferedBlock))
151172
if err != nil {
152-
return -1, fmt.Errorf("unable to unpad data: %w", err)
173+
return fmt.Errorf("unable to unpad data: %w", err)
153174
}
154175

155-
return d.Write(nil)
176+
return d.flushBuffer()
156177
}
157178

158179
func (d *decrypter) Close() error {
159-
_, err := d.flushBufferWithoutPadding()
180+
err := d.flushBufferWithoutPadding()
160181

161182
if v, ok := d.out.(io.WriteCloser); ok {
162183
if err := v.Close(); err != nil {

pkg/crypto/encrypter.go

Lines changed: 63 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import (
1111
"encoding/pem"
1212
"fmt"
1313
"io"
14+
15+
"github.com/maxlaverse/synocrypto/pkg/log"
1416
)
1517

1618
const (
1719
// maxBlockSize is the maximum size an object can have. If the limit
1820
// is cross, CloudSync complains about it and says it considers the file
19-
// to be corrupted. This size seems to be 8192 but we're being conservative.
20-
maxBlockSize = 4096
21+
// to be corrupted.
22+
maxBlockSize = 8192
2123
)
2224

2325
// EncryptOnceWithPasswordAndSalt encrypts a single blob of data with
@@ -99,21 +101,22 @@ type encrypter struct {
99101
mode cipher.BlockMode
100102
bufferedBlock []byte
101103
out io.Writer
104+
blockIndex int
102105
}
103106

104-
func (d *encrypter) Write(p []byte) (int, error) {
105-
if len(p) == 0 {
106-
return d.write(nil)
107-
}
107+
func (e *encrypter) Write(p []byte) (int, error) {
108+
return e.writeInChunks(p, maxBlockSize)
109+
}
108110

111+
func (e *encrypter) writeInChunks(p []byte, size int) (int, error) {
109112
written := 0
110-
for start := 0; start < len(p); start += maxBlockSize {
111-
end := start + maxBlockSize
113+
for start := 0; start <= len(p); start += size {
114+
end := start + size
112115
if end > len(p) {
113116
end = len(p)
114117
}
115118

116-
n, err := d.write(p[start:end])
119+
n, err := e.write(p[start:end])
117120
written = written + n
118121
if err != nil {
119122
return written, err
@@ -122,66 +125,76 @@ func (d *encrypter) Write(p []byte) (int, error) {
122125
return written, nil
123126
}
124127

125-
func (d *encrypter) write(p []byte) (int, error) {
126-
if d.hasBufferedBlock {
127-
lastBlockEncrypted := make([]byte, len(d.bufferedBlock))
128-
129-
err := func() (err error) {
130-
defer func() {
131-
if r := recover(); r != nil {
132-
err = fmt.Errorf("CryptBlocks paniced: %v", r)
133-
}
134-
}()
135-
136-
d.mode.CryptBlocks(lastBlockEncrypted, d.bufferedBlock)
137-
return
138-
}()
128+
func (e *encrypter) write(p []byte) (int, error) {
129+
if e.hasBufferedBlock {
130+
err := e.encryptAndFlushBuffer()
139131
if err != nil {
140-
return -1, fmt.Errorf("error crypting block: %w", err)
132+
return -1, err
141133
}
134+
}
142135

143-
n, err := d.out.Write(lastBlockEncrypted)
144-
if err != nil {
145-
return n, fmt.Errorf("error flushing block: %w", err)
146-
}
147-
d.hasBufferedBlock = false
136+
if len(p) != 0 {
137+
e.bufferData(p)
148138
}
139+
return len(p), nil
140+
}
149141

150-
// We have to return len(p) instead of what was really written
151-
// to the output, or io.Copy() returns 'short write' error.
152-
if len(p) == 0 {
153-
return len(p), nil
142+
func (e *encrypter) bufferData(p []byte) {
143+
log.Debugf("Buffering %d bytes of plain data", len(p))
144+
e.bufferedBlock = make([]byte, len(p))
145+
copy(e.bufferedBlock, p)
146+
147+
e.hasBufferedBlock = true
148+
}
149+
150+
func (e *encrypter) encryptAndFlushBuffer() error {
151+
encryptedBlock := make([]byte, len(e.bufferedBlock))
152+
153+
err := func() (err error) {
154+
defer func() {
155+
if r := recover(); r != nil {
156+
err = fmt.Errorf("CryptBlocks paniced: %v", r)
157+
}
158+
}()
159+
160+
e.mode.CryptBlocks(encryptedBlock, e.bufferedBlock)
161+
return
162+
}()
163+
if err != nil {
164+
return fmt.Errorf("error crypting block: %w", err)
154165
}
155166

156-
if len(d.bufferedBlock) < len(p) {
157-
d.bufferedBlock = make([]byte, len(p))
158-
} else if len(d.bufferedBlock) > len(p) {
159-
d.bufferedBlock = d.bufferedBlock[:len(p)]
167+
e.blockIndex += 1
168+
log.Debugf("Block #%d - Writting out %d encrypted bytes ", e.blockIndex, len(encryptedBlock))
169+
_, err = e.out.Write(encryptedBlock)
170+
if err != nil {
171+
return fmt.Errorf("error flushing block: %w", err)
160172
}
161-
copy(d.bufferedBlock, p)
162173

163-
d.hasBufferedBlock = true
164-
return len(p), nil
174+
e.hasBufferedBlock = false
175+
return nil
165176
}
166177

167-
func (d *encrypter) flushBufferWithPadding() (int, error) {
168-
if !d.hasBufferedBlock {
169-
return -1, nil
178+
func (e *encrypter) encryptAndFlushBufferWithPadding() error {
179+
if !e.hasBufferedBlock {
180+
return nil
170181
}
171182

172183
var err error
173-
d.bufferedBlock, err = pkcs7Pad(d.bufferedBlock, d.mode.BlockSize())
184+
sizeBeforePadding := len(e.bufferedBlock)
185+
e.bufferedBlock, err = pkcs7Pad(e.bufferedBlock, e.mode.BlockSize())
186+
log.Debugf("Adding %d bytes of padding to the plain data", len(e.bufferedBlock)-sizeBeforePadding)
187+
174188
if err != nil {
175-
return -1, fmt.Errorf("unable to pad data: %w", err)
189+
return fmt.Errorf("unable to pad data: %w", err)
176190
}
177-
178-
return d.Write(nil)
191+
return e.encryptAndFlushBuffer()
179192
}
180193

181-
func (d *encrypter) Close() error {
182-
_, err := d.flushBufferWithPadding()
194+
func (e *encrypter) Close() error {
195+
err := e.encryptAndFlushBufferWithPadding()
183196

184-
if v, ok := d.out.(io.WriteCloser); ok {
197+
if v, ok := e.out.(io.WriteCloser); ok {
185198
if err := v.Close(); err != nil {
186199
return err
187200
}

pkg/crypto/encrypter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestEncrypt(t *testing.T) {
4242
decrypter := NewDecrypterWithPasswordAndSalt([]byte(tc.givenSessionKey), []byte{}, &outputDecrypted)
4343
n, err = decrypter.Write(outputEncrypted.Bytes())
4444
assert.NoError(t, err)
45-
assert.Equal(t, 0, n)
45+
assert.Equal(t, outputEncrypted.Len(), n)
4646

4747
err = decrypter.Close()
4848
assert.NoError(t, err)

synocrypto.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ type EncrypterOptions struct {
6565

6666
// DisableDigestGeneration allows to disable the md5 checksum computation.
6767
DisableDigestGeneration bool
68+
69+
// DisableIntegrityCheck disables the check that what was encrypted can be decrypted.
70+
DisableIntegrityCheck bool
6871
}
6972

7073
// NewEncrypter returns a new encrypter

0 commit comments

Comments
 (0)