mirror of
https://github.com/tgdrive/teldrive.git
synced 2024-09-20 08:15:55 +08:00
enable random salt geenration for file parts
This commit is contained in:
parent
049cc0f54a
commit
6b0768161e
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
6
pkg/database/migrations/20231208114142_alter.sql
Normal file
6
pkg/database/migrations/20231208114142_alter.sql
Normal 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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -16,6 +16,7 @@ type Part struct {
|
|||
Start int64
|
||||
End int64
|
||||
Size int64
|
||||
Salt string
|
||||
}
|
||||
|
||||
type JWTClaims struct {
|
||||
|
|
Loading…
Reference in a new issue