enable random salt geenration for file parts

This commit is contained in:
divyam234 2023-12-08 13:16:05 +05:30
parent 049cc0f54a
commit 6b0768161e
15 changed files with 83 additions and 64 deletions

View file

@ -137,13 +137,9 @@ In addition to the mandatory variables, you can also set the following optional
- `ENCRYPTION_KEY` : Password for Encryption.
- `ENCRYPTION_SALT` : Salt for Encryption.
> [!WARNING]
> Keep your Password and Salt safe once generated , teldrive uses same encryption as of rclone internally
so you don't need to enable crypt in rclone.Enabling crypt in rclone makes UI reduntant so encrypting files
in teldrive internally is better way to encrypt files instead of enabling in rclone.To encrypt files see more
about teldrive rclone config.
> Keep your Password safe once generated teldrive uses same encryption as of rclone internally
so you don't need to enable crypt in rclone.**Teldrive generates random salt for each file part and saves in database so its more secure than rclone crypt whereas in rclone same salt value is used for all files by which can be compromised easily**. Enabling crypt in rclone makes UI reduntant so encrypting files in teldrive internally is better way to encrypt files and more secure encryption than rclone.To encrypt files see more about teldrive rclone config.
### For making use of Multi Bots support

View file

@ -34,7 +34,6 @@ type Config struct {
UploadRetention int `envconfig:"UPLOAD_RETENTION" default:"15"`
DisableStreamBots bool `envconfig:"DISABLE_STREAM_BOTS" default:"false"`
EncryptionKey string `envconfig:"ENCRYPTION_KEY"`
EncryptionSalt string `envconfig:"ENCRYPTION_SALT"`
ExecDir string
}

View file

@ -533,7 +533,7 @@ func (c *Cipher) DecryptDataSeek(ctx context.Context, open OpenRangeSeek, offset
return out, nil
}
func (c *Cipher) EncryptedSize(size int64) int64 {
func EncryptedSize(size int64) int64 {
blocks, residue := size/blockDataSize, size%blockDataSize
encryptedSize := int64(fileHeaderSize) + blocks*(blockHeaderSize+blockDataSize)
if residue != 0 {
@ -542,7 +542,7 @@ func (c *Cipher) EncryptedSize(size int64) int64 {
return encryptedSize
}
func (c *Cipher) DecryptedSize(size int64) (int64, error) {
func DecryptedSize(size int64) (int64, error) {
size -= int64(fileHeaderSize)
if size < 0 {
return 0, ErrorEncryptedFileTooShort

View file

@ -4,6 +4,7 @@ import (
"context"
"io"
"github.com/divyam234/teldrive/config"
"github.com/divyam234/teldrive/internal/crypt"
"github.com/divyam234/teldrive/pkg/types"
"github.com/gotd/td/telegram"
@ -17,14 +18,13 @@ type decrpytedReader struct {
reader io.ReadCloser
bytesread int64
contentLength int64
cipher *crypt.Cipher
config *config.Config
}
func NewDecryptedReader(
ctx context.Context,
client *telegram.Client,
parts []types.Part,
cipher *crypt.Cipher,
contentLength int64) (io.ReadCloser, error) {
r := &decrpytedReader{
@ -32,7 +32,7 @@ func NewDecryptedReader(
parts: parts,
client: client,
contentLength: contentLength,
cipher: cipher,
config: config.GetConfig(),
}
res, err := r.nextPart()
@ -80,7 +80,9 @@ func (r *decrpytedReader) Close() (err error) {
func (r *decrpytedReader) nextPart() (io.ReadCloser, error) {
return r.cipher.DecryptDataSeek(r.ctx,
cipher, _ := crypt.NewCipher(r.config.EncryptionKey, r.parts[r.pos].Salt)
return cipher.DecryptDataSeek(r.ctx,
func(ctx context.Context,
underlyingOffset,
underlyingLimit int64) (io.ReadCloser, error) {

View file

@ -1,5 +0,0 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE teldrive.files ADD COLUMN "encrypted" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE teldrive.uploads ADD COLUMN "encrypted" BOOLEAN NOT NULL DEFAULT FALSE;
-- +goose StatementEnd

View file

@ -0,0 +1,6 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE teldrive.files ADD COLUMN IF NOT EXISTS "encrypted" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE teldrive.uploads ADD COLUMN IF NOT EXISTS "encrypted" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE teldrive.uploads ADD COLUMN IF NOT EXISTS "salt" TEXT;
-- +goose StatementEnd

View file

@ -27,7 +27,8 @@ func ToFileOutFull(file models.File) *schemas.FileOutFull {
parts := []schemas.Part{}
for _, part := range *file.Parts {
parts = append(parts, schemas.Part{
ID: part.ID,
ID: part.ID,
Salt: part.Salt,
})
}
@ -47,6 +48,7 @@ func ToUploadOut(in *models.Upload) *schemas.UploadPartOut {
PartNo: in.PartNo,
Size: in.Size,
Encrypted: in.Encrypted,
Salt: in.Salt,
}
return out
}

View file

@ -27,7 +27,8 @@ type File struct {
type Parts []Part
type Part struct {
ID int64 `json:"id"`
ID int64 `json:"id"`
Salt string `json:"salt,omitempty"`
}
func (a Parts) Value() (driver.Value, error) {

View file

@ -11,6 +11,7 @@ type Upload struct {
PartNo int `gorm:"type:integer"`
PartId int `gorm:"type:integer"`
Encrypted bool `gorm:"default:false"`
Salt string `gorm:"type:text"`
ChannelID int64 `gorm:"type:bigint"`
Size int64 `gorm:"type:bigint"`
CreatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`

View file

@ -15,7 +15,8 @@ type SortingQuery struct {
}
type Part struct {
ID int64 `json:"id"`
ID int64 `json:"id"`
Salt string `json:"salt"`
}
type FileQuery struct {

View file

@ -14,6 +14,7 @@ type UploadPartOut struct {
ChannelID int64 `json:"channelId"`
Size int64 `json:"size"`
Encrypted bool `json:"encrypted"`
Salt string `json:"salt"`
}
type UploadOut struct {

View file

@ -138,7 +138,7 @@ func getTGMessages(ctx context.Context, client *telegram.Client, parts []schemas
return messages, nil
}
func getParts(ctx context.Context, cipher *crypt.Cipher, client *telegram.Client, file *schemas.FileOutFull, userID string) ([]types.Part, error) {
func getParts(ctx context.Context, client *telegram.Client, file *schemas.FileOutFull, userID string) ([]types.Part, error) {
parts := []types.Part{}
@ -156,17 +156,22 @@ func getParts(ctx context.Context, cipher *crypt.Cipher, client *telegram.Client
return nil, err
}
for _, message := range messages.Messages {
for i, message := range messages.Messages {
item := message.(*tg.Message)
media := item.Media.(*tg.MessageMediaDocument)
document := media.Document.(*tg.Document)
location := document.AsInputDocumentFileLocation()
end := document.Size - 1
if cipher != nil {
end, _ = cipher.DecryptedSize(document.Size)
if file.Encrypted {
end, _ = crypt.DecryptedSize(document.Size)
end -= 1
}
parts = append(parts, types.Part{Location: location, Start: 0, End: end, Size: document.Size})
parts = append(parts, types.Part{
Location: location,
End: end,
Size: document.Size,
Salt: file.Parts[i].Salt,
})
}
cache.GetCache().Set(key, &parts, 3600)
return parts, nil
@ -189,37 +194,26 @@ func rangedParts(parts []types.Part, startByte, endByte int64) []types.Part {
endInLastChunk := endByte % chunkSize
if firstChunk == lastChunk {
validParts = append(validParts, types.Part{
Location: parts[firstChunk].Location,
Start: startInFirstChunk,
End: endInLastChunk,
Size: parts[firstChunk].Size,
})
part := parts[firstChunk]
part.Start = startInFirstChunk
part.End = endInLastChunk
validParts = append(validParts, part)
} else {
validParts = append(validParts, types.Part{
Location: parts[firstChunk].Location,
Start: startInFirstChunk,
End: parts[firstChunk].End,
Size: parts[firstChunk].Size,
})
part := parts[firstChunk]
part.Start = startInFirstChunk
validParts = append(validParts, part)
// Add valid parts from any chunks in between.
for i := firstChunk + 1; i < lastChunk; i++ {
validParts = append(validParts, types.Part{
Location: parts[i].Location,
Start: 0,
End: parts[i].End,
Size: parts[i].Size,
})
part := parts[i]
part.Start = 0
validParts = append(validParts, part)
}
// Add valid parts from the last chunk.
validParts = append(validParts, types.Part{
Location: parts[lastChunk].Location,
Start: 0,
End: endInLastChunk,
Size: parts[lastChunk].Size,
})
endPart := parts[lastChunk]
endPart.Start = 0
endPart.End = endInLastChunk
validParts = append(validParts, endPart)
}
return validParts

View file

@ -13,7 +13,6 @@ import (
cnf "github.com/divyam234/teldrive/config"
"github.com/divyam234/teldrive/internal/cache"
"github.com/divyam234/teldrive/internal/crypt"
"github.com/divyam234/teldrive/internal/http_range"
"github.com/divyam234/teldrive/internal/md5"
"github.com/divyam234/teldrive/internal/reader"
@ -85,7 +84,8 @@ func (fs *FileService) CreateFile(c *gin.Context) (*schemas.FileOut, *types.AppE
parts := models.Parts{}
for _, part := range fileIn.Parts {
parts = append(parts, models.Part{
ID: part.ID,
ID: part.ID,
Salt: part.Salt,
})
}
@ -554,14 +554,9 @@ func (fs *FileService) GetFileStream(c *gin.Context) {
var (
token, channelUser string
cipher *crypt.Cipher
lr io.ReadCloser
)
if file.Encrypted {
cipher, _ = crypt.NewCipher(config.EncryptionKey, config.EncryptionSalt)
}
if config.LazyStreamBots {
tgc.Workers.Set(tokens, file.ChannelID)
token = tgc.Workers.Next(file.ChannelID)
@ -569,13 +564,13 @@ func (fs *FileService) GetFileStream(c *gin.Context) {
channelUser = strings.Split(token, ":")[0]
if r.Method != "HEAD" {
tgc.RunWithAuth(c, client, token, func(ctx context.Context) error {
parts, err := getParts(c, cipher, client, file, channelUser)
parts, err := getParts(c, client, file, channelUser)
if err != nil {
return err
}
parts = rangedParts(parts, start, end)
if file.Encrypted {
lr, _ = reader.NewDecryptedReader(c, client, parts, cipher, contentLength)
lr, _ = reader.NewDecryptedReader(c, client, parts, contentLength)
} else {
lr, _ = reader.NewLinearReader(c, client, parts, contentLength)
}
@ -613,7 +608,7 @@ func (fs *FileService) GetFileStream(c *gin.Context) {
}
if r.Method != "HEAD" {
parts, err := getParts(c, cipher, client.Tg, file, channelUser)
parts, err := getParts(c, client.Tg, file, channelUser)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -622,7 +617,7 @@ func (fs *FileService) GetFileStream(c *gin.Context) {
parts = rangedParts(parts, start, end)
if file.Encrypted {
lr, _ = reader.NewDecryptedReader(c, client.Tg, parts, cipher, contentLength)
lr, _ = reader.NewDecryptedReader(c, client.Tg, parts, contentLength)
} else {
lr, _ = reader.NewLinearReader(c, client.Tg, parts, contentLength)
}

View file

@ -2,6 +2,9 @@ package services
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"net/http"
"strconv"
@ -25,6 +28,8 @@ import (
"gorm.io/gorm"
)
const saltLength = 32
type UploadService struct {
Db *gorm.DB
}
@ -33,6 +38,20 @@ func NewUploadService(db *gorm.DB) *UploadService {
return &UploadService{Db: db}
}
func generateRandomSalt() (string, error) {
randomBytes := make([]byte, saltLength)
_, err := rand.Read(randomBytes)
if err != nil {
return "", err
}
hasher := sha256.New()
hasher.Write(randomBytes)
hashedSalt := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
return hashedSalt, nil
}
func (us *UploadService) GetUploadFileById(c *gin.Context) (*schemas.UploadOut, *types.AppError) {
uploadId := c.Param("id")
parts := []schemas.UploadPartOut{}
@ -147,9 +166,14 @@ func (us *UploadService) UploadFile(c *gin.Context) (*schemas.UploadPartOut, *ty
config := cnf.GetConfig()
var salt string
if uploadQuery.Encrypted {
cipher, _ := crypt.NewCipher(config.EncryptionKey, config.EncryptionSalt)
fileSize = cipher.EncryptedSize(fileSize)
//gen random Salt
salt, _ = generateRandomSalt()
cipher, _ := crypt.NewCipher(config.EncryptionKey, salt)
fileSize = crypt.EncryptedSize(fileSize)
fileStream, _ = cipher.EncryptData(fileStream)
}
@ -202,6 +226,7 @@ func (us *UploadService) UploadFile(c *gin.Context) (*schemas.UploadPartOut, *ty
PartNo: uploadQuery.PartNo,
UserId: userId,
Encrypted: uploadQuery.Encrypted,
Salt: salt,
}
if err := us.Db.Create(partUpload).Error; err != nil {

View file

@ -16,6 +16,7 @@ type Part struct {
Start int64
End int64
Size int64
Salt string
}
type JWTClaims struct {