package encfs import ( "crypto/aes" "crypto/rand" "encoding/binary" "encoding/json" "errors" "io" "io/fs" "math" "os" "strings" "syscall" "github.com/spf13/afero" ) const EncFileExt = ".__encfile" var ( ErrFileForbiddenFileExt = errors.New("file ext is forbidden") ) type EncFileMeta struct { Name string `json:"name"` Iv []byte `json:"iv"` } func openOrNewEncFileMeta(name string) (*EncFileMeta, error) { oldEncFileMeta, err := openEncFileMeta(name) if err == nil && oldEncFileMeta != nil { return oldEncFileMeta, nil } iv := make([]byte, 16) _, err = rand.Read(iv) if err != nil { return nil, err } encFileMeta := &EncFileMeta{ Name: name, Iv: iv, } encFileMetaName := name + EncFileExt encFileMetaFile, err := os.Create(encFileMetaName) if err != nil { return nil, err } defer func() { _ = encFileMetaFile.Close() }() encFileMetaBytes, err := marshalEncFileMeta(encFileMeta) if err != nil { return nil, err } _, err = encFileMetaFile.Write(encFileMetaBytes) if err != nil { return nil, err } return encFileMeta, nil } func openEncFileMeta(name string) (*EncFileMeta, error) { encFileMetaName := name + EncFileExt encFileMetaFile, err := os.Open(encFileMetaName) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } defer func() { _ = encFileMetaFile.Close() }() encFileMetaBytes, err := io.ReadAll(encFileMetaFile) if err != nil { return nil, err } encFileMeta, err := unmarchalEncFileMeta(encFileMetaBytes) if err != nil { return nil, err } return encFileMeta, nil } func marshalEncFileMeta(encFileMeata *EncFileMeta) ([]byte, error) { return json.Marshal(encFileMeata) } func unmarchalEncFileMeta(data []byte) (*EncFileMeta, error) { var encFileMeta EncFileMeta err := json.Unmarshal(data, &encFileMeta) if err != nil { return nil, err } return &encFileMeta, nil } type EncFile struct { isDir bool closed bool encFileMeta *EncFileMeta encFs *EncFs filePos int64 file *os.File } func NewEncFile(name string, file *os.File, encFs *EncFs, isCreate bool) (*EncFile, error) { fileInfo, err := file.Stat() if err != nil { return nil, err } isDir := fileInfo.IsDir() var encFileMeta *EncFileMeta = nil fileState, err := os.Stat(name) if err == nil && !fileState.IsDir() && fileState.Size() == 0 { isCreate = true } if !isDir { if isCreate { encFileMeta, err = openOrNewEncFileMeta(name) } else { encFileMeta, err = openEncFileMeta(name) } if err != nil { return nil, err } } return &EncFile{ isDir: isDir, closed: false, encFileMeta: encFileMeta, encFs: encFs, filePos: 0, file: file, }, nil } func (f *EncFile) Close() error { if f.closed { return afero.ErrFileClosed } f.closed = true return f.file.Close() } func (f *EncFile) Read(p []byte) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } beforeReadFilePos := f.filePos readLen, err := f.file.Read(p) if err == nil { f.filePos += int64(readLen) if f.encFs != nil && f.encFs.key != nil && f.encFileMeta != nil { encryptedBytes, err := generateCtrEncryptBytes(f.encFs.key.key, f.encFileMeta.Iv, beforeReadFilePos, int64(readLen)) if err != nil { return 0, err } for i := 0; i < readLen; i++ { p[i] = p[i] ^ encryptedBytes[i] } } } return readLen, err } func (f *EncFile) ReadAt(p []byte, off int64) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } readLen, err := f.file.ReadAt(p, off) if err == nil { if f.encFs != nil && f.encFs.key != nil && f.encFileMeta != nil { encryptedBytes, err := generateCtrEncryptBytes(f.encFs.key.key, f.encFileMeta.Iv, off, int64(readLen)) if err != nil { return 0, err } for i := 0; i < readLen; i++ { p[i] = p[i] ^ encryptedBytes[i] } } } return readLen, err } func (f *EncFile) Seek(offset int64, whence int) (int64, error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } ret, err := f.file.Seek(offset, whence) if err == nil { f.filePos = ret } return ret, err } func (f *EncFile) Write(p []byte) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } writeBuff := p if f.encFs != nil && f.encFs.key != nil && f.encFileMeta != nil { buff := make([]byte, len(p)) encryptedBytes, err := generateCtrEncryptBytes(f.encFs.key.key, f.encFileMeta.Iv, f.filePos, int64(len(p))) if err != nil { return 0, err } for i := 0; i < len(p); i++ { buff[i] = p[i] ^ encryptedBytes[i] } writeBuff = buff } writeLen, err := f.file.Write(writeBuff) if err == nil { f.filePos += int64(writeLen) } return writeLen, err } func (f *EncFile) WriteAt(p []byte, off int64) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } writeBuff := p if f.encFs != nil && f.encFs.key != nil && f.encFileMeta != nil { buff := make([]byte, len(p)) encryptedBytes, err := generateCtrEncryptBytes(f.encFs.key.key, f.encFileMeta.Iv, off, int64(len(p))) if err != nil { return 0, err } for i := 0; i < len(p); i++ { buff[i] = p[i] ^ encryptedBytes[i] } writeBuff = buff } writeLen, err := f.file.WriteAt(writeBuff, off) return writeLen, err } func (f *EncFile) Name() string { return f.file.Name() } func (f *EncFile) Readdir(count int) ([]os.FileInfo, error) { if f.closed { return nil, afero.ErrFileClosed } // FIXME is count * 2 just ok? fileInfos, err := f.file.Readdir(count * 2) if err != nil { return nil, err } if count == 0 { return make([]fs.FileInfo, 0), nil } filterFileInfos := make([]os.FileInfo, 0) for _, fileInfo := range fileInfos { isEncFileMetaFile := strings.HasSuffix(fileInfo.Name(), EncFileExt) if !isEncFileMetaFile { filterFileInfos = append(filterFileInfos, fileInfo) } if count >= 0 && len(filterFileInfos) >= count { break } } return filterFileInfos, nil } func (f *EncFile) Readdirnames(n int) ([]string, error) { fi, err := f.Readdir(n) if err != nil { return nil, err } var names []string for _, f := range fi { names = append(names, f.Name()) } return names, nil } func (f *EncFile) Stat() (os.FileInfo, error) { return f.file.Stat() } func (f *EncFile) Sync() error { return f.file.Sync() } func (f *EncFile) Truncate(size int64) error { return f.file.Truncate(size) } func (f *EncFile) WriteString(s string) (ret int, err error) { return f.Write([]byte(s)) } func (f *EncFile) checkIsFile() error { if f.closed { return afero.ErrFileClosed } if f.isDir { return syscall.EISDIR } return nil } func generateCtrEncryptBytes(key, iv []byte, offset, len int64) ([]byte, error) { endOffset := offset + len blockOffset := offset / 16 encryptStartOffset := blockOffset * 16 encryptEndOffset := (endOffset / 16) * 16 if endOffset%16 > 0 { encryptEndOffset += 16 } blocksCount := int((encryptEndOffset - encryptStartOffset) / 16) cipher, err := aes.NewCipher(key) if err != nil { return nil, err } encryptBytes := make([]byte, blocksCount*16) for i := 0; i < blocksCount; i++ { encNonce := nonceAdd(iv, uint64(i)+uint64(blockOffset)) cipher.Encrypt(encryptBytes[i*16:(i+1)*16], encNonce) } encryptedBytes := encryptBytes[offset-encryptStartOffset : offset-encryptStartOffset+len] //fmt.Println("XX", hex.EncodeToString(key), hex.EncodeToString(iv), offset, len, hex.EncodeToString(encryptedBytes)) return encryptedBytes, nil } func nonceAdd(nonce []byte, incrementValue uint64) []byte { n1 := binary.BigEndian.Uint64(nonce[:8]) n2 := binary.BigEndian.Uint64(nonce[8:]) leftToMax := math.MaxUint64 - n2 if leftToMax <= incrementValue { incrementValue -= leftToMax + 1 n2 = incrementValue n1 += 1 } else { n2 += incrementValue } newNonce := make([]byte, 16) binary.BigEndian.PutUint64(newNonce, n1) binary.BigEndian.PutUint64(newNonce[8:], n2) return newNonce }