{"id":29128983,"url":"https://github.com/nico-martin/secure-message-link","last_synced_at":"2025-06-30T02:37:42.405Z","repository":{"id":264454436,"uuid":"893393616","full_name":"nico-martin/secure-message-link","owner":"nico-martin","description":"An app that allows you encrypt a message and share it with a link.","archived":false,"fork":false,"pushed_at":"2024-12-09T22:58:27.000Z","size":283,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-09T23:22:34.239Z","etag":null,"topics":["webcrypto-api"],"latest_commit_sha":null,"homepage":"https://notes.nico.dev/","language":"TypeScript","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/nico-martin.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":"2024-11-24T10:31:08.000Z","updated_at":"2024-12-09T22:58:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"9c2ba917-6022-4593-8643-8bd36b52ecfd","html_url":"https://github.com/nico-martin/secure-message-link","commit_stats":null,"previous_names":["nico-martin/secure-message-link"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nico-martin/secure-message-link","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-martin%2Fsecure-message-link","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-martin%2Fsecure-message-link/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-martin%2Fsecure-message-link/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-martin%2Fsecure-message-link/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nico-martin","download_url":"https://codeload.github.com/nico-martin/secure-message-link/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-martin%2Fsecure-message-link/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262700269,"owners_count":23350444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["webcrypto-api"],"created_at":"2025-06-30T02:37:41.763Z","updated_at":"2025-06-30T02:37:42.372Z","avatar_url":"https://github.com/nico-martin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Secure Message Link\n\nAn app that allows you encrypt a message and share it with a link.\n\n## How it works\n\nMy goal was to create a simple way to share a private message via a URL. And in the safest possible way\n\n### Generate the message\n\nThe encryption process is done in the browser, using the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).\n\n1. A random password is generated (as a uuidv4) and converted to binary format\n2. Salt: A random 16-byte value ensures unique encryption, even with the same password\n3. Key Derivation: A secure key is created from the password, salt, 100,000 iterations, and SHA-256\n4. AES Key: The derived key is turned into an encryption key for AES-GCM\n5. Initialization Vector (IV): A 12-byte random value ensures secure encryption. \n6. AES-GCM: Encrypts the message using the derived key and IV. \n7. Output: The function returns the encrypted message (ciphertext), IV, and salt for decryption.\n\nOnce the ciphertext, the salt and the iv value have been generated, they are stored in a postgress database (using [Supabase](https://supabase.com/)).  \n\nThe ID of the entry is then packed together with the password into a base64 string, which this is then the hash of the link to be sent.\n\n### Open the message\n\nWhen the link is opened, the hash is decoded, and the password and the ID are extracted. After the confirmation of the user, the encrypted message is fetched (and deleted) from the database.  \n\nThe password is then used to decrypt the message, and the result is displayed to the user.\n\n### Security\n\nSince the password is never sent to the server, **I have no way of decrypting the messages from the database.**    \nOnly the person with the link (and the password it contains) can do this.\n\n## Backend\n\nFor the backend I used [Supabase](https://supabase.com/), a service that provides a Postgres database and an API to interact with it.  \nHowever, this would work with pretty much any database technology, as the complex processes run directly in the browser.\n\n### Messages Database\n\n```sql\nCREATE TABLE messages (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  ciphertext TEXT NOT NULL,\n  iv TEXT NOT NULL,\n  salt TEXT NOT NULL,\n  expires TIMESTAMP NOT NULL\n);\n```\n\n### Insert Message\nSince I am using RLS (Row Level Security) on the table and the user is anonymous, I need to use a function to insert the message..\n\n```sql\nCREATE OR REPLACE FUNCTION public.insert_message(\n  ciphertext TEXT,\n  iv TEXT,\n  salt TEXT,\n  expires TIMESTAMP\n) RETURNS UUID\nSECURITY DEFINER\nAS $$\nDECLARE\n  new_id UUID;\nBEGIN\n  INSERT INTO public.messages (ciphertext, iv, salt, expires)\n  VALUES (ciphertext, iv, salt, expires)\n  RETURNING id INTO new_id;\n\n  RETURN new_id;\nEND;\n$$ LANGUAGE plpgsql;\n```\n\n### Get Message by ID\n\n..and another function to then get the message by ID and delete it.\n\n```sql\nCREATE OR REPLACE FUNCTION public.get_message_by_id(\n  message_id UUID\n) RETURNS public.messages\nSECURITY DEFINER\nAS $$\nDECLARE\n  result public.messages;\nBEGIN\n  SELECT * INTO result FROM public.messages WHERE id = message_id AND expires \u003e NOW();\n  DELETE FROM public.messages WHERE id = message_id;\n\n  RETURN result;\nEND;\n$$ LANGUAGE plpgsql;\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnico-martin%2Fsecure-message-link","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnico-martin%2Fsecure-message-link","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnico-martin%2Fsecure-message-link/lists"}