[{"data":1,"prerenderedAt":2763},["ShallowReactive",2],{"\u002Fcrypto":3},{"id":4,"title":5,"body":6,"description":2757,"extension":2758,"meta":2759,"navigation":324,"order":141,"package":13,"path":2760,"seo":2761,"stem":5,"__hash__":2762},"docs\u002Fcrypto.md","crypto",{"type":7,"value":8,"toc":2731},"minimark",[9,14,31,92,97,122,126,249,255,263,267,391,400,403,407,412,513,516,674,678,802,806,809,916,920,995,998,1005,1049,1066,1070,1083,1231,1326,1329,1332,1339,1344,1350,1396,1415,1419,1578,1589,1593,1746,1750,1817,1824,1835,1890,1901,1905,2002,2022,2025,2138,2142,2152,2357,2361,2476,2480,2486,2546,2550,2669,2673,2727],[10,11,13],"h1",{"id":12},"alikhalilllnuxt-crypto","@alikhalilll\u002Fnuxt-crypto",[15,16,17,18,22,23,26,27,30],"p",{},"Symmetric encryption for Nuxt 3 \u002F 4 built on the native ",[19,20,21],"strong",{},"Web Crypto API",". Defaults to ",[19,24,25],{},"AES-256-GCM"," with ",[19,28,29],{},"PBKDF2-SHA256"," key derivation.",[32,33,34,50,56,66,75,81],"ul",{},[35,36,37,40,41,45,46,49],"li",{},[19,38,39],{},"Framework-agnostic core"," — ",[42,43,44],"code",{},"createCryptoService()"," works anywhere ",[42,47,48],{},"SubtleCrypto"," exists (browser, Deno, Bun, Node 20+).",[35,51,52,55],{},[19,53,54],{},"Key caching"," — derived keys are cached per salt, so bulk decrypt stays fast even at 100k+ PBKDF2 iterations.",[35,57,58,61,62,65],{},[19,59,60],{},"Pluggable algorithms"," — swap the default AES-GCM implementation for your own ",[42,63,64],{},"CryptoAlgorithm"," without touching the payload envelope.",[35,67,68,40,71,74],{},[19,69,70],{},"Versioned payload format",[42,72,73],{},"v1.{salt}.{iv}.{cipher}"," with clean forward compatibility.",[35,76,77,80],{},[19,78,79],{},"Server-only mode"," — opt into registering the plugin only on the server so the passphrase never ships to the browser bundle.",[35,82,83,86,87,91],{},[19,84,85],{},"Device fingerprint"," ",[88,89,90],"em",{},"(new)"," — optional HttpOnly-cookie-based binding so ciphertext becomes undecryptable outside the browser that created it, while still surviving IP changes.",[93,94,96],"h2",{"id":95},"install","Install",[98,99,104],"pre",{"className":100,"code":101,"language":102,"meta":103,"style":103},"language-bash shiki shiki-themes github-dark github-dark","pnpm add @alikhalilll\u002Fnuxt-crypto\n","bash","",[42,105,106],{"__ignoreMap":103},[107,108,111,115,119],"span",{"class":109,"line":110},"line",1,[107,112,114],{"class":113},"sFR8T","pnpm",[107,116,118],{"class":117},"s4wv1"," add",[107,120,121],{"class":117}," @alikhalilll\u002Fnuxt-crypto\n",[93,123,125],{"id":124},"register-the-module","Register the module",[98,127,131],{"className":128,"code":129,"language":130,"meta":103,"style":103},"language-ts shiki shiki-themes github-dark github-dark","\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@alikhalilll\u002Fnuxt-crypto'],\n  crypto: {\n    passphrase: process.env.NUXT_ENCRYPTION_PASSPHRASE ?? '',\n    provideName: '$crypto',\n    iterations: 100_000,\n    keyCacheSize: 64,\n    serverOnly: false,\n  },\n});\n","ts",[42,132,133,139,156,168,174,193,204,215,226,237,243],{"__ignoreMap":103},[107,134,135],{"class":109,"line":110},[107,136,138],{"class":137},"sJ8bj","\u002F\u002F nuxt.config.ts\n",[107,140,142,146,149,152],{"class":109,"line":141},2,[107,143,145],{"class":144},"sOPea","export",[107,147,148],{"class":144}," default",[107,150,151],{"class":113}," defineNuxtConfig",[107,153,155],{"class":154},"suv1-","({\n",[107,157,159,162,165],{"class":109,"line":158},3,[107,160,161],{"class":154},"  modules: [",[107,163,164],{"class":117},"'@alikhalilll\u002Fnuxt-crypto'",[107,166,167],{"class":154},"],\n",[107,169,171],{"class":109,"line":170},4,[107,172,173],{"class":154},"  crypto: {\n",[107,175,177,180,184,187,190],{"class":109,"line":176},5,[107,178,179],{"class":154},"    passphrase: process.env.",[107,181,183],{"class":182},"s8ozJ","NUXT_ENCRYPTION_PASSPHRASE",[107,185,186],{"class":144}," ??",[107,188,189],{"class":117}," ''",[107,191,192],{"class":154},",\n",[107,194,196,199,202],{"class":109,"line":195},6,[107,197,198],{"class":154},"    provideName: ",[107,200,201],{"class":117},"'$crypto'",[107,203,192],{"class":154},[107,205,207,210,213],{"class":109,"line":206},7,[107,208,209],{"class":154},"    iterations: ",[107,211,212],{"class":182},"100_000",[107,214,192],{"class":154},[107,216,218,221,224],{"class":109,"line":217},8,[107,219,220],{"class":154},"    keyCacheSize: ",[107,222,223],{"class":182},"64",[107,225,192],{"class":154},[107,227,229,232,235],{"class":109,"line":228},9,[107,230,231],{"class":154},"    serverOnly: ",[107,233,234],{"class":182},"false",[107,236,192],{"class":154},[107,238,240],{"class":109,"line":239},10,[107,241,242],{"class":154},"  },\n",[107,244,246],{"class":109,"line":245},11,[107,247,248],{"class":154},"});\n",[15,250,251,254],{},[42,252,253],{},".env"," (not committed):",[98,256,261],{"className":257,"code":259,"language":260},[258],"language-text","NUXT_ENCRYPTION_PASSPHRASE=replace-me-with-a-long-random-secret\n","text",[42,262,259],{"__ignoreMap":103},[93,264,266],{"id":265},"basic-round-trip","Basic round-trip",[98,268,272],{"className":269,"code":270,"language":271,"meta":103,"style":103},"language-vue shiki shiki-themes github-dark github-dark","\u003Cscript setup lang=\"ts\">\nconst { $crypto } = useNuxtApp();\n\nconst payload = await $crypto.encrypt('super-secret');\n\u002F\u002F payload: \"v1.\u003CsaltB64>.\u003CivB64>.\u003CcipherB64>\"\n\nconst plain = await $crypto.decrypt(payload);\n\u003C\u002Fscript>\n","vue",[42,273,274,298,320,326,354,359,363,382],{"__ignoreMap":103},[107,275,276,279,283,286,289,292,295],{"class":109,"line":110},[107,277,278],{"class":154},"\u003C",[107,280,282],{"class":281},"sxg3X","script",[107,284,285],{"class":113}," setup",[107,287,288],{"class":113}," lang",[107,290,291],{"class":154},"=",[107,293,294],{"class":117},"\"ts\"",[107,296,297],{"class":154},">\n",[107,299,300,303,306,309,312,314,317],{"class":109,"line":141},[107,301,302],{"class":144},"const",[107,304,305],{"class":154}," { ",[107,307,308],{"class":182},"$crypto",[107,310,311],{"class":154}," } ",[107,313,291],{"class":144},[107,315,316],{"class":113}," useNuxtApp",[107,318,319],{"class":154},"();\n",[107,321,322],{"class":109,"line":158},[107,323,325],{"emptyLinePlaceholder":324},true,"\n",[107,327,328,330,333,336,339,342,345,348,351],{"class":109,"line":170},[107,329,302],{"class":144},[107,331,332],{"class":182}," payload",[107,334,335],{"class":144}," =",[107,337,338],{"class":144}," await",[107,340,341],{"class":154}," $crypto.",[107,343,344],{"class":113},"encrypt",[107,346,347],{"class":154},"(",[107,349,350],{"class":117},"'super-secret'",[107,352,353],{"class":154},");\n",[107,355,356],{"class":109,"line":176},[107,357,358],{"class":137},"\u002F\u002F payload: \"v1.\u003CsaltB64>.\u003CivB64>.\u003CcipherB64>\"\n",[107,360,361],{"class":109,"line":195},[107,362,325],{"emptyLinePlaceholder":324},[107,364,365,367,370,372,374,376,379],{"class":109,"line":206},[107,366,302],{"class":144},[107,368,369],{"class":182}," plain",[107,371,335],{"class":144},[107,373,338],{"class":144},[107,375,341],{"class":154},[107,377,378],{"class":113},"decrypt",[107,380,381],{"class":154},"(payload);\n",[107,383,384,387,389],{"class":109,"line":217},[107,385,386],{"class":154},"\u003C\u002F",[107,388,282],{"class":281},[107,390,297],{"class":154},[15,392,393,395,396,399],{},[42,394,308],{}," is typed as ",[42,397,398],{},"CryptoService"," via auto-generated module augmentation.",[401,402],"demo-crypto",{},[93,404,406],{"id":405},"encrypt-a-json-object","Encrypt a JSON object",[15,408,409,411],{},[42,410,344],{}," takes a string — stringify structured data first.",[98,413,415],{"className":128,"code":414,"language":130,"meta":103,"style":103},"const session = { userId: 42, roles: ['admin'], issuedAt: Date.now() };\nconst payload = await $crypto.encrypt(JSON.stringify(session));\nconst restored = JSON.parse(await $crypto.decrypt(payload)) as typeof session;\n",[42,416,417,447,475],{"__ignoreMap":103},[107,418,419,421,424,426,429,432,435,438,441,444],{"class":109,"line":110},[107,420,302],{"class":144},[107,422,423],{"class":182}," session",[107,425,335],{"class":144},[107,427,428],{"class":154}," { userId: ",[107,430,431],{"class":182},"42",[107,433,434],{"class":154},", roles: [",[107,436,437],{"class":117},"'admin'",[107,439,440],{"class":154},"], issuedAt: Date.",[107,442,443],{"class":113},"now",[107,445,446],{"class":154},"() };\n",[107,448,449,451,453,455,457,459,461,463,466,469,472],{"class":109,"line":141},[107,450,302],{"class":144},[107,452,332],{"class":182},[107,454,335],{"class":144},[107,456,338],{"class":144},[107,458,341],{"class":154},[107,460,344],{"class":113},[107,462,347],{"class":154},[107,464,465],{"class":182},"JSON",[107,467,468],{"class":154},".",[107,470,471],{"class":113},"stringify",[107,473,474],{"class":154},"(session));\n",[107,476,477,479,482,484,487,489,492,494,497,499,501,504,507,510],{"class":109,"line":158},[107,478,302],{"class":144},[107,480,481],{"class":182}," restored",[107,483,335],{"class":144},[107,485,486],{"class":182}," JSON",[107,488,468],{"class":154},[107,490,491],{"class":113},"parse",[107,493,347],{"class":154},[107,495,496],{"class":144},"await",[107,498,341],{"class":154},[107,500,378],{"class":113},[107,502,503],{"class":154},"(payload)) ",[107,505,506],{"class":144},"as",[107,508,509],{"class":144}," typeof",[107,511,512],{"class":154}," session;\n",[15,514,515],{},"A tiny composable keeps this ergonomic:",[98,517,519],{"className":128,"code":518,"language":130,"meta":103,"style":103},"\u002F\u002F composables\u002FuseEncrypted.ts\nexport function useEncrypted() {\n  const { $crypto } = useNuxtApp();\n  return {\n    encode: async \u003CT>(value: T) => $crypto.encrypt(JSON.stringify(value)),\n    decode: async \u003CT>(payload: string) => JSON.parse(await $crypto.decrypt(payload)) as T,\n  };\n}\n",[42,520,521,526,539,556,564,615,664,669],{"__ignoreMap":103},[107,522,523],{"class":109,"line":110},[107,524,525],{"class":137},"\u002F\u002F composables\u002FuseEncrypted.ts\n",[107,527,528,530,533,536],{"class":109,"line":141},[107,529,145],{"class":144},[107,531,532],{"class":144}," function",[107,534,535],{"class":113}," useEncrypted",[107,537,538],{"class":154},"() {\n",[107,540,541,544,546,548,550,552,554],{"class":109,"line":158},[107,542,543],{"class":144},"  const",[107,545,305],{"class":154},[107,547,308],{"class":182},[107,549,311],{"class":154},[107,551,291],{"class":144},[107,553,316],{"class":113},[107,555,319],{"class":154},[107,557,558,561],{"class":109,"line":170},[107,559,560],{"class":144},"  return",[107,562,563],{"class":154}," {\n",[107,565,566,569,572,575,578,581,584,588,591,594,597,600,602,604,606,608,610,612],{"class":109,"line":176},[107,567,568],{"class":113},"    encode",[107,570,571],{"class":154},": ",[107,573,574],{"class":144},"async",[107,576,577],{"class":154}," \u003C",[107,579,580],{"class":113},"T",[107,582,583],{"class":154},">(",[107,585,587],{"class":586},"s-3mD","value",[107,589,590],{"class":144},":",[107,592,593],{"class":113}," T",[107,595,596],{"class":154},") ",[107,598,599],{"class":144},"=>",[107,601,341],{"class":154},[107,603,344],{"class":113},[107,605,347],{"class":154},[107,607,465],{"class":182},[107,609,468],{"class":154},[107,611,471],{"class":113},[107,613,614],{"class":154},"(value)),\n",[107,616,617,620,622,624,626,628,630,633,635,638,640,642,644,646,648,650,652,654,656,658,660,662],{"class":109,"line":195},[107,618,619],{"class":113},"    decode",[107,621,571],{"class":154},[107,623,574],{"class":144},[107,625,577],{"class":154},[107,627,580],{"class":113},[107,629,583],{"class":154},[107,631,632],{"class":586},"payload",[107,634,590],{"class":144},[107,636,637],{"class":182}," string",[107,639,596],{"class":154},[107,641,599],{"class":144},[107,643,486],{"class":182},[107,645,468],{"class":154},[107,647,491],{"class":113},[107,649,347],{"class":154},[107,651,496],{"class":144},[107,653,341],{"class":154},[107,655,378],{"class":113},[107,657,503],{"class":154},[107,659,506],{"class":144},[107,661,593],{"class":113},[107,663,192],{"class":154},[107,665,666],{"class":109,"line":206},[107,667,668],{"class":154},"  };\n",[107,670,671],{"class":109,"line":217},[107,672,673],{"class":154},"}\n",[93,675,677],{"id":676},"persist-to-a-cookie","Persist to a cookie",[98,679,681],{"className":128,"code":680,"language":130,"meta":103,"style":103},"const cookie = useCookie\u003Cstring | null>('session');\n\n\u002F\u002F Write\ncookie.value = await $crypto.encrypt(JSON.stringify({ userId: 42 }));\n\n\u002F\u002F Read\nif (cookie.value) {\n  const { userId } = JSON.parse(await $crypto.decrypt(cookie.value));\n}\n",[42,682,683,713,717,722,751,755,760,768,798],{"__ignoreMap":103},[107,684,685,687,690,692,695,697,700,703,706,708,711],{"class":109,"line":110},[107,686,302],{"class":144},[107,688,689],{"class":182}," cookie",[107,691,335],{"class":144},[107,693,694],{"class":113}," useCookie",[107,696,278],{"class":154},[107,698,699],{"class":182},"string",[107,701,702],{"class":144}," |",[107,704,705],{"class":182}," null",[107,707,583],{"class":154},[107,709,710],{"class":117},"'session'",[107,712,353],{"class":154},[107,714,715],{"class":109,"line":141},[107,716,325],{"emptyLinePlaceholder":324},[107,718,719],{"class":109,"line":158},[107,720,721],{"class":137},"\u002F\u002F Write\n",[107,723,724,727,729,731,733,735,737,739,741,743,746,748],{"class":109,"line":170},[107,725,726],{"class":154},"cookie.value ",[107,728,291],{"class":144},[107,730,338],{"class":144},[107,732,341],{"class":154},[107,734,344],{"class":113},[107,736,347],{"class":154},[107,738,465],{"class":182},[107,740,468],{"class":154},[107,742,471],{"class":113},[107,744,745],{"class":154},"({ userId: ",[107,747,431],{"class":182},[107,749,750],{"class":154}," }));\n",[107,752,753],{"class":109,"line":176},[107,754,325],{"emptyLinePlaceholder":324},[107,756,757],{"class":109,"line":195},[107,758,759],{"class":137},"\u002F\u002F Read\n",[107,761,762,765],{"class":109,"line":206},[107,763,764],{"class":144},"if",[107,766,767],{"class":154}," (cookie.value) {\n",[107,769,770,772,774,777,779,781,783,785,787,789,791,793,795],{"class":109,"line":217},[107,771,543],{"class":144},[107,773,305],{"class":154},[107,775,776],{"class":182},"userId",[107,778,311],{"class":154},[107,780,291],{"class":144},[107,782,486],{"class":182},[107,784,468],{"class":154},[107,786,491],{"class":113},[107,788,347],{"class":154},[107,790,496],{"class":144},[107,792,341],{"class":154},[107,794,378],{"class":113},[107,796,797],{"class":154},"(cookie.value));\n",[107,799,800],{"class":109,"line":228},[107,801,673],{"class":154},[93,803,805],{"id":804},"key-cache","Key cache",[15,807,808],{},"PBKDF2 is deliberately slow. The built-in cache (default size 64) keys derived material by salt, so a second decrypt of any payload you've already touched is essentially free.",[98,810,812],{"className":128,"code":811,"language":130,"meta":103,"style":103},"const payloads = await Promise.all(items.map((i) => $crypto.encrypt(i)));\n\u002F\u002F Fast — each salt was just derived during encrypt.\nconst plain = await Promise.all(payloads.map((p) => $crypto.decrypt(p)));\n\n\u002F\u002F Force re-derivation (e.g. during a passphrase rotation audit):\n$crypto.clearKeyCache();\n",[42,813,814,856,861,897,901,906],{"__ignoreMap":103},[107,815,816,818,821,823,825,828,830,833,836,839,842,845,847,849,851,853],{"class":109,"line":110},[107,817,302],{"class":144},[107,819,820],{"class":182}," payloads",[107,822,335],{"class":144},[107,824,338],{"class":144},[107,826,827],{"class":182}," Promise",[107,829,468],{"class":154},[107,831,832],{"class":113},"all",[107,834,835],{"class":154},"(items.",[107,837,838],{"class":113},"map",[107,840,841],{"class":154},"((",[107,843,844],{"class":586},"i",[107,846,596],{"class":154},[107,848,599],{"class":144},[107,850,341],{"class":154},[107,852,344],{"class":113},[107,854,855],{"class":154},"(i)));\n",[107,857,858],{"class":109,"line":141},[107,859,860],{"class":137},"\u002F\u002F Fast — each salt was just derived during encrypt.\n",[107,862,863,865,867,869,871,873,875,877,880,882,884,886,888,890,892,894],{"class":109,"line":158},[107,864,302],{"class":144},[107,866,369],{"class":182},[107,868,335],{"class":144},[107,870,338],{"class":144},[107,872,827],{"class":182},[107,874,468],{"class":154},[107,876,832],{"class":113},[107,878,879],{"class":154},"(payloads.",[107,881,838],{"class":113},[107,883,841],{"class":154},[107,885,15],{"class":586},[107,887,596],{"class":154},[107,889,599],{"class":144},[107,891,341],{"class":154},[107,893,378],{"class":113},[107,895,896],{"class":154},"(p)));\n",[107,898,899],{"class":109,"line":170},[107,900,325],{"emptyLinePlaceholder":324},[107,902,903],{"class":109,"line":176},[107,904,905],{"class":137},"\u002F\u002F Force re-derivation (e.g. during a passphrase rotation audit):\n",[107,907,908,911,914],{"class":109,"line":195},[107,909,910],{"class":154},"$crypto.",[107,912,913],{"class":113},"clearKeyCache",[107,915,319],{"class":154},[93,917,919],{"id":918},"error-handling","Error handling",[921,922,923,936],"table",{},[924,925,926],"thead",{},[927,928,929,933],"tr",{},[930,931,932],"th",{},"Scenario",[930,934,935],{},"Message",[937,938,939,953,963,973,985],"tbody",{},[927,940,941,948],{},[942,943,944,945],"td",{},"Payload isn't ",[42,946,947],{},"a.b.c.d",[942,949,950],{},[42,951,952],{},"Invalid payload format — expected 4 dot-separated segments.",[927,954,955,958],{},[942,956,957],{},"A segment is empty",[942,959,960],{},[42,961,962],{},"Invalid payload format — one or more segments were empty.",[927,964,965,968],{},[942,966,967],{},"Algorithm version mismatch",[942,969,970],{},[42,971,972],{},"Unsupported payload version: v2 (algorithm expects v1).",[927,974,975,978],{},[942,976,977],{},"Wrong passphrase \u002F tampered ciphertext",[942,979,980,981,984],{},"Native ",[42,982,983],{},"OperationError"," from Web Crypto.",[927,986,987,990],{},[942,988,989],{},"Passphrase not set in module config",[942,991,992],{},[42,993,994],{},"[nuxt-crypto] passphrase is required.",[93,996,79],{"id":997},"server-only-mode",[15,999,1000,1001,1004],{},"Set ",[42,1002,1003],{},"serverOnly: true"," to skip the client plugin and keep the passphrase out of the browser bundle.",[98,1006,1008],{"className":128,"code":1007,"language":130,"meta":103,"style":103},"crypto: {\n  passphrase: process.env.NUXT_ENCRYPTION_PASSPHRASE ?? '',\n  serverOnly: true,\n}\n",[42,1009,1010,1017,1033,1045],{"__ignoreMap":103},[107,1011,1012,1014],{"class":109,"line":110},[107,1013,5],{"class":113},[107,1015,1016],{"class":154},": {\n",[107,1018,1019,1022,1025,1027,1029,1031],{"class":109,"line":141},[107,1020,1021],{"class":113},"  passphrase",[107,1023,1024],{"class":154},": process.env.",[107,1026,183],{"class":182},[107,1028,186],{"class":144},[107,1030,189],{"class":117},[107,1032,192],{"class":154},[107,1034,1035,1038,1040,1043],{"class":109,"line":158},[107,1036,1037],{"class":113},"  serverOnly",[107,1039,571],{"class":154},[107,1041,1042],{"class":182},"true",[107,1044,192],{"class":154},[107,1046,1047],{"class":109,"line":170},[107,1048,673],{"class":154},[15,1050,1051,1052,1054,1055,1058,1059,1062,1063,468],{},"With this enabled, ",[42,1053,308],{}," is ",[42,1056,1057],{},"undefined"," on the client. Use it in Nitro routes, server-only plugins, or ",[42,1060,1061],{},"\u003Cscript setup>"," blocks guarded by ",[42,1064,1065],{},"import.meta.server",[93,1067,1069],{"id":1068},"nitro-routes","Nitro routes",[15,1071,1072,1073,1076,1077,1079,1080,590],{},"Nitro event handlers run outside the Nuxt app context — ",[42,1074,1075],{},"useNuxtApp()"," (and ",[42,1078,308],{},") is not available there. Use the framework-agnostic core and cache it in ",[42,1081,1082],{},"server\u002Futils\u002F",[98,1084,1086],{"className":128,"code":1085,"language":130,"meta":103,"style":103},"\u002F\u002F server\u002Futils\u002Fcrypto.ts\nimport { createCryptoService } from '@alikhalilll\u002Fnuxt-crypto\u002Fcore';\n\nlet servicePromise: ReturnType\u003Ctypeof createCryptoService> | null = null;\n\nexport function useServerCrypto() {\n  if (!servicePromise) {\n    servicePromise = createCryptoService({\n      passphrase: process.env.NUXT_ENCRYPTION_PASSPHRASE!,\n      iterations: 100_000,\n    });\n  }\n  return servicePromise;\n}\n",[42,1087,1088,1093,1110,1114,1146,1150,1161,1175,1187,1198,1207,1212,1218,1226],{"__ignoreMap":103},[107,1089,1090],{"class":109,"line":110},[107,1091,1092],{"class":137},"\u002F\u002F server\u002Futils\u002Fcrypto.ts\n",[107,1094,1095,1098,1101,1104,1107],{"class":109,"line":141},[107,1096,1097],{"class":144},"import",[107,1099,1100],{"class":154}," { createCryptoService } ",[107,1102,1103],{"class":144},"from",[107,1105,1106],{"class":117}," '@alikhalilll\u002Fnuxt-crypto\u002Fcore'",[107,1108,1109],{"class":154},";\n",[107,1111,1112],{"class":109,"line":158},[107,1113,325],{"emptyLinePlaceholder":324},[107,1115,1116,1119,1122,1124,1127,1129,1132,1135,1138,1140,1142,1144],{"class":109,"line":170},[107,1117,1118],{"class":144},"let",[107,1120,1121],{"class":154}," servicePromise",[107,1123,590],{"class":144},[107,1125,1126],{"class":113}," ReturnType",[107,1128,278],{"class":154},[107,1130,1131],{"class":144},"typeof",[107,1133,1134],{"class":154}," createCryptoService> ",[107,1136,1137],{"class":144},"|",[107,1139,705],{"class":182},[107,1141,335],{"class":144},[107,1143,705],{"class":182},[107,1145,1109],{"class":154},[107,1147,1148],{"class":109,"line":176},[107,1149,325],{"emptyLinePlaceholder":324},[107,1151,1152,1154,1156,1159],{"class":109,"line":195},[107,1153,145],{"class":144},[107,1155,532],{"class":144},[107,1157,1158],{"class":113}," useServerCrypto",[107,1160,538],{"class":154},[107,1162,1163,1166,1169,1172],{"class":109,"line":206},[107,1164,1165],{"class":144},"  if",[107,1167,1168],{"class":154}," (",[107,1170,1171],{"class":144},"!",[107,1173,1174],{"class":154},"servicePromise) {\n",[107,1176,1177,1180,1182,1185],{"class":109,"line":217},[107,1178,1179],{"class":154},"    servicePromise ",[107,1181,291],{"class":144},[107,1183,1184],{"class":113}," createCryptoService",[107,1186,155],{"class":154},[107,1188,1189,1192,1194,1196],{"class":109,"line":228},[107,1190,1191],{"class":154},"      passphrase: process.env.",[107,1193,183],{"class":182},[107,1195,1171],{"class":144},[107,1197,192],{"class":154},[107,1199,1200,1203,1205],{"class":109,"line":239},[107,1201,1202],{"class":154},"      iterations: ",[107,1204,212],{"class":182},[107,1206,192],{"class":154},[107,1208,1209],{"class":109,"line":245},[107,1210,1211],{"class":154},"    });\n",[107,1213,1215],{"class":109,"line":1214},12,[107,1216,1217],{"class":154},"  }\n",[107,1219,1221,1223],{"class":109,"line":1220},13,[107,1222,560],{"class":144},[107,1224,1225],{"class":154}," servicePromise;\n",[107,1227,1229],{"class":109,"line":1228},14,[107,1230,673],{"class":154},[98,1232,1234],{"className":128,"code":1233,"language":130,"meta":103,"style":103},"\u002F\u002F server\u002Fapi\u002Fsession\u002Fencode.post.ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  const crypto = await useServerCrypto();\n  return { token: await crypto.encrypt(JSON.stringify(body)) };\n});\n",[42,1235,1236,1241,1265,1282,1297,1322],{"__ignoreMap":103},[107,1237,1238],{"class":109,"line":110},[107,1239,1240],{"class":137},"\u002F\u002F server\u002Fapi\u002Fsession\u002Fencode.post.ts\n",[107,1242,1243,1245,1247,1250,1252,1254,1256,1259,1261,1263],{"class":109,"line":141},[107,1244,145],{"class":144},[107,1246,148],{"class":144},[107,1248,1249],{"class":113}," defineEventHandler",[107,1251,347],{"class":154},[107,1253,574],{"class":144},[107,1255,1168],{"class":154},[107,1257,1258],{"class":586},"event",[107,1260,596],{"class":154},[107,1262,599],{"class":144},[107,1264,563],{"class":154},[107,1266,1267,1269,1272,1274,1276,1279],{"class":109,"line":158},[107,1268,543],{"class":144},[107,1270,1271],{"class":182}," body",[107,1273,335],{"class":144},[107,1275,338],{"class":144},[107,1277,1278],{"class":113}," readBody",[107,1280,1281],{"class":154},"(event);\n",[107,1283,1284,1286,1289,1291,1293,1295],{"class":109,"line":170},[107,1285,543],{"class":144},[107,1287,1288],{"class":182}," crypto",[107,1290,335],{"class":144},[107,1292,338],{"class":144},[107,1294,1158],{"class":113},[107,1296,319],{"class":154},[107,1298,1299,1301,1304,1306,1309,1311,1313,1315,1317,1319],{"class":109,"line":176},[107,1300,560],{"class":144},[107,1302,1303],{"class":154}," { token: ",[107,1305,496],{"class":144},[107,1307,1308],{"class":154}," crypto.",[107,1310,344],{"class":113},[107,1312,347],{"class":154},[107,1314,465],{"class":182},[107,1316,468],{"class":154},[107,1318,471],{"class":113},[107,1320,1321],{"class":154},"(body)) };\n",[107,1323,1324],{"class":109,"line":195},[107,1325,248],{"class":154},[93,1327,85],{"id":1328},"device-fingerprint",[15,1330,1331],{},"Bind a payload to the browser that created it — a copy of the ciphertext in another browser or on another device will refuse to decrypt. Useful for short-lived CSRF tokens, one-time magic links, anti-replay nonces, or any flow where a stolen token must be worthless off-origin.",[15,1333,1334,1335,1338],{},"The fingerprint is built from an ",[19,1336,1337],{},"HttpOnly device-ID cookie"," (not the client IP), so it survives network changes — Wi-Fi → 4G, cell handoffs, VPN rotations, laptop sleeps — while still blocking copy-paste to a different browser or device.",[1340,1341,1343],"h3",{"id":1342},"setup","Setup",[15,1345,1346,1347,590],{},"Add a fingerprint salt to your runtime config — a long random secret, ",[19,1348,1349],{},"server-side only",[98,1351,1353],{"className":128,"code":1352,"language":130,"meta":103,"style":103},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  runtimeConfig: {\n    cryptoFingerprintSalt: process.env.NUXT_CRYPTO_FINGERPRINT_SALT ?? '',\n  },\n});\n",[42,1354,1355,1359,1369,1374,1388,1392],{"__ignoreMap":103},[107,1356,1357],{"class":109,"line":110},[107,1358,138],{"class":137},[107,1360,1361,1363,1365,1367],{"class":109,"line":141},[107,1362,145],{"class":144},[107,1364,148],{"class":144},[107,1366,151],{"class":113},[107,1368,155],{"class":154},[107,1370,1371],{"class":109,"line":158},[107,1372,1373],{"class":154},"  runtimeConfig: {\n",[107,1375,1376,1379,1382,1384,1386],{"class":109,"line":170},[107,1377,1378],{"class":154},"    cryptoFingerprintSalt: process.env.",[107,1380,1381],{"class":182},"NUXT_CRYPTO_FINGERPRINT_SALT",[107,1383,186],{"class":144},[107,1385,189],{"class":117},[107,1387,192],{"class":154},[107,1389,1390],{"class":109,"line":176},[107,1391,242],{"class":154},[107,1393,1394],{"class":109,"line":195},[107,1395,248],{"class":154},[98,1397,1399],{"className":100,"code":1398,"language":102,"meta":103,"style":103},"# .env\nNUXT_CRYPTO_FINGERPRINT_SALT=replace-with-64-random-hex-chars\n",[42,1400,1401,1406],{"__ignoreMap":103},[107,1402,1403],{"class":109,"line":110},[107,1404,1405],{"class":137},"# .env\n",[107,1407,1408,1410,1412],{"class":109,"line":141},[107,1409,1381],{"class":154},[107,1411,291],{"class":144},[107,1413,1414],{"class":117},"replace-with-64-random-hex-chars\n",[1340,1416,1418],{"id":1417},"encrypt-with-a-fingerprint-server-side","Encrypt with a fingerprint (server side)",[98,1420,1422],{"className":128,"code":1421,"language":130,"meta":103,"style":103},"\u002F\u002F server\u002Fapi\u002Fsession\u002Fencode.post.ts\nimport { getClientFingerprint } from '@alikhalilll\u002Fnuxt-crypto\u002Fserver';\n\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  const crypto = await useServerCrypto(); \u002F\u002F from server\u002Futils\u002Fcrypto.ts — see \"Nitro routes\" above\n\n  const fingerprint = await getClientFingerprint(event, {\n    salt: useRuntimeConfig().cryptoFingerprintSalt,\n  });\n\n  return {\n    token: await crypto.encrypt(JSON.stringify(body), { fingerprint }),\n  };\n});\n",[42,1423,1424,1428,1442,1446,1468,1482,1500,1504,1521,1532,1537,1541,1547,1569,1573],{"__ignoreMap":103},[107,1425,1426],{"class":109,"line":110},[107,1427,1240],{"class":137},[107,1429,1430,1432,1435,1437,1440],{"class":109,"line":141},[107,1431,1097],{"class":144},[107,1433,1434],{"class":154}," { getClientFingerprint } ",[107,1436,1103],{"class":144},[107,1438,1439],{"class":117}," '@alikhalilll\u002Fnuxt-crypto\u002Fserver'",[107,1441,1109],{"class":154},[107,1443,1444],{"class":109,"line":158},[107,1445,325],{"emptyLinePlaceholder":324},[107,1447,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466],{"class":109,"line":170},[107,1449,145],{"class":144},[107,1451,148],{"class":144},[107,1453,1249],{"class":113},[107,1455,347],{"class":154},[107,1457,574],{"class":144},[107,1459,1168],{"class":154},[107,1461,1258],{"class":586},[107,1463,596],{"class":154},[107,1465,599],{"class":144},[107,1467,563],{"class":154},[107,1469,1470,1472,1474,1476,1478,1480],{"class":109,"line":176},[107,1471,543],{"class":144},[107,1473,1271],{"class":182},[107,1475,335],{"class":144},[107,1477,338],{"class":144},[107,1479,1278],{"class":113},[107,1481,1281],{"class":154},[107,1483,1484,1486,1488,1490,1492,1494,1497],{"class":109,"line":195},[107,1485,543],{"class":144},[107,1487,1288],{"class":182},[107,1489,335],{"class":144},[107,1491,338],{"class":144},[107,1493,1158],{"class":113},[107,1495,1496],{"class":154},"(); ",[107,1498,1499],{"class":137},"\u002F\u002F from server\u002Futils\u002Fcrypto.ts — see \"Nitro routes\" above\n",[107,1501,1502],{"class":109,"line":206},[107,1503,325],{"emptyLinePlaceholder":324},[107,1505,1506,1508,1511,1513,1515,1518],{"class":109,"line":217},[107,1507,543],{"class":144},[107,1509,1510],{"class":182}," fingerprint",[107,1512,335],{"class":144},[107,1514,338],{"class":144},[107,1516,1517],{"class":113}," getClientFingerprint",[107,1519,1520],{"class":154},"(event, {\n",[107,1522,1523,1526,1529],{"class":109,"line":228},[107,1524,1525],{"class":154},"    salt: ",[107,1527,1528],{"class":113},"useRuntimeConfig",[107,1530,1531],{"class":154},"().cryptoFingerprintSalt,\n",[107,1533,1534],{"class":109,"line":239},[107,1535,1536],{"class":154},"  });\n",[107,1538,1539],{"class":109,"line":245},[107,1540,325],{"emptyLinePlaceholder":324},[107,1542,1543,1545],{"class":109,"line":1214},[107,1544,560],{"class":144},[107,1546,563],{"class":154},[107,1548,1549,1552,1554,1556,1558,1560,1562,1564,1566],{"class":109,"line":1220},[107,1550,1551],{"class":154},"    token: ",[107,1553,496],{"class":144},[107,1555,1308],{"class":154},[107,1557,344],{"class":113},[107,1559,347],{"class":154},[107,1561,465],{"class":182},[107,1563,468],{"class":154},[107,1565,471],{"class":113},[107,1567,1568],{"class":154},"(body), { fingerprint }),\n",[107,1570,1571],{"class":109,"line":1228},[107,1572,668],{"class":154},[107,1574,1576],{"class":109,"line":1575},15,[107,1577,248],{"class":154},[15,1579,1580,1581,1584,1585,1588],{},"On the first request for a browser, ",[42,1582,1583],{},"getClientFingerprint"," sets an HttpOnly cookie (",[42,1586,1587],{},"__nuxt_crypto_device",") with a random 32-byte device ID. Subsequent calls reuse it, so the returned fingerprint is stable per browser.",[1340,1590,1592],{"id":1591},"decrypt-with-a-fingerprint-server-side","Decrypt with a fingerprint (server side)",[98,1594,1596],{"className":128,"code":1595,"language":130,"meta":103,"style":103},"\u002F\u002F server\u002Fapi\u002Fsession\u002Fdecode.post.ts\nimport { getClientFingerprint } from '@alikhalilll\u002Fnuxt-crypto\u002Fserver';\n\nexport default defineEventHandler(async (event) => {\n  const { token } = await readBody(event);\n  const crypto = await useServerCrypto();\n\n  const fingerprint = await getClientFingerprint(event, {\n    salt: useRuntimeConfig().cryptoFingerprintSalt,\n  });\n\n  \u002F\u002F Same cookie → same fingerprint → succeeds.\n  \u002F\u002F Different browser\u002Fdevice → no cookie → different fingerprint → OperationError.\n  return { body: JSON.parse(await crypto.decrypt(token, { fingerprint })) };\n});\n",[42,1597,1598,1603,1615,1619,1641,1660,1674,1678,1692,1700,1704,1708,1713,1718,1742],{"__ignoreMap":103},[107,1599,1600],{"class":109,"line":110},[107,1601,1602],{"class":137},"\u002F\u002F server\u002Fapi\u002Fsession\u002Fdecode.post.ts\n",[107,1604,1605,1607,1609,1611,1613],{"class":109,"line":141},[107,1606,1097],{"class":144},[107,1608,1434],{"class":154},[107,1610,1103],{"class":144},[107,1612,1439],{"class":117},[107,1614,1109],{"class":154},[107,1616,1617],{"class":109,"line":158},[107,1618,325],{"emptyLinePlaceholder":324},[107,1620,1621,1623,1625,1627,1629,1631,1633,1635,1637,1639],{"class":109,"line":170},[107,1622,145],{"class":144},[107,1624,148],{"class":144},[107,1626,1249],{"class":113},[107,1628,347],{"class":154},[107,1630,574],{"class":144},[107,1632,1168],{"class":154},[107,1634,1258],{"class":586},[107,1636,596],{"class":154},[107,1638,599],{"class":144},[107,1640,563],{"class":154},[107,1642,1643,1645,1647,1650,1652,1654,1656,1658],{"class":109,"line":176},[107,1644,543],{"class":144},[107,1646,305],{"class":154},[107,1648,1649],{"class":182},"token",[107,1651,311],{"class":154},[107,1653,291],{"class":144},[107,1655,338],{"class":144},[107,1657,1278],{"class":113},[107,1659,1281],{"class":154},[107,1661,1662,1664,1666,1668,1670,1672],{"class":109,"line":195},[107,1663,543],{"class":144},[107,1665,1288],{"class":182},[107,1667,335],{"class":144},[107,1669,338],{"class":144},[107,1671,1158],{"class":113},[107,1673,319],{"class":154},[107,1675,1676],{"class":109,"line":206},[107,1677,325],{"emptyLinePlaceholder":324},[107,1679,1680,1682,1684,1686,1688,1690],{"class":109,"line":217},[107,1681,543],{"class":144},[107,1683,1510],{"class":182},[107,1685,335],{"class":144},[107,1687,338],{"class":144},[107,1689,1517],{"class":113},[107,1691,1520],{"class":154},[107,1693,1694,1696,1698],{"class":109,"line":228},[107,1695,1525],{"class":154},[107,1697,1528],{"class":113},[107,1699,1531],{"class":154},[107,1701,1702],{"class":109,"line":239},[107,1703,1536],{"class":154},[107,1705,1706],{"class":109,"line":245},[107,1707,325],{"emptyLinePlaceholder":324},[107,1709,1710],{"class":109,"line":1214},[107,1711,1712],{"class":137},"  \u002F\u002F Same cookie → same fingerprint → succeeds.\n",[107,1714,1715],{"class":109,"line":1220},[107,1716,1717],{"class":137},"  \u002F\u002F Different browser\u002Fdevice → no cookie → different fingerprint → OperationError.\n",[107,1719,1720,1722,1725,1727,1729,1731,1733,1735,1737,1739],{"class":109,"line":1228},[107,1721,560],{"class":144},[107,1723,1724],{"class":154}," { body: ",[107,1726,465],{"class":182},[107,1728,468],{"class":154},[107,1730,491],{"class":113},[107,1732,347],{"class":154},[107,1734,496],{"class":144},[107,1736,1308],{"class":154},[107,1738,378],{"class":113},[107,1740,1741],{"class":154},"(token, { fingerprint })) };\n",[107,1743,1744],{"class":109,"line":1575},[107,1745,248],{"class":154},[1340,1747,1749],{"id":1748},"what-survives-what-doesnt","What survives, what doesn't",[921,1751,1752,1761],{},[924,1753,1754],{},[927,1755,1756,1758],{},[930,1757,932],{},[930,1759,1760],{},"Still decrypts?",[937,1762,1763,1771,1779,1786,1793,1801,1809],{},[927,1764,1765,1768],{},[942,1766,1767],{},"Wi-Fi → 4G on the same device",[942,1769,1770],{},"✅ yes — cookie travels",[927,1772,1773,1776],{},[942,1774,1775],{},"Cell tower handoff",[942,1777,1778],{},"✅ yes",[927,1780,1781,1784],{},[942,1782,1783],{},"Laptop sleeps, rejoins a new Wi-Fi",[942,1785,1778],{},[927,1787,1788,1791],{},[942,1789,1790],{},"VPN exit node changes",[942,1792,1778],{},[927,1794,1795,1798],{},[942,1796,1797],{},"User copies token to another browser",[942,1799,1800],{},"❌ no — no cookie there",[927,1802,1803,1806],{},[942,1804,1805],{},"Token exfiltrated via XSS to attacker's box",[942,1807,1808],{},"❌ no — HttpOnly cookie unreachable",[927,1810,1811,1814],{},[942,1812,1813],{},"User clears cookies",[942,1815,1816],{},"❌ no — device ID regenerates",[1340,1818,1820,1823],{"id":1819},"derivefingerprint-bring-your-own-device-id",[42,1821,1822],{},"deriveFingerprint"," — bring your own device ID",[15,1825,1826,1827,1830,1831,1834],{},"If you already have a stable per-browser identifier (a session cookie, a signed JWT ",[42,1828,1829],{},"sub"," claim, a row in your ",[42,1832,1833],{},"devices"," table), skip the helper cookie entirely:",[98,1836,1838],{"className":128,"code":1837,"language":130,"meta":103,"style":103},"import { deriveFingerprint } from '@alikhalilll\u002Fnuxt-crypto\u002Fserver';\n\nconst fingerprint = await deriveFingerprint({\n  deviceId: session.id,\n  salt: useRuntimeConfig().cryptoFingerprintSalt,\n});\n",[42,1839,1840,1853,1857,1872,1877,1886],{"__ignoreMap":103},[107,1841,1842,1844,1847,1849,1851],{"class":109,"line":110},[107,1843,1097],{"class":144},[107,1845,1846],{"class":154}," { deriveFingerprint } ",[107,1848,1103],{"class":144},[107,1850,1439],{"class":117},[107,1852,1109],{"class":154},[107,1854,1855],{"class":109,"line":141},[107,1856,325],{"emptyLinePlaceholder":324},[107,1858,1859,1861,1863,1865,1867,1870],{"class":109,"line":158},[107,1860,302],{"class":144},[107,1862,1510],{"class":182},[107,1864,335],{"class":144},[107,1866,338],{"class":144},[107,1868,1869],{"class":113}," deriveFingerprint",[107,1871,155],{"class":154},[107,1873,1874],{"class":109,"line":170},[107,1875,1876],{"class":154},"  deviceId: session.id,\n",[107,1878,1879,1882,1884],{"class":109,"line":176},[107,1880,1881],{"class":154},"  salt: ",[107,1883,1528],{"class":113},[107,1885,1531],{"class":154},[107,1887,1888],{"class":109,"line":195},[107,1889,248],{"class":154},[1891,1892,1894],"alert",{"type":1893},"warning",[15,1895,1896,1897,1900],{},"Binding ciphertext to a fingerprint is ",[19,1898,1899],{},"not appropriate for long-lived user data",". If the device cookie is cleared or the session rotates, those payloads become undecryptable — permanently. Use this for tokens the user can afford to lose: short sessions, magic links, one-shot nonces.",[1340,1902,1904],{"id":1903},"customizing-the-cookie","Customizing the cookie",[98,1906,1908],{"className":128,"code":1907,"language":130,"meta":103,"style":103},"await getClientFingerprint(event, {\n  salt: useRuntimeConfig().cryptoFingerprintSalt,\n  cookieName: 'my-app-dev-id',\n  cookieMaxAge: 60 * 60 * 24 * 30, \u002F\u002F 30 days\n  cookieOptions: {\n    sameSite: 'strict', \u002F\u002F defaults to 'lax'\n    domain: '.example.com',\n  },\n});\n",[42,1909,1910,1918,1926,1936,1966,1971,1984,1994,1998],{"__ignoreMap":103},[107,1911,1912,1914,1916],{"class":109,"line":110},[107,1913,496],{"class":144},[107,1915,1517],{"class":113},[107,1917,1520],{"class":154},[107,1919,1920,1922,1924],{"class":109,"line":141},[107,1921,1881],{"class":154},[107,1923,1528],{"class":113},[107,1925,1531],{"class":154},[107,1927,1928,1931,1934],{"class":109,"line":158},[107,1929,1930],{"class":154},"  cookieName: ",[107,1932,1933],{"class":117},"'my-app-dev-id'",[107,1935,192],{"class":154},[107,1937,1938,1941,1944,1947,1950,1952,1955,1957,1960,1963],{"class":109,"line":170},[107,1939,1940],{"class":154},"  cookieMaxAge: ",[107,1942,1943],{"class":182},"60",[107,1945,1946],{"class":144}," *",[107,1948,1949],{"class":182}," 60",[107,1951,1946],{"class":144},[107,1953,1954],{"class":182}," 24",[107,1956,1946],{"class":144},[107,1958,1959],{"class":182}," 30",[107,1961,1962],{"class":154},", ",[107,1964,1965],{"class":137},"\u002F\u002F 30 days\n",[107,1967,1968],{"class":109,"line":176},[107,1969,1970],{"class":154},"  cookieOptions: {\n",[107,1972,1973,1976,1979,1981],{"class":109,"line":195},[107,1974,1975],{"class":154},"    sameSite: ",[107,1977,1978],{"class":117},"'strict'",[107,1980,1962],{"class":154},[107,1982,1983],{"class":137},"\u002F\u002F defaults to 'lax'\n",[107,1985,1986,1989,1992],{"class":109,"line":206},[107,1987,1988],{"class":154},"    domain: ",[107,1990,1991],{"class":117},"'.example.com'",[107,1993,192],{"class":154},[107,1995,1996],{"class":109,"line":217},[107,1997,242],{"class":154},[107,1999,2000],{"class":109,"line":228},[107,2001,248],{"class":154},[15,2003,2004,2005,1962,2008,1962,2011,1962,2014,2017,2018,2021],{},"Defaults: ",[42,2006,2007],{},"httpOnly: true",[42,2009,2010],{},"sameSite: 'lax'",[42,2012,2013],{},"path: '\u002F'",[42,2015,2016],{},"secure"," auto-detected from the request protocol, ",[42,2019,2020],{},"maxAge"," = 1 year.",[93,2023,39],{"id":2024},"framework-agnostic-core",[98,2026,2028],{"className":128,"code":2027,"language":130,"meta":103,"style":103},"import { createCryptoService } from '@alikhalilll\u002Fnuxt-crypto\u002Fcore';\n\nconst service = await createCryptoService({\n  passphrase: process.env.ENC_PASS!,\n  iterations: 100_000,\n  keyCacheSize: 64,\n});\n\nconst payload = await service.encrypt('hi');\nconst clear = await service.decrypt(payload);\n",[42,2029,2030,2042,2046,2061,2073,2082,2091,2095,2099,2121],{"__ignoreMap":103},[107,2031,2032,2034,2036,2038,2040],{"class":109,"line":110},[107,2033,1097],{"class":144},[107,2035,1100],{"class":154},[107,2037,1103],{"class":144},[107,2039,1106],{"class":117},[107,2041,1109],{"class":154},[107,2043,2044],{"class":109,"line":141},[107,2045,325],{"emptyLinePlaceholder":324},[107,2047,2048,2050,2053,2055,2057,2059],{"class":109,"line":158},[107,2049,302],{"class":144},[107,2051,2052],{"class":182}," service",[107,2054,335],{"class":144},[107,2056,338],{"class":144},[107,2058,1184],{"class":113},[107,2060,155],{"class":154},[107,2062,2063,2066,2069,2071],{"class":109,"line":170},[107,2064,2065],{"class":154},"  passphrase: process.env.",[107,2067,2068],{"class":182},"ENC_PASS",[107,2070,1171],{"class":144},[107,2072,192],{"class":154},[107,2074,2075,2078,2080],{"class":109,"line":176},[107,2076,2077],{"class":154},"  iterations: ",[107,2079,212],{"class":182},[107,2081,192],{"class":154},[107,2083,2084,2087,2089],{"class":109,"line":195},[107,2085,2086],{"class":154},"  keyCacheSize: ",[107,2088,223],{"class":182},[107,2090,192],{"class":154},[107,2092,2093],{"class":109,"line":206},[107,2094,248],{"class":154},[107,2096,2097],{"class":109,"line":217},[107,2098,325],{"emptyLinePlaceholder":324},[107,2100,2101,2103,2105,2107,2109,2112,2114,2116,2119],{"class":109,"line":228},[107,2102,302],{"class":144},[107,2104,332],{"class":182},[107,2106,335],{"class":144},[107,2108,338],{"class":144},[107,2110,2111],{"class":154}," service.",[107,2113,344],{"class":113},[107,2115,347],{"class":154},[107,2117,2118],{"class":117},"'hi'",[107,2120,353],{"class":154},[107,2122,2123,2125,2128,2130,2132,2134,2136],{"class":109,"line":239},[107,2124,302],{"class":144},[107,2126,2127],{"class":182}," clear",[107,2129,335],{"class":144},[107,2131,338],{"class":144},[107,2133,2111],{"class":154},[107,2135,378],{"class":113},[107,2137,381],{"class":154},[93,2139,2141],{"id":2140},"custom-algorithm","Custom algorithm",[15,2143,2144,2145,2147,2148,2151],{},"Replace AES-GCM with any cipher by implementing ",[42,2146,64],{},". The payload envelope is preserved; the ",[42,2149,2150],{},"version"," tag routes decrypt to the right implementation.",[98,2153,2155],{"className":128,"code":2154,"language":130,"meta":103,"style":103},"import type { CryptoAlgorithm } from '@alikhalilll\u002Fnuxt-crypto\u002Ftypes';\n\nconst myAlgo: CryptoAlgorithm = {\n  version: 'v2',\n  async deriveKey({ subtle, passphrase, salt, iterations }) {\n    \u002F* ... *\u002F\n  },\n  async encrypt({ subtle, key, plainText }) {\n    \u002F* ... *\u002F\n  },\n  async decrypt({ subtle, key, cipher, iv }) {\n    \u002F* ... *\u002F\n  },\n};\n\nconst service = await createCryptoService({\n  passphrase: 'p4ss',\n  algorithm: myAlgo,\n});\n",[42,2156,2157,2174,2178,2194,2204,2236,2241,2245,2268,2272,2276,2303,2307,2311,2316,2320,2335,2346,2352],{"__ignoreMap":103},[107,2158,2159,2161,2164,2167,2169,2172],{"class":109,"line":110},[107,2160,1097],{"class":144},[107,2162,2163],{"class":144}," type",[107,2165,2166],{"class":154}," { CryptoAlgorithm } ",[107,2168,1103],{"class":144},[107,2170,2171],{"class":117}," '@alikhalilll\u002Fnuxt-crypto\u002Ftypes'",[107,2173,1109],{"class":154},[107,2175,2176],{"class":109,"line":141},[107,2177,325],{"emptyLinePlaceholder":324},[107,2179,2180,2182,2185,2187,2190,2192],{"class":109,"line":158},[107,2181,302],{"class":144},[107,2183,2184],{"class":182}," myAlgo",[107,2186,590],{"class":144},[107,2188,2189],{"class":113}," CryptoAlgorithm",[107,2191,335],{"class":144},[107,2193,563],{"class":154},[107,2195,2196,2199,2202],{"class":109,"line":170},[107,2197,2198],{"class":154},"  version: ",[107,2200,2201],{"class":117},"'v2'",[107,2203,192],{"class":154},[107,2205,2206,2209,2212,2215,2218,2220,2223,2225,2228,2230,2233],{"class":109,"line":176},[107,2207,2208],{"class":144},"  async",[107,2210,2211],{"class":113}," deriveKey",[107,2213,2214],{"class":154},"({ ",[107,2216,2217],{"class":586},"subtle",[107,2219,1962],{"class":154},[107,2221,2222],{"class":586},"passphrase",[107,2224,1962],{"class":154},[107,2226,2227],{"class":586},"salt",[107,2229,1962],{"class":154},[107,2231,2232],{"class":586},"iterations",[107,2234,2235],{"class":154}," }) {\n",[107,2237,2238],{"class":109,"line":195},[107,2239,2240],{"class":137},"    \u002F* ... *\u002F\n",[107,2242,2243],{"class":109,"line":206},[107,2244,242],{"class":154},[107,2246,2247,2249,2252,2254,2256,2258,2261,2263,2266],{"class":109,"line":217},[107,2248,2208],{"class":144},[107,2250,2251],{"class":113}," encrypt",[107,2253,2214],{"class":154},[107,2255,2217],{"class":586},[107,2257,1962],{"class":154},[107,2259,2260],{"class":586},"key",[107,2262,1962],{"class":154},[107,2264,2265],{"class":586},"plainText",[107,2267,2235],{"class":154},[107,2269,2270],{"class":109,"line":228},[107,2271,2240],{"class":137},[107,2273,2274],{"class":109,"line":239},[107,2275,242],{"class":154},[107,2277,2278,2280,2283,2285,2287,2289,2291,2293,2296,2298,2301],{"class":109,"line":245},[107,2279,2208],{"class":144},[107,2281,2282],{"class":113}," decrypt",[107,2284,2214],{"class":154},[107,2286,2217],{"class":586},[107,2288,1962],{"class":154},[107,2290,2260],{"class":586},[107,2292,1962],{"class":154},[107,2294,2295],{"class":586},"cipher",[107,2297,1962],{"class":154},[107,2299,2300],{"class":586},"iv",[107,2302,2235],{"class":154},[107,2304,2305],{"class":109,"line":1214},[107,2306,2240],{"class":137},[107,2308,2309],{"class":109,"line":1220},[107,2310,242],{"class":154},[107,2312,2313],{"class":109,"line":1228},[107,2314,2315],{"class":154},"};\n",[107,2317,2318],{"class":109,"line":1575},[107,2319,325],{"emptyLinePlaceholder":324},[107,2321,2323,2325,2327,2329,2331,2333],{"class":109,"line":2322},16,[107,2324,302],{"class":144},[107,2326,2052],{"class":182},[107,2328,335],{"class":144},[107,2330,338],{"class":144},[107,2332,1184],{"class":113},[107,2334,155],{"class":154},[107,2336,2338,2341,2344],{"class":109,"line":2337},17,[107,2339,2340],{"class":154},"  passphrase: ",[107,2342,2343],{"class":117},"'p4ss'",[107,2345,192],{"class":154},[107,2347,2349],{"class":109,"line":2348},18,[107,2350,2351],{"class":154},"  algorithm: myAlgo,\n",[107,2353,2355],{"class":109,"line":2354},19,[107,2356,248],{"class":154},[93,2358,2360],{"id":2359},"rotating-the-passphrase","Rotating the passphrase",[98,2362,2364],{"className":128,"code":2363,"language":130,"meta":103,"style":103},"const oldService = await createCryptoService({ passphrase: OLD_PASS });\nconst newService = await createCryptoService({ passphrase: NEW_PASS });\n\nasync function rotate(payload: string): Promise\u003Cstring> {\n  const plain = await oldService.decrypt(payload);\n  return newService.encrypt(plain);\n}\n",[42,2365,2366,2388,2408,2412,2443,2460,2472],{"__ignoreMap":103},[107,2367,2368,2370,2373,2375,2377,2379,2382,2385],{"class":109,"line":110},[107,2369,302],{"class":144},[107,2371,2372],{"class":182}," oldService",[107,2374,335],{"class":144},[107,2376,338],{"class":144},[107,2378,1184],{"class":113},[107,2380,2381],{"class":154},"({ passphrase: ",[107,2383,2384],{"class":182},"OLD_PASS",[107,2386,2387],{"class":154}," });\n",[107,2389,2390,2392,2395,2397,2399,2401,2403,2406],{"class":109,"line":141},[107,2391,302],{"class":144},[107,2393,2394],{"class":182}," newService",[107,2396,335],{"class":144},[107,2398,338],{"class":144},[107,2400,1184],{"class":113},[107,2402,2381],{"class":154},[107,2404,2405],{"class":182},"NEW_PASS",[107,2407,2387],{"class":154},[107,2409,2410],{"class":109,"line":158},[107,2411,325],{"emptyLinePlaceholder":324},[107,2413,2414,2416,2418,2421,2423,2425,2427,2429,2432,2434,2436,2438,2440],{"class":109,"line":170},[107,2415,574],{"class":144},[107,2417,532],{"class":144},[107,2419,2420],{"class":113}," rotate",[107,2422,347],{"class":154},[107,2424,632],{"class":586},[107,2426,590],{"class":144},[107,2428,637],{"class":182},[107,2430,2431],{"class":154},")",[107,2433,590],{"class":144},[107,2435,827],{"class":113},[107,2437,278],{"class":154},[107,2439,699],{"class":182},[107,2441,2442],{"class":154},"> {\n",[107,2444,2445,2447,2449,2451,2453,2456,2458],{"class":109,"line":176},[107,2446,543],{"class":144},[107,2448,369],{"class":182},[107,2450,335],{"class":144},[107,2452,338],{"class":144},[107,2454,2455],{"class":154}," oldService.",[107,2457,378],{"class":113},[107,2459,381],{"class":154},[107,2461,2462,2464,2467,2469],{"class":109,"line":195},[107,2463,560],{"class":144},[107,2465,2466],{"class":154}," newService.",[107,2468,344],{"class":113},[107,2470,2471],{"class":154},"(plain);\n",[107,2473,2474],{"class":109,"line":206},[107,2475,673],{"class":154},[93,2477,2479],{"id":2478},"payload-format","Payload format",[15,2481,2482,2485],{},[42,2483,2484],{},"v1.{saltB64}.{ivB64}.{cipherB64}"," — four dot-separated segments, each standard base64.",[921,2487,2488,2501],{},[924,2489,2490],{},[927,2491,2492,2495,2498],{},[930,2493,2494],{},"Segment",[930,2496,2497],{},"Bytes",[930,2499,2500],{},"Notes",[937,2502,2503,2516,2526,2536],{},[927,2504,2505,2510,2513],{},[942,2506,2507],{},[42,2508,2509],{},"v1",[942,2511,2512],{},"—",[942,2514,2515],{},"Algorithm \u002F version tag.",[927,2517,2518,2520,2523],{},[942,2519,2227],{},[942,2521,2522],{},"16",[942,2524,2525],{},"Per-encryption PBKDF2 salt.",[927,2527,2528,2530,2533],{},[942,2529,2300],{},[942,2531,2532],{},"12",[942,2534,2535],{},"AES-GCM initialization vector.",[927,2537,2538,2540,2543],{},[942,2539,2295],{},[942,2541,2542],{},"N",[942,2544,2545],{},"Ciphertext + 16-byte GCM auth tag.",[93,2547,2549],{"id":2548},"module-options","Module options",[921,2551,2552,2568],{},[924,2553,2554],{},[927,2555,2556,2559,2562,2565],{},[930,2557,2558],{},"Option",[930,2560,2561],{},"Type",[930,2563,2564],{},"Default",[930,2566,2567],{},"Purpose",[937,2569,2570,2588,2614,2632,2650],{},[927,2571,2572,2576,2580,2585],{},[942,2573,2574],{},[42,2575,2222],{},[942,2577,2578],{},[42,2579,699],{},[942,2581,2582],{},[42,2583,2584],{},"''",[942,2586,2587],{},"Passphrase to derive the AES key from. Throws at use if empty.",[927,2589,2590,2595,2599,2603],{},[942,2591,2592],{},[42,2593,2594],{},"provideName",[942,2596,2597],{},[42,2598,699],{},[942,2600,2601],{},[42,2602,201],{},[942,2604,2605,2606,2609,2610,2613],{},"Injected under ",[42,2607,2608],{},"$\u003Cname>",". Leading ",[42,2611,2612],{},"$"," is stripped.",[927,2615,2616,2620,2625,2629],{},[942,2617,2618],{},[42,2619,2232],{},[942,2621,2622],{},[42,2623,2624],{},"number",[942,2626,2627],{},[42,2628,212],{},[942,2630,2631],{},"PBKDF2 iteration count.",[927,2633,2634,2639,2643,2647],{},[942,2635,2636],{},[42,2637,2638],{},"keyCacheSize",[942,2640,2641],{},[42,2642,2624],{},[942,2644,2645],{},[42,2646,223],{},[942,2648,2649],{},"Max derived keys kept in memory. Set to 0 to disable caching.",[927,2651,2652,2657,2662,2666],{},[942,2653,2654],{},[42,2655,2656],{},"serverOnly",[942,2658,2659],{},[42,2660,2661],{},"boolean",[942,2663,2664],{},[42,2665,234],{},[942,2667,2668],{},"When true, plugin runs only on the server.",[93,2670,2672],{"id":2671},"exported-types","Exported types",[98,2674,2676],{"className":128,"code":2675,"language":130,"meta":103,"style":103},"import type {\n  CryptoService,\n  CryptoServiceConfig,\n  CryptoAlgorithm,\n  CryptoModuleOptions,\n  Bytes,\n  ParsedPayload,\n} from '@alikhalilll\u002Fnuxt-crypto\u002Ftypes';\n",[42,2677,2678,2686,2691,2696,2701,2706,2711,2716],{"__ignoreMap":103},[107,2679,2680,2682,2684],{"class":109,"line":110},[107,2681,1097],{"class":144},[107,2683,2163],{"class":144},[107,2685,563],{"class":154},[107,2687,2688],{"class":109,"line":141},[107,2689,2690],{"class":154},"  CryptoService,\n",[107,2692,2693],{"class":109,"line":158},[107,2694,2695],{"class":154},"  CryptoServiceConfig,\n",[107,2697,2698],{"class":109,"line":170},[107,2699,2700],{"class":154},"  CryptoAlgorithm,\n",[107,2702,2703],{"class":109,"line":176},[107,2704,2705],{"class":154},"  CryptoModuleOptions,\n",[107,2707,2708],{"class":109,"line":195},[107,2709,2710],{"class":154},"  Bytes,\n",[107,2712,2713],{"class":109,"line":206},[107,2714,2715],{"class":154},"  ParsedPayload,\n",[107,2717,2718,2721,2723,2725],{"class":109,"line":217},[107,2719,2720],{"class":154},"} ",[107,2722,1103],{"class":144},[107,2724,2171],{"class":117},[107,2726,1109],{"class":154},[2728,2729,2730],"style",{},"html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":103,"searchDepth":141,"depth":141,"links":2732},[2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2751,2752,2753,2754,2755,2756],{"id":95,"depth":141,"text":96},{"id":124,"depth":141,"text":125},{"id":265,"depth":141,"text":266},{"id":405,"depth":141,"text":406},{"id":676,"depth":141,"text":677},{"id":804,"depth":141,"text":805},{"id":918,"depth":141,"text":919},{"id":997,"depth":141,"text":79},{"id":1068,"depth":141,"text":1069},{"id":1328,"depth":141,"text":85,"children":2743},[2744,2745,2746,2747,2748,2750],{"id":1342,"depth":158,"text":1343},{"id":1417,"depth":158,"text":1418},{"id":1591,"depth":158,"text":1592},{"id":1748,"depth":158,"text":1749},{"id":1819,"depth":158,"text":2749},"deriveFingerprint — bring your own device ID",{"id":1903,"depth":158,"text":1904},{"id":2024,"depth":141,"text":39},{"id":2140,"depth":141,"text":2141},{"id":2359,"depth":141,"text":2360},{"id":2478,"depth":141,"text":2479},{"id":2548,"depth":141,"text":2549},{"id":2671,"depth":141,"text":2672},"AES-256-GCM + PBKDF2 encryption for Nuxt 3\u002F4 using the Web Crypto API, with key caching and pluggable algorithms.","md",{},"\u002Fcrypto",{"title":5,"description":2757},"roO8oLQPXYdP74zvZVv6yuXXvMI07WPh1j_4YU65NNk",1776531510116]