GoFile is a small self-hosted file sharing app that performs client-side encryption in the browser before upload.
The server stores only encrypted bytes plus minimal metadata in SQLite. Recipients download the encrypted payload and decrypt locally using a secret code shared out-of-band.
- Client-side encryption (AES-GCM) before any data leaves the device
- Secret code wrapping: the per-upload encryption key is encrypted with a key derived from the secret code
- Share by link: recipients open a URL and provide the secret code to decrypt
- Expiry and download limits
- Single or multi-file uploads
- Single file uploads are stored as-is (encrypted)
- Multiple files / folders are zipped in the browser (JSZip), then encrypted and uploaded as one archive
- Server-side enforcement
- Rejects expired, revoked, or download-limit-exceeded shares
- Atomic download counter increment to avoid race issues
- Large uploads
- Total upload limit is 10 GiB (server-enforced)
-
Upload page (
/)- You select one or more files (or a folder).
- The browser:
- Creates a ZIP archive if multiple files/folders were selected.
- Generates a random 32-byte encryption key.
- Encrypts the file (or ZIP) using AES-GCM.
- Generates a secret code (human-readable).
- Derives a wrapping key from the secret code using PBKDF2 (SHA-256, 100,000 iterations).
- Encrypts (wraps) the random encryption key with the wrapping key (AES-GCM).
- The browser uploads:
- The encrypted file bytes
- The encrypted key (Base64)
- File name, expiry, and download limit
-
Server
- Stores the encrypted blob on disk in
./data/<id>.bin - Stores metadata in
./shares.db(SQLite)
- Stores the encrypted blob on disk in
-
Download page (
/share.html?id=<id>)- The browser fetches metadata (
/api/meta?id=...). - User enters the secret code.
- The browser derives the wrapping key, decrypts the stored encryption key, downloads the encrypted file, and decrypts locally.
- The browser fetches metadata (
- The server never sees the plaintext content of uploaded files.
- The server does receive:
- Encrypted file bytes
- Encrypted encryption key (wrapped key)
- File name and policy metadata (expiry, max downloads)
- The secret code is shown only once after upload; if lost, the content cannot be recovered.
Important: This app is designed for privacy-by-design file transport. It is not a substitute for a full access-control system, auditing, or enterprise key management.
- Maximum upload size (server-enforced): 10 GiB
- Multi-part parsing includes an additional overhead cushion of 32 MiB
- Download limits:
maxDownloads = 0means unlimited- Otherwise enforced atomically on each download request
.
├── server/
│ └── main.go
└── web/
├── index.html
├── share.html
└── assets/
├── styles.css
├── upload.js
├── share.js
└── images/
- Go (recent version recommended)
- No separate database installation required (SQLite file is created locally)
- A modern browser with WebCrypto support (Chrome, Edge, Firefox, Safari)
From the repository root:
cd server
go run .Then open:
- Upload page:
http://localhost:8080/ - Download page:
http://localhost:8080/share.html
On first run, the server creates:
./shares.db(SQLite database)./data/(encrypted blob storage)
- Open
http://localhost:8080/ - Select files or drag and drop (folders supported on compatible browsers)
- Choose:
- Expires in
- Max downloads (0 = unlimited)
- Click Encrypt and upload
- Save:
- The share link
- The secret code
- Send the link and the secret code via different channels.
- Open the share link (example):
http://localhost:8080/share.html?id=<id> - Enter the secret code (format auto-applied)
- Click Decrypt and download
- If the download is a ZIP, extract it to get all files.
Creates a share and uploads encrypted bytes.
- Content-Type:
multipart/form-data - Fields:
file(binary) — encrypted dataencryptedKeyB64(string) — Base64 of AES-GCM wrapped encryption keyfileName(string)expiresInSeconds(string/int)maxDownloads(string/int, optional; defaults to 0)
Response (JSON):
{
"id": "generated-id",
"shareUrl": "/share.html?id=generated-id",
"expiresAt": "2025-01-01T12:00:00Z"
}Returns share metadata used by the download page.
Response (JSON):
{
"id": "generated-id",
"encryptedKeyB64": "base64...",
"fileName": "archive.zip",
"expiresAt": "2025-01-01T12:00:00Z",
"maxDownloads": 0,
"downloadsUsed": 0,
"revoked": false,
"createdAt": "2025-01-01T10:00:00Z"
}Errors:
404Share not found403Expired, revoked, or download limit exceeded
Downloads encrypted bytes and increments the download counter (atomically).
- Response:
application/octet-stream Content-Dispositionis set to:<fileName>.encrypted
Errors:
404Share not found / file not found403Expired, revoked, or download limit exceeded
The following values are currently constants in code:
- Server listen address:
:8080 - Data directory:
./data - SQLite file:
./shares.db - Max upload size:
10 GiB - Multipart overhead cushion:
32 MiB - PBKDF2 parameters (client-side):
- Salt:
gofile-salt-v1 - Iterations:
100000 - Hash:
SHA-256
- Salt:
- Encryption algorithm:
AES-GCM(12-byte IV)
If you intend to change PBKDF2 parameters, keep upload.js and share.js in sync, or old shares will not decrypt.
If you deploy this publicly, consider:
- Running behind a reverse proxy (Nginx/Caddy) with HTTPS
- Setting timeouts and limits at the proxy level (upload size, request timeouts)
- Access controls if you do not want a public upload endpoint
- Monitoring disk usage and scheduling cleanup (current cleanup runs on each create request)
- Backups of
shares.dbif you need metadata history (encrypted blobs are stored in./data)
- Upload fails with “File size exceeds 10GB limit”
- The server enforces a strict 10 GiB limit. Reduce total selection size.
- Download fails with “Invalid secret code.”
- The secret code must match exactly; ensure you copied it correctly.
- Share says expired
- The expiration is enforced on metadata and download endpoints.
- Multiple files download
- You will receive a single ZIP archive; extract it after download.