{"id":19672007,"url":"https://github.com/michelp/signcryption-token-spec","last_synced_at":"2026-02-10T21:02:01.516Z","repository":{"id":145541206,"uuid":"443865042","full_name":"michelp/signcryption-token-spec","owner":"michelp","description":"A binary, encrypted, authenticated token format called a *Sign-Cryption Token* aka SCT.","archived":false,"fork":false,"pushed_at":"2022-01-02T21:43:44.000Z","size":30,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-31T21:38:46.707Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michelp.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":"2022-01-02T20:38:31.000Z","updated_at":"2024-08-26T23:07:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"4bad1d25-5158-4053-a28e-70df750c33b3","html_url":"https://github.com/michelp/signcryption-token-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/michelp/signcryption-token-spec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelp%2Fsigncryption-token-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelp%2Fsigncryption-token-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelp%2Fsigncryption-token-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelp%2Fsigncryption-token-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michelp","download_url":"https://codeload.github.com/michelp/signcryption-token-spec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelp%2Fsigncryption-token-spec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29316374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-10T20:44:44.282Z","status":"ssl_error","status_checked_at":"2026-02-10T20:44:43.393Z","response_time":65,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":[],"created_at":"2024-11-11T17:10:36.756Z","updated_at":"2026-02-10T21:02:01.461Z","avatar_url":"https://github.com/michelp.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# SCT: Libsodium Sign-Cryption Token\n\nA binary, encrypted, authenticated token format called a\n*Sign-Cryption Token* aka SCT.\n\n**NOTE: This is a preliminary sketch of an idea open for public comment.  You should not use this unless you understand the risks of using new and untested cryptographic software.**\n\n## SCT version 0x0000\n\n - SCT requires [`libsodium`](https://github.com/jedisct1/libsodium), including the additional function\n   libraries in [`libsodium-xchacha20-siv`](https://github.com/jedisct1/libsodium-xchacha20-siv) and\n   [`libsodium-signcryption`](https://github.com/jedisct1/libsodium-signcryption).\n\n - SCTs have an encrypted payload and unencrypted authenticated\n   additional data payload. Either payload, but not both may be empty.\n\n - SCT does NOT support shared-secret encryption. All SCT token\n   *producers* MUST have a keypair consisting of public and secret\n   keys.\n\n - Token *consumers* without possession of the recipient secret key\n   CANNOT decrypt the *encrypted* payload.\n\n - Token consumers with the public key of the token producer can\n   verify the authenticity of the *unencrypted* portion of a token.\n\n - SCT gives you no choice of cryptographic algorithm. SCTs MUST use\n   `XChaCha20-SIV` combined mode deterministic authenticated\n   encryption with additional data using\n   `crypto_aead_det_xchacha20_*`.\n\n - The `shared_key` used for encryption MUST be generated by the\n   `crypto_signcrypt_tbsbr_sign_*` API using the sender's secret key\n   and the recipient's public key.  The recipient can then recover\n   this key using `crypto_signcrypt_tbsbr_verify_*`.\n\n - SCT does NOT care about encodings such as JSON or any kind of human\n   readability. The input payloads, sender/recipient ids, and key\n   pairs are binary byte arrays and the output is a base64 character\n   array. Encoding payloads to and from formats like JSON to a byte\n   representation is a trivial operation and entirely up to the user.\n   \n - The output of SCT is a byte string of five encoded base64 sections:\n \n   `\u003cversion\u003e.\u003csender_id\u003e.\u003cciphertext\u003e.\u003ccontext\u003e.\u003csignature\u003e`\n   \n   The function `sodium_bin2base64` can be used to encode using the\n   `sodium_base64_VARIANT_URLSAFE_NO_PADDING` variant.\n\n## Example function with pgsodium\n\nThis is a PostgreSQL function that generates a token:\n\n```sql\nCREATE OR REPLACE FUNCTION crypto_signcrypt_token(\n    sender       bytea,\n    recipient    bytea,\n    sender_sk    bytea,\n    recipient_pk bytea,\n    message      bytea,\n    additional   bytea)\nRETURNS text AS $$\nWITH\n    sign_before AS (\n        SELECT state, shared_key\n        FROM crypto_signcrypt_sign_before(\n            sender,\n            recipient,\n            sender_sk,\n            recipient_pk,\n            additional)\n    ),\n    ciphertext AS (\n        SELECT crypto_aead_det_encrypt(\n            message,\n            additional,\n            b.shared_key\n        ) AS ciphertext\n        FROM sign_before b\n    ),\n    signature AS (\n        SELECT crypto_signcrypt_sign_after(\n            b.state,\n            sender_sk,\n            c.ciphertext\n        ) AS signature\n        FROM\n            sign_before b,\n            ciphertext c\n    )\n    SELECT format(\n        '0000.%s.%s.%s.%s',\n        sodium_bin2base64(sender),\n        sodium_bin2base64(c.ciphertext),\n        sodium_bin2base64(additional),\n        sodium_bin2base64(s.signature))\n    FROM\n        ciphertext c,\n        signature s;\n$$ LANGUAGE SQL STRICT;\n```\n\nHere's an example of a token generated from keypairs.  First create\nkeypairs for `bob` and `alice`.  Typically this would be done in\nseparate edge processes but are shown together here for brevity:\n\n```sql\npostgres=# select public as pk, secret as sk from crypto_signcrypt_new_keypair () \\gset bob_\npostgres=# select public as pk, secret as sk from crypto_signcrypt_new_keypair () \\gset alice_\n```\n\nNow generate a token from bob to alice.  In this exasmple the `sender`\nand `recipient` ids are corresponding public keys, but this is not\nnecessary, the id can be any unique identifier for a user or group:\n\n```sql\npostgres=# select crypto_signcrypt_token(:'bob_pk', :'alice_pk', :'bob_sk', :'alice_pk', 'this is encrypted s3kret message', 'this is unencrpyted additional data');\n\n0000.YvWFDwWg1sBVhwnccoMwDbvw7PzE0SeHvj3g7fDhqBQ.XnPzbou0Rr-NahE3nEGW6EC5QAFvT11iQzAFHu9NjOksdzV61fuftjDfLgU_vZp7IMAfryeoUAGlQCP7h4RM5g.dGhpcyBpcyB1bmVuY3JweXRlZCBhZGRpdGlvbmFsIGRhdGE.qS1slA8qW4J_uKO079VlzKC5BUazG1W67TVuYCqRKgY8CHybwfgho5U_LNGQTZ60nkDxfU4Q9U3o2w2BAwAAAA\n```\n\n## Rational and Comparisons\n\n**The following section are my personal opinions and motivations.**\n\n\"Perfection is achieved, not when there is nothing more to add, but\nwhen there is nothing left to take away.\" - Antoine de Saint Exupery\n\nJWT is bad.  There are endless blog posts on the badness of JWT.  One\nof the big baddnesses is that the token specifies the encryption\nalgorithm in its header, and a baddie can influence the decrypting of\nthe token by choosing the algorithm.  This is called an algorithm\nconfusion attack.\n\nPASETO is better in many ways, for example by specifying specific\nversions, but it also introduced the same flaw that it was meant to\nfix with JWT, an attacker can *choose* either `local` or `public`\ntoken \"purposes\" in an attempt to fool the server with the same kind\nof algorithm confusion attacks.\n\nSo, PASETO version 3 and 4 put a new section in their spec about\n[\"Algorithm Lucidity\"](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/03-Algorithm-Lucidity.md) and how every language MUST use whatever type\nenforcement features exist to safeguard against using the wrong key\nfor the wrong purpose.\n\nSCT does not allow algorithm confusion attacks as there is one and\nonly one algorithm that can cover either or both purposes that `local`\nand `public` serve in PASETO.  SCT does NOT support shared secret\nencryption, and so there is no possibility of Algorithm Lucidity\nattacks being an issue.  All SCT producers MUST have a valid key pair.\n\nPASETO's current two \"purposes\", `local` and `public` are quite\ndifferent, `local` uses shared secret encryption and its payload is\nencrypted and authenticated.  `public` does NO ENCRYPTION and just\nauthenticates its payload data.  These two very different purposes do\nnot overlap.\n\nSCT has combines both purposes using an algorithm called\n*signcryption*.  A token can have encrypted payload, in which case the\nthe decryptor must have the recipient secret key.  But a token can\nalso be authenticated by anyone with the sender's public key.  They\ncan't decrypt the encrypted payload, but the can verify the token is\nauthentic including the unencrypted additional data.\n\nSo now, instead of one token spec with two distinct purposes, and\nrules about algorithm lucidity and type checking, etc, SCT has one and\nonly one token format.  The token can contain an authenticated\nencrypted payload, and/or it can authenticated unencrypted additional\ndata.  If you have the recipient's secret key, you can decrypt it, but\nanyone with the senders public key can authenticate it.\n\nOne last thing I personally don't like about JWT and PASETO is their\nfocus on JSON payloads.  SCT payloads are *bytes*, not JSON or protobuf nor any other specific format.  If you want to\nencode your data into bytes, go for it, every language in the world\ncan trivially do that. But JSON is *not required*.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichelp%2Fsigncryption-token-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichelp%2Fsigncryption-token-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichelp%2Fsigncryption-token-spec/lists"}