{"id":48050222,"url":"https://github.com/bain3/f.bain","last_synced_at":"2026-04-04T14:21:01.901Z","repository":{"id":43752613,"uuid":"294510733","full_name":"bain3/f.bain","owner":"bain3","description":"E2EE file uploading website","archived":false,"fork":false,"pushed_at":"2024-08-23T22:30:33.000Z","size":2618,"stargazers_count":15,"open_issues_count":5,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-08-23T23:37:24.525Z","etag":null,"topics":["cryptography","security","storage","website"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bain3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-10T20:03:46.000Z","updated_at":"2024-08-23T22:30:36.000Z","dependencies_parsed_at":"2024-08-23T23:46:12.254Z","dependency_job_id":null,"html_url":"https://github.com/bain3/f.bain","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bain3/f.bain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bain3%2Ff.bain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bain3%2Ff.bain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bain3%2Ff.bain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bain3%2Ff.bain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bain3","download_url":"https://codeload.github.com/bain3/f.bain/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bain3%2Ff.bain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31402341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","security","storage","website"],"created_at":"2026-04-04T14:21:01.742Z","updated_at":"2026-04-04T14:21:01.875Z","avatar_url":"https://github.com/bain3.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [f.bain](https://f.bain.cz/)\nSources for my end to end encrypted file sharing website. The server has no knowledge about the file uploaded,\nexcept for its size.\n\nUsing f.bain as a website was always the intended way, but to increase the security even further a native\ncommand line utility was created ([fget](https://github.com/bain3/fget)). By using a native application you\nremove the risk of being served malicious javascript from a potentially compromised server.\n\n## Deployment\nDeploying this website isn't very complicated.\n1. Clone this repo\n2. Take a look at `docker-compose.yml` and change the environment variables if you want to\n3. Use docker-compose to build and run the api\n4. Install a reverse proxy (for example nginx or Caddy) and set it up like so:\n   - try responding with a file from `/static` (e.g. index.html)\n   - if the file can't be found, reverse proxy the request to the API.\n   - if the API returns an error, return the static file corresponding to the error.\n     Although you can find countless tutorials online on how to do this, \n     here are some examples:\n\n\u003cdetails\u003e\u003csummary\u003eNginx configuration example\u003c/summary\u003e\n\n```nginx\nserver {\n    root /path/to/static;\n    error_log off;\n    access_log off;\n    index index.html;\n\n    client_max_body_size 0;\n\n    server_name example.com;\n\n    error_page 404 /404.html;\n    error_page 429 /429.html;\n\n    location / {\n        try_files $uri $uri/ @proxy_pass;\n    }\n\n    location @proxy_pass {\n        proxy_intercept_errors on;\n        proxy_pass http://localhost:3333;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"Upgrade\";\n        proxy_set_header Host $host;\n    }\n\n    listen [::]:443 ssl http2;\n    listen 443 ssl http2;\n    # SSL configuration\n}\n\nserver {\n    if ($host = example.com) {\n        return 301 https://$host$request_uri;\n    }\n\n    server_name example.com;\n    listen [::]:80;\n    listen 80;\n    return 404;\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eCaddy configuration example\u003c/summary\u003e\n\n[//]: # (While Caddyfiles are not Python, it is the closest highlighting we can get.)\n\n```python\n# Replace f.example.com with your (sub) domain.\nf.example.com {\n    # copy or mount the static directory of the repo to /srv/f-bain\n    root * /srv/f-bain\n    encode gzip\n    file_server\n\n    @reverse_proxy {\n        not file {\n            try_files {path} {path}/index.html\n        }\n    }\n\t\n    # Forward requests that are not for static files to the API\n    # (change hostname \u0026 port if needed)\n    reverse_proxy @reverse_proxy f-bain-api:80 {\n        @error status 404 429\n        handle_response @error {\n            rewrite * /{rp.status_code}.html\n            file_server\n        }\n    }\n\n}\n```\n\u003c/details\u003e\n\n5. Set up HTTPS for basic security\n   \n## How does it work?\n### Short description\nYour browser encrypts the file locally before sending it to the server. The key is carefully placed inside the url,\nso it isn't sent when you request the file back. This way the server has no knowledge about the contents of the file\nand only you have the key to decrypt the data.\n\n### Long description\n#### Upload\nOnce the user selects a file, the javascript will generate a random string as a password from a key alphabet.\nThe special key alphabet maximises density of data in the url without sacrificing security.\nIt will also generate \"salt\" for the PBKDF2 key derivation function. This salt will be later sent to\nthe server as metadata. It is essentially to protect other files on the server. This way an attacker\ncan't steal all the files stored on the server and break all the passwords in bulk.\n\nThe next step (still in the browser) is to strengthen and stretch the password using the mentioned [PBKDF2][pbkdf2]\nkey derivation function. The result of this function will be a 768 bit key.\n\nWe split this 768 bit key into three 256 bit parts which we will be using as our key, iv and filename iv.\n\nNext up is encrypting the file using [AES in GCM mode][aesgcm]. Since we want to support bigger files, we\nsplit it into blocks of 5 megabytes, and generate new ivs for every new block, which we add to the start \nof the previous block.\n```\n[iv1 + data (encrypted using og iv)][tag]\n[iv2 + data (encrypted using iv1)  ][tag]\n[iv3 + data (encrypted using iv2)  ][tag]\n...\n```\nEach block will end up being `5242928` bytes long.\nAll of this is periodically added to a blob to not make the browser crash, and is the longest part.\n\nThe last step is to encrypt the filename in AES-GCM with our last iv and encode it in base64.\n\nThe encrypted file, filename and the salt is sent to the server, which then responds with an id.\nThe browser constructs the final url locally.\n```\nhttps://f.bain.cz/\u003cid\u003e#\u003cpassword\u003e\n```\n\nSince the password is set as a fragment (after the `#`) it is not sent to the server when you request the file\nback.\n\n#### Download\n*(I recommend reading the upload part to fully understand the download)*\n\nDownload is simpler than upload. First off the browser parses the url for the id and password.\n\nUsing the id it gathers metadata about the file (the encrypted file name and the salt) from `/\u003cid\u003e/meta`. \nIt then derives the 768 bit key from the password.\n\nAfter that the browser downloads the encrypted data (from `/\u003cid\u003e/raw`)(everything stored as a \nblob to prevent browser crashing), and starts to decrypt the `5242928` byte long blocks. \nEach time splitting the block into the next iv and decrypted data, setting the next iv, and \nstoring the decrypted data into an output blob.\n\nThen it quickly decrypts the file name, and creates a fake link element with it pointing to the output\nblob, containing the decrypted data. At last it invokes a click on the element which makes the browser save \nthe file.\n\nGGs if you actually read all of it!\n\n## Help appreciated! (and a disclaimer)\nI am just an amateur who wanted to make myself a pretty secure website where to drop off my files (and\nto practice some cryptography). I am in no way certified in this field, so if you know how all of this\nworks and/or have found out that I badly implemented parts of this, please contact me on [keybase][kb],\nor by any other means (contact info at bain.cz). Thank you!\n\n\n# Changing max file size\n```\nPOST /max-filesize/{new_max} (ex. 5K, 500M, 2G)\nAuthorization: TOKEN\n\n(empty body)\n```\n \n\n[pbkdf2]: https://en.wikipedia.org/wiki/PBKDF2\n[aesgcm]: https://en.wikipedia.org/wiki/Galois/Counter_Mode\n[kb]: https://keybase.io/bain3\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbain3%2Ff.bain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbain3%2Ff.bain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbain3%2Ff.bain/lists"}