[{"data":1,"prerenderedAt":1069},["ShallowReactive",2],{"navigation":3,"-examples-end-to-end-encryption":167,"-examples-end-to-end-encryption-surround":1066},[4,22,78,106,141,148],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","0.Getting-Started\u002F0.index",[9,10,14,18],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","0.Getting-Started\u002F1.installation",{"title":15,"path":16,"stem":17},"Quickstart","\u002Fgetting-started\u002Fquickstart","0.Getting-Started\u002F2.quickstart",{"title":19,"path":20,"stem":21},"Core concepts","\u002Fgetting-started\u002Fcore-concepts","0.Getting-Started\u002F3.core-concepts",{"title":23,"path":24,"stem":25,"children":26,"icon":28},"JWT","\u002Fjwt","1.JWT\u002F0.index",[27,29,52],{"title":23,"path":24,"stem":25,"icon":28},"i-carbon-certificate",{"title":30,"path":31,"stem":32,"children":33,"icon":35},"JWS","\u002Fjwt\u002Fjws","1.JWT\u002F1.JWS\u002F0.index",[34,36,40,44,48],{"title":30,"path":31,"stem":32,"icon":35},"i-carbon-document-signed",{"title":37,"path":38,"stem":39},"Signing","\u002Fjwt\u002Fjws\u002Fsigning","1.JWT\u002F1.JWS\u002F1.signing",{"title":41,"path":42,"stem":43},"Verifying","\u002Fjwt\u002Fjws\u002Fverifying","1.JWT\u002F1.JWS\u002F2.verifying",{"title":45,"path":46,"stem":47},"Multi-signature","\u002Fjwt\u002Fjws\u002Fmulti-signature","1.JWT\u002F1.JWS\u002F3.multi-signature",{"title":49,"path":50,"stem":51},"Algorithms","\u002Fjwt\u002Fjws\u002Falgorithms","1.JWT\u002F1.JWS\u002F4.algorithms",{"title":53,"path":54,"stem":55,"children":56,"icon":58},"JWE","\u002Fjwt\u002Fjwe","1.JWT\u002F2.JWE\u002F0.index",[57,59,63,67,71,75],{"title":53,"path":54,"stem":55,"icon":58},"i-carbon-locked",{"title":60,"path":61,"stem":62},"Encrypting","\u002Fjwt\u002Fjwe\u002Fencrypting","1.JWT\u002F2.JWE\u002F1.encrypting",{"title":64,"path":65,"stem":66},"Decrypting","\u002Fjwt\u002Fjwe\u002Fdecrypting","1.JWT\u002F2.JWE\u002F2.decrypting",{"title":68,"path":69,"stem":70},"Multi-recipient","\u002Fjwt\u002Fjwe\u002Fmulti-recipient","1.JWT\u002F2.JWE\u002F3.multi-recipient",{"title":72,"path":73,"stem":74},"ECDH-ES and end-to-end encryption","\u002Fjwt\u002Fjwe\u002Fecdh-es","1.JWT\u002F2.JWE\u002F4.ecdh-es",{"title":49,"path":76,"stem":77},"\u002Fjwt\u002Fjwe\u002Falgorithms","1.JWT\u002F2.JWE\u002F5.algorithms",{"title":79,"path":80,"stem":81,"children":82,"icon":84},"Examples","\u002Fexamples","10.Examples\u002F0.index",[83,85,90,94,98,102],{"title":79,"path":80,"stem":81,"icon":84},"i-carbon-code-reference",{"title":86,"path":87,"stem":88,"icon":89},"Authentication basics","\u002Fexamples\u002Fauthentication-basics","10.Examples\u002F1.authentication-basics","i-lucide-code",{"title":91,"path":92,"stem":93,"icon":89},"Consuming a JWKS endpoint","\u002Fexamples\u002Fjwks-endpoint","10.Examples\u002F2.jwks-endpoint",{"title":95,"path":96,"stem":97,"icon":89},"Refresh token pattern","\u002Fexamples\u002Frefresh-token-pattern","10.Examples\u002F3.refresh-token-pattern",{"title":99,"path":100,"stem":101,"icon":89},"End-to-end encryption","\u002Fexamples\u002Fend-to-end-encryption","10.Examples\u002F4.end-to-end-encryption",{"title":103,"path":104,"stem":105,"icon":89},"Signed receipts","\u002Fexamples\u002Fsigned-receipts","10.Examples\u002F5.signed-receipts",{"title":107,"path":108,"stem":109,"children":110,"icon":112},"JWK","\u002Fjwk","2.JWK\u002F0.index",[111,113,117,121,125,129,133,137],{"title":107,"path":108,"stem":109,"icon":112},"i-carbon-two-factor-authentication",{"title":114,"path":115,"stem":116},"Generating keys","\u002Fjwk\u002Fgenerating","2.JWK\u002F1.generating",{"title":118,"path":119,"stem":120},"Importing and exporting","\u002Fjwk\u002Fimport-export","2.JWK\u002F2.import-export",{"title":122,"path":123,"stem":124},"PEM conversion","\u002Fjwk\u002Fpem","2.JWK\u002F3.pem",{"title":126,"path":127,"stem":128},"Key wrapping","\u002Fjwk\u002Fwrapping","2.JWK\u002F4.wrapping",{"title":130,"path":131,"stem":132},"Password derivation","\u002Fjwk\u002Fpassword-derivation","2.JWK\u002F5.password-derivation",{"title":134,"path":135,"stem":136},"JWK Sets","\u002Fjwk\u002Fjwk-sets","2.JWK\u002F6.jwk-sets",{"title":138,"path":139,"stem":140},"JWK cache","\u002Fjwk\u002Fcache","2.JWK\u002F7.cache",{"title":142,"path":143,"stem":144,"children":145,"icon":147},"Utilities","\u002Futilities","3.Utilities\u002F0.index",[146],{"title":142,"path":143,"stem":144,"icon":147},"i-carbon-tool-box",{"title":149,"path":150,"stem":151,"children":152,"icon":154},"Adapters","\u002Fadapters","99.Adapters\u002F0.index",[153,155,159,163],{"title":149,"path":150,"stem":151,"icon":154},"i-carbon-plug",{"title":156,"path":157,"stem":158},"H3 sessions","\u002Fadapters\u002Fh3-sessions","99.Adapters\u002F1.h3-sessions",{"title":160,"path":161,"stem":162},"Lifecycle hooks","\u002Fadapters\u002Fhooks","99.Adapters\u002F2.hooks",{"title":164,"path":165,"stem":166},"Lower-level functions","\u002Fadapters\u002Flower-level","99.Adapters\u002F3.lower-level",{"id":168,"title":99,"body":169,"description":215,"extension":1061,"meta":1062,"navigation":1063,"path":100,"seo":1064,"stem":101,"__hash__":1065},"content\u002F10.Examples\u002F4.end-to-end-encryption.md",{"type":170,"value":171,"toc":1051,"icon":89},"minimark",[172,185,200,205,208,303,310,314,317,454,457,489,499,510,514,517,680,683,687,700,787,790,846,857,875,879,897,946,950,963,993,996,1000,1026,1030,1047],[173,174,175,176,180,181,184],"p",{},"In an ",[177,178,179],"strong",{},"end-to-end encrypted"," (E2EE) system, each participant holds a ",[177,182,183],{},"key pair",": the private key lives on their device and never leaves; the public key is shared openly. Senders encrypt with the recipient's public key; only the recipient's private key can decrypt.",[173,186,187,188,191,192,199],{},"unjwt's public-key encryption uses ",[177,189,190],{},"ECDH-ES"," (",[193,194,198],"a",{"href":195,"rel":196},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc7518#section-4.6",[197],"nofollow","RFC 7518 §4.6",") — elliptic-curve Diffie-Hellman with ephemeral keys — with optional AES Key Wrap.",[201,202,204],"h2",{"id":203},"key-setup-once-per-participant","Key setup — once per participant",[173,206,207],{},"Every participant generates their key pair once (at account creation, app install, etc.) and persists it:",[209,210,216],"pre",{"className":211,"code":212,"filename":213,"language":214,"meta":215,"style":215},"language-ts shiki shiki-themes github-light github-dark github-dark","import { generateJWK } from \"unjwt\u002Fjwk\";\n\n\u002F\u002F Alice generates her key pair\nconst aliceKeys = await generateJWK(\"ECDH-ES+A256KW\", { kid: \"alice-2025\" });\n\u002F\u002F aliceKeys.privateKey — store securely on Alice's device\n\u002F\u002F aliceKeys.publicKey  — publish openly (e.g. at \u002F.well-known\u002Fjwks.json)\n","setup-alice.ts","ts","",[217,218,219,242,249,256,291,297],"code",{"__ignoreMap":215},[220,221,224,228,232,235,239],"span",{"class":222,"line":223},"line",1,[220,225,227],{"class":226},"so5gQ","import",[220,229,231],{"class":230},"slsVL"," { generateJWK } ",[220,233,234],{"class":226},"from",[220,236,238],{"class":237},"sfrk1"," \"unjwt\u002Fjwk\"",[220,240,241],{"class":230},";\n",[220,243,245],{"class":222,"line":244},2,[220,246,248],{"emptyLinePlaceholder":247},true,"\n",[220,250,252],{"class":222,"line":251},3,[220,253,255],{"class":254},"sCsY4","\u002F\u002F Alice generates her key pair\n",[220,257,259,262,266,269,272,276,279,282,285,288],{"class":222,"line":258},4,[220,260,261],{"class":226},"const",[220,263,265],{"class":264},"suiK_"," aliceKeys",[220,267,268],{"class":226}," =",[220,270,271],{"class":226}," await",[220,273,275],{"class":274},"shcOC"," generateJWK",[220,277,278],{"class":230},"(",[220,280,281],{"class":237},"\"ECDH-ES+A256KW\"",[220,283,284],{"class":230},", { kid: ",[220,286,287],{"class":237},"\"alice-2025\"",[220,289,290],{"class":230}," });\n",[220,292,294],{"class":222,"line":293},5,[220,295,296],{"class":254},"\u002F\u002F aliceKeys.privateKey — store securely on Alice's device\n",[220,298,300],{"class":222,"line":299},6,[220,301,302],{"class":254},"\u002F\u002F aliceKeys.publicKey  — publish openly (e.g. at \u002F.well-known\u002Fjwks.json)\n",[173,304,305,306,309],{},"The public key is shared out-of-band — through a user profile page, a JWKS endpoint, a shared directory, a QR code at registration. ",[177,307,308],{},"Cryptographically",", the system only needs authentic delivery of the public key; it doesn't need to be secret.",[201,311,313],{"id":312},"one-to-one-sending-a-private-message","One-to-one — sending a private message",[173,315,316],{},"Alice wants to send a message only Bob can read:",[209,318,321],{"className":211,"code":319,"filename":320,"language":214,"meta":215,"style":215},"import { encrypt, decrypt } from \"unjwt\u002Fjwe\";\n\n\u002F\u002F ── On Alice's device ──\nconst token = await encrypt(\n  { message: \"Hello Bob! Meeting at 3pm.\", from: \"alice\" },\n  bobPublicKey, \u002F\u002F fetched from Bob's profile\n);\n\u002F\u002F `token` is a compact JWE string. Send via any channel — email, HTTP, etc.\n\n\u002F\u002F ── On Bob's device ──\nconst { payload } = await decrypt(token, bobPrivateKey);\nconsole.log(payload);\n\u002F\u002F { message: \"Hello Bob! Meeting at 3pm.\", from: \"alice\" }\n","alice-to-bob.ts",[217,322,323,337,341,346,363,380,388,394,400,405,411,436,448],{"__ignoreMap":215},[220,324,325,327,330,332,335],{"class":222,"line":223},[220,326,227],{"class":226},[220,328,329],{"class":230}," { encrypt, decrypt } ",[220,331,234],{"class":226},[220,333,334],{"class":237}," \"unjwt\u002Fjwe\"",[220,336,241],{"class":230},[220,338,339],{"class":222,"line":244},[220,340,248],{"emptyLinePlaceholder":247},[220,342,343],{"class":222,"line":251},[220,344,345],{"class":254},"\u002F\u002F ── On Alice's device ──\n",[220,347,348,350,353,355,357,360],{"class":222,"line":258},[220,349,261],{"class":226},[220,351,352],{"class":264}," token",[220,354,268],{"class":226},[220,356,271],{"class":226},[220,358,359],{"class":274}," encrypt",[220,361,362],{"class":230},"(\n",[220,364,365,368,371,374,377],{"class":222,"line":293},[220,366,367],{"class":230},"  { message: ",[220,369,370],{"class":237},"\"Hello Bob! Meeting at 3pm.\"",[220,372,373],{"class":230},", from: ",[220,375,376],{"class":237},"\"alice\"",[220,378,379],{"class":230}," },\n",[220,381,382,385],{"class":222,"line":299},[220,383,384],{"class":230},"  bobPublicKey, ",[220,386,387],{"class":254},"\u002F\u002F fetched from Bob's profile\n",[220,389,391],{"class":222,"line":390},7,[220,392,393],{"class":230},");\n",[220,395,397],{"class":222,"line":396},8,[220,398,399],{"class":254},"\u002F\u002F `token` is a compact JWE string. Send via any channel — email, HTTP, etc.\n",[220,401,403],{"class":222,"line":402},9,[220,404,248],{"emptyLinePlaceholder":247},[220,406,408],{"class":222,"line":407},10,[220,409,410],{"class":254},"\u002F\u002F ── On Bob's device ──\n",[220,412,414,416,419,422,425,428,430,433],{"class":222,"line":413},11,[220,415,261],{"class":226},[220,417,418],{"class":230}," { ",[220,420,421],{"class":264},"payload",[220,423,424],{"class":230}," } ",[220,426,427],{"class":226},"=",[220,429,271],{"class":226},[220,431,432],{"class":274}," decrypt",[220,434,435],{"class":230},"(token, bobPrivateKey);\n",[220,437,439,442,445],{"class":222,"line":438},12,[220,440,441],{"class":230},"console.",[220,443,444],{"class":274},"log",[220,446,447],{"class":230},"(payload);\n",[220,449,451],{"class":222,"line":450},13,[220,452,453],{"class":254},"\u002F\u002F { message: \"Hello Bob! Meeting at 3pm.\", from: \"alice\" }\n",[173,455,456],{},"Under the hood, unjwt:",[458,459,461,469,476,479,482],"steps",{"level":460},"4",[462,463,464,465,468],"h4",{},"Generates a fresh ",[177,466,467],{},"ephemeral key pair"," on Alice's side (one-time use).",[462,470,471,472,475],{},"Derives a shared secret from ",[217,473,474],{},"ephemeralPrivate × bobPublic"," (ECDH).",[462,477,478],{},"Uses the shared secret as a KEK to wrap a random CEK.",[462,480,481],{},"Encrypts the payload with the CEK (AES-GCM).",[462,483,484,485,488],{},"Writes the ephemeral public key (",[217,486,487],{},"epk",") into the JWE header.",[173,490,491,492,495,496,498],{},"Bob re-derives the same shared secret using ",[217,493,494],{},"bobPrivate × ephemeralPublic"," (from the ",[217,497,487],{}," header), unwraps the CEK, decrypts.",[173,500,501,502,505,506,509],{},"The ephemeral private key is ",[177,503,504],{},"discarded after encryption"," — this is where ",[177,507,508],{},"forward secrecy"," comes from. Even if Bob's long-term key is stolen later, past messages can't be decrypted because the ephemeral private key is gone.",[201,511,513],{"id":512},"one-to-many-simple-fan-out","One-to-many — simple fan-out",[173,515,516],{},"The easiest way to reach multiple recipients is one token per recipient:",[209,518,521],{"className":211,"code":519,"filename":520,"language":214,"meta":215,"style":215},"const recipients = [\n  { name: \"bob\", publicKey: bobPublicKey },\n  { name: \"charlie\", publicKey: charliePublicKey },\n  { name: \"dave\", publicKey: davePublicKey },\n];\n\nconst tokens = await Promise.all(\n  recipients.map(({ publicKey }) => encrypt({ msg: \"Team update\" }, publicKey)),\n);\n\n\u002F\u002F Deliver each token to its intended recipient\nawait Promise.all(recipients.map(({ name }, i) => deliver(name, tokens[i])));\n","fan-out.ts",[217,522,523,535,546,556,566,571,575,597,629,633,637,642],{"__ignoreMap":215},[220,524,525,527,530,532],{"class":222,"line":223},[220,526,261],{"class":226},[220,528,529],{"class":264}," recipients",[220,531,268],{"class":226},[220,533,534],{"class":230}," [\n",[220,536,537,540,543],{"class":222,"line":244},[220,538,539],{"class":230},"  { name: ",[220,541,542],{"class":237},"\"bob\"",[220,544,545],{"class":230},", publicKey: bobPublicKey },\n",[220,547,548,550,553],{"class":222,"line":251},[220,549,539],{"class":230},[220,551,552],{"class":237},"\"charlie\"",[220,554,555],{"class":230},", publicKey: charliePublicKey },\n",[220,557,558,560,563],{"class":222,"line":258},[220,559,539],{"class":230},[220,561,562],{"class":237},"\"dave\"",[220,564,565],{"class":230},", publicKey: davePublicKey },\n",[220,567,568],{"class":222,"line":293},[220,569,570],{"class":230},"];\n",[220,572,573],{"class":222,"line":299},[220,574,248],{"emptyLinePlaceholder":247},[220,576,577,579,582,584,586,589,592,595],{"class":222,"line":390},[220,578,261],{"class":226},[220,580,581],{"class":264}," tokens",[220,583,268],{"class":226},[220,585,271],{"class":226},[220,587,588],{"class":264}," Promise",[220,590,591],{"class":230},".",[220,593,594],{"class":274},"all",[220,596,362],{"class":230},[220,598,599,602,605,608,612,615,618,620,623,626],{"class":222,"line":396},[220,600,601],{"class":230},"  recipients.",[220,603,604],{"class":274},"map",[220,606,607],{"class":230},"(({ ",[220,609,611],{"class":610},"sQHwn","publicKey",[220,613,614],{"class":230}," }) ",[220,616,617],{"class":226},"=>",[220,619,359],{"class":274},[220,621,622],{"class":230},"({ msg: ",[220,624,625],{"class":237},"\"Team update\"",[220,627,628],{"class":230}," }, publicKey)),\n",[220,630,631],{"class":222,"line":402},[220,632,393],{"class":230},[220,634,635],{"class":222,"line":407},[220,636,248],{"emptyLinePlaceholder":247},[220,638,639],{"class":222,"line":413},[220,640,641],{"class":254},"\u002F\u002F Deliver each token to its intended recipient\n",[220,643,644,647,649,651,653,656,658,660,663,666,669,672,674,677],{"class":222,"line":438},[220,645,646],{"class":226},"await",[220,648,588],{"class":264},[220,650,591],{"class":230},[220,652,594],{"class":274},[220,654,655],{"class":230},"(recipients.",[220,657,604],{"class":274},[220,659,607],{"class":230},[220,661,662],{"class":610},"name",[220,664,665],{"class":230}," }, ",[220,667,668],{"class":610},"i",[220,670,671],{"class":230},") ",[220,673,617],{"class":226},[220,675,676],{"class":274}," deliver",[220,678,679],{"class":230},"(name, tokens[i])));\n",[173,681,682],{},"Each recipient decrypts their own token independently. The trade-off is bandwidth: the payload is encrypted N times.",[201,684,686],{"id":685},"one-to-many-shared-ciphertext","One-to-many — shared ciphertext",[173,688,689,690,695,696,699],{},"When the payload is large (documents, media, …), re-encrypting per recipient is wasteful. Use ",[193,691,692],{"href":69},[217,693,694],{},"encryptMulti"," to encrypt ",[177,697,698],{},"once"," with a random CEK, then wrap that CEK separately per recipient:",[209,701,704],{"className":211,"code":702,"filename":703,"language":214,"meta":215,"style":215},"import { encryptMulti, decryptMulti } from \"unjwt\u002Fjwe\";\n\nconst jwe = await encryptMulti(\n  { msg: \"Team update\", size: \"100KB\" },\n  [{ key: bobPublicKey }, { key: charliePublicKey }, { key: davePublicKey }],\n  { enc: \"A256GCM\" },\n);\n\n\u002F\u002F `jwe` is a JSON object — stringify and deliver the same structure to everyone.\n\u002F\u002F Each recipient uses their OWN private key to decrypt.\n","multi.ts",[217,705,706,719,723,739,754,759,769,773,777,782],{"__ignoreMap":215},[220,707,708,710,713,715,717],{"class":222,"line":223},[220,709,227],{"class":226},[220,711,712],{"class":230}," { encryptMulti, decryptMulti } ",[220,714,234],{"class":226},[220,716,334],{"class":237},[220,718,241],{"class":230},[220,720,721],{"class":222,"line":244},[220,722,248],{"emptyLinePlaceholder":247},[220,724,725,727,730,732,734,737],{"class":222,"line":251},[220,726,261],{"class":226},[220,728,729],{"class":264}," jwe",[220,731,268],{"class":226},[220,733,271],{"class":226},[220,735,736],{"class":274}," encryptMulti",[220,738,362],{"class":230},[220,740,741,744,746,749,752],{"class":222,"line":258},[220,742,743],{"class":230},"  { msg: ",[220,745,625],{"class":237},[220,747,748],{"class":230},", size: ",[220,750,751],{"class":237},"\"100KB\"",[220,753,379],{"class":230},[220,755,756],{"class":222,"line":293},[220,757,758],{"class":230},"  [{ key: bobPublicKey }, { key: charliePublicKey }, { key: davePublicKey }],\n",[220,760,761,764,767],{"class":222,"line":299},[220,762,763],{"class":230},"  { enc: ",[220,765,766],{"class":237},"\"A256GCM\"",[220,768,379],{"class":230},[220,770,771],{"class":222,"line":390},[220,772,393],{"class":230},[220,774,775],{"class":222,"line":396},[220,776,248],{"emptyLinePlaceholder":247},[220,778,779],{"class":222,"line":402},[220,780,781],{"class":254},"\u002F\u002F `jwe` is a JSON object — stringify and deliver the same structure to everyone.\n",[220,783,784],{"class":222,"line":407},[220,785,786],{"class":254},"\u002F\u002F Each recipient uses their OWN private key to decrypt.\n",[173,788,789],{},"On the recipient side:",[209,791,794],{"className":211,"code":792,"filename":793,"language":214,"meta":215,"style":215},"const { payload, recipientIndex } = await decryptMulti(jwe, myPrivateKey);\nconsole.log(payload); \u002F\u002F { msg: \"Team update\", size: \"100KB\" }\nconsole.log(recipientIndex); \u002F\u002F which entry of jwe.recipients[] matched your key\n","multi-decrypt.ts",[217,795,796,822,834],{"__ignoreMap":215},[220,797,798,800,802,804,807,810,812,814,816,819],{"class":222,"line":223},[220,799,261],{"class":226},[220,801,418],{"class":230},[220,803,421],{"class":264},[220,805,806],{"class":230},", ",[220,808,809],{"class":264},"recipientIndex",[220,811,424],{"class":230},[220,813,427],{"class":226},[220,815,271],{"class":226},[220,817,818],{"class":274}," decryptMulti",[220,820,821],{"class":230},"(jwe, myPrivateKey);\n",[220,823,824,826,828,831],{"class":222,"line":244},[220,825,441],{"class":230},[220,827,444],{"class":274},[220,829,830],{"class":230},"(payload); ",[220,832,833],{"class":254},"\u002F\u002F { msg: \"Team update\", size: \"100KB\" }\n",[220,835,836,838,840,843],{"class":222,"line":251},[220,837,441],{"class":230},[220,839,444],{"class":274},[220,841,842],{"class":230},"(recipientIndex); ",[220,844,845],{"class":254},"\u002F\u002F which entry of jwe.recipients[] matched your key\n",[173,847,848,849,852,853,856],{},"Everyone receives the same ",[217,850,851],{},"jwe.ciphertext"," segment; each recipient's ",[217,854,855],{},"encrypted_key"," is wrapped for their key alone.",[858,859,860,867],"tip",{},[173,861,862,863,866],{},"Use ",[177,864,865],{},"fan-out"," (one token per recipient) for small messages to a few recipients — simpler, cleaner isolation.",[173,868,862,869,191,872,874],{},[177,870,871],{},"shared ciphertext",[217,873,694],{},") for large payloads or wide fanout — much less bandwidth, and delivery can piggyback on the same transport.",[201,876,878],{"id":877},"what-if-i-need-the-raw-shared-secret","What if I need the raw shared secret?",[173,880,881,882,885,886,889,890,896],{},"The layered \"derive → wrap → encrypt\" flow above is what ",[217,883,884],{},"encrypt()"," handles internally. If you need just the ",[177,887,888],{},"derived shared secret"," — for a custom hybrid protocol, a non-JWE wrapper, or interop testing — use ",[193,891,893],{"href":892},"\u002Fjwt\u002Fjwe\u002Fecdh-es#derivesharedsecret-the-raw-kdf-step",[217,894,895],{},"deriveSharedSecret",":",[209,898,901],{"className":211,"code":899,"filename":900,"language":214,"meta":215,"style":215},"import { deriveSharedSecret } from \"unjwt\u002Fjwk\";\n\nconst sharedBytes = await deriveSharedSecret(theirPublicKey, myPrivateKey, \"ECDH-ES+A256KW\");\n\u002F\u002F sharedBytes: Uint8Array of derived material, ready for any cipher you like\n","raw-secret.ts",[217,902,903,916,920,941],{"__ignoreMap":215},[220,904,905,907,910,912,914],{"class":222,"line":223},[220,906,227],{"class":226},[220,908,909],{"class":230}," { deriveSharedSecret } ",[220,911,234],{"class":226},[220,913,238],{"class":237},[220,915,241],{"class":230},[220,917,918],{"class":222,"line":244},[220,919,248],{"emptyLinePlaceholder":247},[220,921,922,924,927,929,931,934,937,939],{"class":222,"line":251},[220,923,261],{"class":226},[220,925,926],{"class":264}," sharedBytes",[220,928,268],{"class":226},[220,930,271],{"class":226},[220,932,933],{"class":274}," deriveSharedSecret",[220,935,936],{"class":230},"(theirPublicKey, myPrivateKey, ",[220,938,281],{"class":237},[220,940,393],{"class":230},[220,942,943],{"class":222,"line":258},[220,944,945],{"class":254},"\u002F\u002F sharedBytes: Uint8Array of derived material, ready for any cipher you like\n",[201,947,949],{"id":948},"curves-picking-one","Curves — picking one",[173,951,952,955,956,959,960,896],{},[217,953,954],{},"generateJWK(\"ECDH-ES+A256KW\")"," defaults to ",[217,957,958],{},"P-256"," (NIST curve). For new systems, consider ",[217,961,962],{},"X25519",[209,964,966],{"className":211,"code":965,"language":214,"meta":215,"style":215},"const keys = await generateJWK(\"ECDH-ES+A256KW\", { namedCurve: \"X25519\" });\n",[217,967,968],{"__ignoreMap":215},[220,969,970,972,975,977,979,981,983,985,988,991],{"class":222,"line":223},[220,971,261],{"class":226},[220,973,974],{"class":264}," keys",[220,976,268],{"class":226},[220,978,271],{"class":226},[220,980,275],{"class":274},[220,982,278],{"class":230},[220,984,281],{"class":237},[220,986,987],{"class":230},", { namedCurve: ",[220,989,990],{"class":237},"\"X25519\"",[220,992,290],{"class":230},[173,994,995],{},"X25519 is modern, constant-time, and compact. It's interoperable with any unjwt consumer — the JWE header carries the curve identifier so the recipient uses the right math automatically.",[201,997,999],{"id":998},"security-notes","Security notes",[1001,1002,1003,1010,1016],"ul",{},[1004,1005,1006,1009],"li",{},[177,1007,1008],{},"Public keys must be authentic."," Nothing unjwt (or any crypto library) can do protects against a MITM who substitutes their own public key before delivery. Use TLS, code signing, or out-of-band verification (e.g. safety-number comparison) to establish authenticity.",[1004,1011,1012,1015],{},[177,1013,1014],{},"Private keys stay on-device."," Never transmit or serialize them to untrusted storage. Use a secure enclave \u002F keychain where available.",[1004,1017,1018,1021,1022,1025],{},[177,1019,1020],{},"Forward secrecy depends on ephemeral-key discarding."," unjwt generates them internally and drops references — don't pass your own long-term key as ",[217,1023,1024],{},"options.ecdh.ephemeralKey"," unless you understand the consequences.",[201,1027,1029],{"id":1028},"see-also","See also",[1001,1031,1032,1037,1042],{},[1004,1033,1034],{},[193,1035,1036],{"href":73},"JWE ECDH-ES →",[1004,1038,1039],{},[193,1040,1041],{"href":69},"JWE multi-recipient →",[1004,1043,1044],{},[193,1045,1046],{"href":115},"JWK generation →",[1048,1049,1050],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":215,"searchDepth":244,"depth":244,"links":1052},[1053,1054,1055,1056,1057,1058,1059,1060],{"id":203,"depth":244,"text":204},{"id":312,"depth":244,"text":313},{"id":512,"depth":244,"text":513},{"id":685,"depth":244,"text":686},{"id":877,"depth":244,"text":878},{"id":948,"depth":244,"text":949},{"id":998,"depth":244,"text":999},{"id":1028,"depth":244,"text":1029},"md",{"icon":89},{"icon":89},{"title":99,"description":215},"on_B3KknmF1S_f-1BTry1cPEzTqDBu5rM89zXrzkJkk",[1067,1068],{"title":95,"path":96,"stem":97,"description":215,"icon":89,"children":-1},{"title":103,"path":104,"stem":105,"description":215,"icon":89,"children":-1},1776888559111]