A Go + Gin web app for user auth and encrypted file storage with a static HTML/CSS/JS frontend.
Key Features
- User registration/login with JWT (+ profile, avatar, password change)
- Encrypted file upload/download (AES-256-GCM, chunked)
- Batch upload + resumable uploads
- File preview (images/text/pdf/office)
- File type validation + optional malware scan
- Static web UI served by the backend
cmd/server/main.go: app entrypointinternal/config/: config loading and validationinternal/handler/: Gin HTTP handlersinternal/service/: business logic (file encryption lives here)internal/model/: GORM modelsinternal/pkg/: DB, logger, helpersinternal/routes/: API + static routesweb/templates/: HTML pagesweb/static/: JS/CSS/imagesstorage/: encrypted file blobs (created at runtime)config.yaml: runtime configuration
- Go 1.18+ (recommended to match
go.mod) - MySQL 8+ (or compatible)
- Optional: ClamAV (
clamscanorclamdscan) for malware scanning - Optional: LibreOffice (
libreofficeorsoffice) for Office preview
Minimal required fields:
database.*: DB connection parametersjwt.secret: JWT signing secret (min 32 chars)jwt.expiry_minutes: token lifetime in minutesfile_crypto.key: base64 url-safe secret (min 32 bytes after decoding)malware_scan.*: optional malware scan behavior
Example (already in repo):
server:
app_name: secure_file_box
env: development
debug: true
host: 127.0.0.1
port: 8080
time_zone: Asia/Shanghai
database:
driver: mysql
host: localhost
port: 3306
user: root
password: "0827"
name: secure_file_box
jwt:
issuer: secure_file_box
audience: secure_users
expiry_minutes: 60
secret: <your-strong-secret>
file_crypto:
key: <base64-url-encoded-32-bytes>
malware_scan:
enabled: true
command: "" # empty = auto-detect clamscan/clamdscan
timeout_seconds: 30
allow_on_failure: falseNotes:
- On startup, if
jwt.secretorfile_crypto.keyis missing/weak, the app auto-generates and writes it back toconfig.yaml. file_crypto.keymust be base64 URL-safe (no padding). Example generation:- Config can also be overridden by environment variables like
JWT_SECRETandFILE_CRYPTO_KEY.
python - <<'PY'
import os, base64
print(base64.urlsafe_b64encode(os.urandom(32)).rstrip(b'=').decode())
PYCreate the database (schema name must match config.yaml):
- First, you need to install mysql and mysql and mysql-client(Or MySQL WorkBench for optional GUI choice) For MacOS and Unix/Linux users, use the packahe manager on your platform to install mysql and mysql-client. By the way, the names of mysql and mysql-client might be different in different systems, please go check on the website.
- Debian/Ubuntu systems
sudo apt install mysql mysql-client- RHEL/CentOS/Fedora systems
sudo [dnf|yum|rpm|...] install mysql mysql-client- MacOS/OSX
brew install mysql mysql-client- For Windows users, MySQL WorkBench GUI is more recommanded, go to this website Download MySQL WorkBench
- Next step, make sure MySQL service is started in your system, For MacOS and Unix/Linux users, make sure it's started by inputting command below
- Debian/Ubuntu systems
sudo systemctl start mysql- RHEL/CentOS/Fedora systems
sudo systemctl start mysqld- MacOS/OSX
brew service start mysql- Windows
For Windows users, make sure you use Administrator mode Powershell, and input
net start MySQL80 - Then, enter mysql command line without password
mysql -u root -p- set up the database table and password step by step
CREATE DATABASE secure_file_box;- Set MySQL root password to match your
config.yaml(example):
ALTER USER 'root'@'localhost' IDENTIFIED BY 'yourpassword';- Exit and enter it again using the root user, make sure everything work in shape
From repo root:
go run cmd/server/main.goOpen:
http://127.0.0.1:8080
- Build
go build -o bin/efb_backend cmd/server/main.go- Run
bin/efb_backendAll APIs are mounted under /api/v1.
GET /api/v1/pingPOST /api/v1/auth/registerPOST /api/v1/auth/loginPOST /api/v1/auth/logoutGET /api/v1/user/profilePUT /api/v1/user/profileGET /api/v1/user/avatarPUT /api/v1/user/avatarPUT /api/v1/user/password
Files:
POST /api/v1/files/upload(JWT required)POST /api/v1/files/batch(JWT required)POST /api/v1/files/public/upload(no JWT)GET /api/v1/files(JWT required)GET /api/v1/files/download/:id(JWT required)GET /api/v1/files/preview/:id(JWT required)PUT /api/v1/files/:id(JWT required)DELETE /api/v1/files/:id(JWT required)DELETE /api/v1/files/batch(JWT required)POST /api/v1/files/resumable/init(JWT required)GET /api/v1/files/resumable/:upload_id(JWT required)POST /api/v1/files/resumable/:upload_id/chunk(JWT required)POST /api/v1/files/resumable/:upload_id/complete(JWT required)DELETE /api/v1/files/resumable/:upload_id(JWT required)
Legacy routes (no /api/v1 prefix) are also available for older clients.
- Allowed extensions:
jpg,jpeg,png,gif,webp,txt,md,json,log,csv,pdf,doc,docx,xls,xlsx,ppt,pptx. - Blocked extensions:
exe,dll,so,bin,sh,bat,apk,dmg,iso,msi,com,scr. - Content validation checks MIME type and magic bytes; text files must be UTF-8.
- Resumable uploads clamp
chunk_sizeto 256 KB–20 MB. - Malware scanning is controlled by
malware_scan.*and uses ClamAV. If scanning is enabled and no scanner is available, uploads fail unlessallow_on_failure: true.
/files/preview/:idsupports images, text, PDF, and Office files.- Text previews are limited to 2 MB and decoded as UTF-8/UTF-16/GB18030/ISO-8859-1.
- Office previews require LibreOffice (
libreofficeorsoffice) to convert to PDF on the fly.
File content and metadata are both protected with AES-256-GCM, with keys derived from file_crypto.key.
Key strategy
file_crypto.keymust be Base64 URL-safe (no padding) and decode to at least 32 bytes.- Two subkeys are derived via HMAC-SHA256 from the same master key:
- File content key:
HMAC(key, "file-gcm-aes256") - Metadata key:
HMAC(key, "db-meta-gcm-aes256")
File encryption (chunked)
- Algorithm: AES-256-GCM.
- Chunk size: 32 KB.
- File header: magic
SFB2+ 8-byte random nonce prefix. - Per-chunk nonce:
prefix(8)+counter(4)(big-endian, increasing). - AAD: 4-byte counter (big-endian).
- Chunk storage format:
uint32(len(sealed))(big-endian) +sealed(ciphertext + GCM tag). - Decryption authenticates each chunk; any failure returns
file integrity check failed.
Metadata encryption (DB fields)
- Fields: filename, storage path, size, description, uploader ID, MIME.
- Each field is encrypted independently with a random 12-byte nonce.
- Stored format:
v1:+ Base64 URL-safe (no padding) ofnonce || sealed. - Decrypt failures return
metadata integrity check failed; list API skips such rows to avoid breaking the entire response.
Compatibility and migration
- If
enc_*fields are empty, the service falls back to legacy fields (legacy_*).
Important
- Changing
file_crypto.keywill make existing files and metadata unreadable. invalid file magicorinvalid encrypted metadata formatusually means key mismatch, format change, or corruption.
Tests live under test/ and use a temporary SQLite database (no MySQL required). Malware scanning is disabled in tests, and Office preview conversion is not exercised.
Run all tests:
go test ./...Run only the test package:
go test ./test -vKey coverage:
test/config_test.go: secret/key generation and config write-back.test/file_validation_test.go: extension allow/deny and content validation.test/file_service_test.go: encrypt/decrypt flow, limits, legacy metadata fallback.test/resumable_upload_test.go: init/chunk/complete flow and error cases.test/user_service_test.go: user create/auth/password/profile flows.test/jwt_middleware_test.go: JWT middleware happy/unauthorized paths.test/utils_test.go: password hashing and pagination defaults.
Notes:
- File encryption uses a fixed test key via
test/test_helpers.go. - Temp files and sqlite DBs are created under
t.TempDir()and cleaned automatically.
- MySQL auth error: verify
database.user/passwordand DB is reachable. - Invalid file magic / integrity check failed: file was encrypted with a different
file_crypto.key, uses an old format, or is corrupted. - Key errors at startup: ensure
file_crypto.keyis valid base64 URL-safe and decodes to at least 32 bytes. - Preview unavailable: install LibreOffice (
libreoffice/soffice). - Malware scan failed: verify
malware_scan.commandor install ClamAV.
- Use environment variables or secret manager in production.
- Put Nginx/Traefik in front of the Go server for TLS.
- Back up
storage/and DB together.
Open an issue before large changes. Keep changes small and include tests where possible.

