[{"data":1,"prerenderedAt":6566},["ShallowReactive",2],{"gitee":3},[4,866],{"id":5,"title":6,"articleId":7,"articleThumb":8,"body":9,"categoryId":329,"categoryInfo":829,"createDate":833,"description":834,"excerpt":835,"extension":840,"isHidden":841,"isHot":842,"isRecommend":841,"isTop":841,"meta":843,"navigation":842,"path":844,"rawbody":845,"readingTime":428,"seo":846,"sitemap":847,"stem":855,"tagIds":856,"tagInfo":857,"updateDate":863,"wordCount":864,"__hash__":865},"blog\u002Fblog\u002F2.github仓库代码和gitee仓库代码双向自动同步方案.md","github仓库代码和gitee仓库代码双向自动同步方案","github-gitee-sync","https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20260327203348205.webp",{"type":10,"value":11,"toc":813},"minimark",[12,16,19,22,27,39,51,57,70,76,83,88,91,100,123,134,137,141,144,153,198,201,204,207,218,226,230,237,241,247,251,257,261,264,281,468,471,475,484,495,499,502,507,516,521,528,533,543,689,696,709,712,788,791,795,798,801,805,809],[13,14,15],"p",{},"Github同步代码至Gitee或者是从Gitee更新代码，都需要借助Github Actions功能。GitHub Actions‌ 是 GitHub 提供的持续集成与持续交付（CI\u002FCD）平台，主要用于自动化软件开发流程中的构建、测试和部署任务。通过yml格式文件配置workflow实现自动化操作，可以实现一次提交，多仓库代码同步。目前Github Actions在实现本地代码提交Github仓库时，同步代码到Gitee，可以自动触发workflow执行，实现自动同步功能。反过来，当Gitee代码库存在更新，Github从Gitee同步更新时，借助Github Actions无法实现自动，只能借助定时任务，定时从Gitee同步代码。",[13,17,18],{},"相对于Github同步方案而言，Gitee同步代码至Github或者从Github更新代码非常简单。借助Gitee提供的强大的仓库镜像功能，只需要在Gitee的源仓库上进行简单配置就可以实现。",[13,20,21],{},"下面介绍Github和Gitee双向同步的具体实现步骤。",[23,24,26],"h2",{"id":25},"_1-github自动同步gitee","1 Github自动同步Gitee",[13,28,29,30,38],{},"Github自动同步Gitee是指在本地提交代码至Github远程仓库时，同步将代码更新至Gitee仓库。目前通过Github Actions 实现Github仓库同步到gitee的Actions有很多，本文主要讲述如何使用【",[31,32,37],"a",{"href":33,"rel":34,"target":36},"https:\u002F\u002Fgithub.com\u002Fwearerequired\u002Fgit-mirror-action",[35],"nofollow","_blank","git-mirror-action","】镜像来实现同步操作。",[13,40,41,45,46,50],{},[42,43,37],"code",{"color":44},"primary"," 是一个用于自动同步 GitHub 和 Gitee（或其他 Git 平台）仓库的 GitHub Action，通过配置工作流（workflow）实现自动化部署。github仓库地址为：【",[31,47,49],{"href":33,"rel":48,"target":36},[35],"wearerequired\u002Fgit-mirror-action@master","】",[13,52,53],{},[54,55,56],"strong",{},"核心功能如下：",[58,59,60,64,67],"ul",{},[61,62,63],"li",{},"双向同步：支持将 GitHub 仓库同步到 Gitee 或其他平台，也可实现反向同步。",[61,65,66],{},"‌灵活配置‌：通过 .yml 文件定义同步规则，支持源仓库与目标仓库的地址映射。",[61,68,69],{},"自动化部署‌：结合 GitHub Actions 工作流，可在代码更新时自动触发同步操作。",[13,71,72,75],{},[54,73,74],{},"同步的原理","：利用 SSH 公私钥配对的方式拉取 Github 仓库的代码并推送到 Gitee 仓库中。",[13,77,78,79],{},"详细配置参考：",[31,80,82],{"href":33,"rel":81},[35],"github源码仓库文档",[84,85,87],"h3",{"id":86},"_11-创建gitee目标仓库","1.1 创建Gitee目标仓库",[13,89,90],{},"在实现github仓库同步到gitee仓库之前，首先需要在gitee初始化一个仓库，作为同步的目标仓库。可以直接创建一个空仓库，或者直接通过gitee自带的导入github仓库代码功能。具体导入github仓库步骤如下：",[13,92,93,94,99],{},"① 登陆[",[31,95,98],{"href":96,"rel":97,"target":36},"https:\u002F\u002Fgitee.com\u002Flogin",[35],"gitee","] -> 右上角加号 -> 从github\u002Fgitlab导入仓库。进入页面后，可直接通过url输入，也可以通过github授权后，批量选择导入的仓库。",[101,102,105],"callout",{"color":103,"icon":104},"warning","material-symbols:info-outline",[13,106,107,108,111,112,115,116,50,120,122],{},"温馨提示",[109,110],"br",{},"\n1 从url导入私有仓库时，需要填写仓库的账户和个人令牌。此处的令牌并非密码(github现在不建议直接账户密码认证)，而是GitHub 的「",[54,113,114],{},"Personal access token","」。点击前往设置【",[31,117,114],{"href":118,"rel":119,"target":36},"https:\u002F\u002Fgithub.com\u002Fsettings\u002Ftokens",[35],[109,121],{},"\n2 从github导入仓库，需要github授权才行，授权后可以选择需要导入的仓库。",[13,124,125,126,130,131,133],{},"② 仓库导入成功后，进入仓库，仓库名称后面会显示一个 ",[127,128],"icon",{"name":129},"material-symbols-light:refresh-rounded"," 符号，点击便可手动同步github仓库代码至gitee。私有仓库同步时，需要输入账号和个人令牌(github的",[54,132,114],{},")",[13,135,136],{},"至此，github仓库同步gitee仓库前期准备工作已准备完毕，下面开始介绍如何配置github自动同步代码至gitee。",[84,138,140],{"id":139},"_12-创建ssh公私钥","1.2 创建SSH公私钥",[13,142,143],{},"SSH密钥对由公钥和私钥组成，公钥用于加密数据，私钥用于解密数据，通过非对称加密确保通信安全。使用 SSH 协议可以连接远程服务器和服务并向它们验证。 利用 SSH 密钥可以连接到 GitHub，而无需在每次访问时都提供用户名和 personal access token。GitHub通过公钥加密信息，私钥用于解密，从而确认用户身份。",[13,145,146,147,149,150,152],{},"①在本地机器上打开终端应用程序",[109,148],{},"\n②生成SSH密钥对",[109,151],{},"\n使用ssh-keygen命令来生成密钥对。您可以通过以下命令开始：",[154,155,160],"pre",{"className":156,"code":157,"language":158,"meta":159,"style":159},"language-shell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","ssh-keygen -t rsa  -b 4096 -C \"youremail@example.com\"\n","shell","",[42,161,162],{"__ignoreMap":159},[163,164,167,171,175,178,181,185,188,192,195],"span",{"class":165,"line":166},"line",1,[163,168,170],{"class":169},"sBMFI","ssh-keygen",[163,172,174],{"class":173},"sfazB"," -t",[163,176,177],{"class":173}," rsa",[163,179,180],{"class":173},"  -b",[163,182,184],{"class":183},"sbssI"," 4096",[163,186,187],{"class":173}," -C",[163,189,191],{"class":190},"sMK4o"," \"",[163,193,194],{"class":173},"youremail@example.com",[163,196,197],{"class":190},"\"\n",[13,199,200],{},"这里，-t rsa指定了密钥的类型为RSA，-b 4096指定了密钥的位长度为4096位。RSA密钥的长度可以是1024位、2048位或4096位，推荐使用2048位或更高的安全性。-C（Comment）是 ssh-keygen 命令的一个选项，用于为生成的 SSH 密钥对添加注释。注释内容可以是任意文本，但通常推荐使用邮箱地址或密钥用途描述。注释仅保存在公钥文件中，私钥文件不包含此信息。",[13,202,203],{},"执行上述命令后，系统会询问您希望将密钥存储在哪个文件中，默认是~\u002F.ssh\u002Fid_rsa（私钥）和~\u002F.ssh\u002Fid_rsa.pub（公钥）。您可以按回车接受默认设置，或者输入其他路径来存储密钥文件。",[13,205,206],{},"接下来，系统会询问您是否要为密钥设置密码（passphrase）。强烈建议设置密码，因为即使私钥文件被保护，知道密码仍然可以访问您的服务器。如果您不想设置密码，可以按回车跳过此步骤。",[13,208,209,210,213,214,217],{},"如果连续三次回车，生成的秘钥对存储默认位置。",[42,211,212],{"color":44},"id_rsa"," 为私钥，",[42,215,216],{"color":44},"id_rsa.pub","为公钥。如果提示：already exists（已经存在），则可以到电脑位置：C:\\Users\\电脑账号名\\ .ssh 直接使用。",[13,219,220,221,50],{},"具体图文参考：【",[31,222,225],{"href":223,"rel":224,"target":36},"https:\u002F\u002Fgitee.com\u002Fhelp\u002Farticles\u002F4181#article-header0",[35],"生成\u002F添加SSH公钥",[84,227,229],{"id":228},"_13-github配置ssh私钥","1.3 GitHub配置SSH私钥",[13,231,232,233],{},"打开Github需要同步的项目，点击Settings->Secrets->Actions，设置私钥名称为：GITEE_RSA_PRIVATE_KEY，值为：上面生成SSH的私钥，即id_rsa 文件的内容\n",[234,235],"img",{"alt":159,"src":236},"https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910125740784.webp",[84,238,240],{"id":239},"_14-github配置ssh公钥","1.4 GitHub配置SSH公钥",[13,242,243,244],{},"在Github点击右上角用户图像，点击Settings->SSH and GPG keys->New SSH key，名称为：GITEE_RSA_PUBLIC_KEY，值为：上面生成SSH的公钥，即id_rsa_pub文件内容\n",[234,245],{"alt":159,"src":246},"https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910130224827.webp",[84,248,250],{"id":249},"_15-gitee配置ssh公钥","1.5 Gitee配置SSH公钥",[13,252,253,254],{},"在Gitee 设置->安全设置->SSH公钥，标题为：GITEE_RSA_PUBLIC_KEY，值为：上面生成SSH的公钥，即id_rsa_pub文件内容\n",[234,255],{"alt":159,"src":256},"https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910130741176.webp",[84,258,260],{"id":259},"_16-创建github-workflow","1.6 创建Github workflow",[13,262,263],{},"github创建workflow有2种方式，第一种直接在github项目中，点击actions创建新的workflow，第二种就是将项目clone本地，进行配置。二者大同小异，都是配置相应的设置。",[13,265,266,267,270,273,276,277,280],{},"①第一种：点击actions创建新的workflow\n",[234,268],{"alt":159,"src":269},"https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910131433758.webp",[234,271],{"alt":159,"src":272},"https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910131520866.webp",[234,274],{"alt":159,"src":275},"https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910131747275.webp","\n②第二种：将项目clone到本地，用vscode打开，根目录下创建文件",[42,278,279],{"color":44},".github\\workflows\\sync-to-gitee.yml","，内容如下：",[154,282,287],{"className":283,"code":284,"filename":285,"language":286,"meta":159,"style":159},"language-yml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","name: Sync to Gitee\non:\n    push:\n        branches:\n            # 或者你想要同步的分支\n            - main\njobs:\n    sync:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Sync to Gitee\n              uses: wearerequired\u002Fgit-mirror-action@master\n              env:\n                  # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY\n                  SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}\n              with:\n                  # 注意替换为你的 GitHub 源仓库地址\n                  source-repo: git@github.com:AnyFork\u002Fanyfork-docs-client.git\n                  # 注意替换为你的 Gitee 目标仓库地址\n                  destination-repo: git@gitee.com:AnyFork\u002Fanyfork-docs-client.git\n","sync-to-gitee.yml","yml",[42,288,289,301,311,319,327,334,343,351,359,370,378,390,401,409,415,426,434,440,451,457],{"__ignoreMap":159},[163,290,291,295,298],{"class":165,"line":166},[163,292,294],{"class":293},"swJcz","name",[163,296,297],{"class":190},":",[163,299,300],{"class":173}," Sync to Gitee\n",[163,302,304,308],{"class":165,"line":303},2,[163,305,307],{"class":306},"sfNiH","on",[163,309,310],{"class":190},":\n",[163,312,314,317],{"class":165,"line":313},3,[163,315,316],{"class":293},"    push",[163,318,310],{"class":190},[163,320,322,325],{"class":165,"line":321},4,[163,323,324],{"class":293},"        branches",[163,326,310],{"class":190},[163,328,330],{"class":165,"line":329},5,[163,331,333],{"class":332},"sHwdD","            # 或者你想要同步的分支\n",[163,335,337,340],{"class":165,"line":336},6,[163,338,339],{"class":190},"            -",[163,341,342],{"class":173}," main\n",[163,344,346,349],{"class":165,"line":345},7,[163,347,348],{"class":293},"jobs",[163,350,310],{"class":190},[163,352,354,357],{"class":165,"line":353},8,[163,355,356],{"class":293},"    sync",[163,358,310],{"class":190},[163,360,362,365,367],{"class":165,"line":361},9,[163,363,364],{"class":293},"        runs-on",[163,366,297],{"class":190},[163,368,369],{"class":173}," ubuntu-latest\n",[163,371,373,376],{"class":165,"line":372},10,[163,374,375],{"class":293},"        steps",[163,377,310],{"class":190},[163,379,381,383,386,388],{"class":165,"line":380},11,[163,382,339],{"class":190},[163,384,385],{"class":293}," name",[163,387,297],{"class":190},[163,389,300],{"class":173},[163,391,393,396,398],{"class":165,"line":392},12,[163,394,395],{"class":293},"              uses",[163,397,297],{"class":190},[163,399,400],{"class":173}," wearerequired\u002Fgit-mirror-action@master\n",[163,402,404,407],{"class":165,"line":403},13,[163,405,406],{"class":293},"              env",[163,408,310],{"class":190},[163,410,412],{"class":165,"line":411},14,[163,413,414],{"class":332},"                  # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY\n",[163,416,418,421,423],{"class":165,"line":417},15,[163,419,420],{"class":293},"                  SSH_PRIVATE_KEY",[163,422,297],{"class":190},[163,424,425],{"class":173}," ${{ secrets.GITEE_RSA_PRIVATE_KEY }}\n",[163,427,429,432],{"class":165,"line":428},16,[163,430,431],{"class":293},"              with",[163,433,310],{"class":190},[163,435,437],{"class":165,"line":436},17,[163,438,439],{"class":332},"                  # 注意替换为你的 GitHub 源仓库地址\n",[163,441,443,446,448],{"class":165,"line":442},18,[163,444,445],{"class":293},"                  source-repo",[163,447,297],{"class":190},[163,449,450],{"class":173}," git@github.com:AnyFork\u002Fanyfork-docs-client.git\n",[163,452,454],{"class":165,"line":453},19,[163,455,456],{"class":332},"                  # 注意替换为你的 Gitee 目标仓库地址\n",[163,458,460,463,465],{"class":165,"line":459},20,[163,461,462],{"class":293},"                  destination-repo",[163,464,297],{"class":190},[163,466,467],{"class":173}," git@gitee.com:AnyFork\u002Fanyfork-docs-client.git\n",[13,469,470],{},"至此，github workflow配置完毕。以后提交代码至github仓库时，便会触发workflow自动执行，自动将github仓库代码同步至gitee仓库中。",[23,472,474],{"id":473},"_2-github从gitee同步更新","2 Github从Gitee同步更新",[13,476,477,478,483],{},"Github仓库从Gitee仓库同步代码，也是借助Github Actions功能进行实现，能够实现此功能的Actions较多，本文还是采用\n【",[31,479,482],{"href":480,"rel":481,"target":36},"https:\u002F\u002Fgithub.com\u002FYikun\u002Fhub-mirror-action",[35],"Yikun\u002Fgitee-mirror-action","】进行实现。",[13,485,486,487,490,491,494],{},"由于Github Actions在Gitee代码仓库更新时，无法自动感知gitee仓库发生更新，从而不能自动触发workflow的执行，因此形成了2种不同的同步方式：",[54,488,489],{},"webhooks实时同步方式","和",[54,492,493],{},"定时延迟同步方式","。下面分别介绍这2种同步方式的具体实现步骤。",[84,496,498],{"id":497},"_21-定时延迟同步方式","2.1 定时延迟同步方式",[13,500,501],{},"定时延迟同步方式是最简单的一种方式，虽然存在延迟，但大部分情况下对代码同步的实时性要求并不高。可以根据实际需求，调整定时任务频率，增加同步次数。",[13,503,504],{},[54,505,506],{},"1 创建Github目标仓库",[13,508,509,510,515],{},"在实现从Gitee同步代码至目标仓库Github之前，需要在Github上创建一个空的仓库作为同步的目标仓库。登陆【",[31,511,514],{"href":512,"rel":513,"target":36},"https:\u002F\u002Fgithub.com\u002F",[35],"gihub","】->右上角加号->创建仓库，私有或者公有仓库均可。",[13,517,518],{},[54,519,520],{},"2 目标仓库配置SSH私钥",[13,522,523,524,50],{},"github目标仓库创建好后，需要配置仓库SSH私钥，具体步骤参考：【",[31,525,527],{"href":526},"#_13-github%E9%85%8D%E7%BD%AEssh%E7%A7%81%E9%92%A5","1.3 github配置ssh私钥",[13,529,530],{},[54,531,532],{},"3 目标仓库创建Github workflow",[13,534,535,536,539,540,280],{},"在【",[31,537,260],{"href":538},"#_16-%E5%88%9B%E5%BB%BAgithub-workflow","】已经讲述了如何创建Github workflow，此处不在赘述。下面采用第二种方式进行配置。将项目clone到本地，用vscode打开，根目录下创建文件",[42,541,542],{"color":44},".github\\workflows\\sync-to-github.yml",[154,544,547],{"className":283,"code":545,"filename":546,"language":286,"meta":159,"style":159},"name: Sync to Github\non:\n   schedule:\n    # 每个小时10-59分钟之间，从10分钟开始，每5分钟执行一次\n    - cron:  '10\u002F5 * * * *'\njobs:\n    sync:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Sync to Github\n              uses: wearerequired\u002Fgit-mirror-action@master\n              env:\n                  # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY\n                  SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}\n              with:\n                  # 注意替换为你的 Gitee源仓库地址\n                  source-repo: git@gitee.com:AnyFork\u002Fanyfork-docs-client.git\n                  # 注意替换为你的 Github 目标仓库地址\n                  destination-repo: git@github.com:AnyFork\u002Fanyfork-docs-client.git\n","sync-to-github.yml",[42,548,549,558,564,571,576,595,601,607,615,621,631,639,645,649,657,663,668,676,681],{"__ignoreMap":159},[163,550,551,553,555],{"class":165,"line":166},[163,552,294],{"class":293},[163,554,297],{"class":190},[163,556,557],{"class":173}," Sync to Github\n",[163,559,560,562],{"class":165,"line":303},[163,561,307],{"class":306},[163,563,310],{"class":190},[163,565,566,569],{"class":165,"line":313},[163,567,568],{"class":293},"   schedule",[163,570,310],{"class":190},[163,572,573],{"class":165,"line":321},[163,574,575],{"class":332},"    # 每个小时10-59分钟之间，从10分钟开始，每5分钟执行一次\n",[163,577,578,581,584,586,589,592],{"class":165,"line":329},[163,579,580],{"class":190},"    -",[163,582,583],{"class":293}," cron",[163,585,297],{"class":190},[163,587,588],{"class":190},"  '",[163,590,591],{"class":173},"10\u002F5 * * * *",[163,593,594],{"class":190},"'\n",[163,596,597,599],{"class":165,"line":336},[163,598,348],{"class":293},[163,600,310],{"class":190},[163,602,603,605],{"class":165,"line":345},[163,604,356],{"class":293},[163,606,310],{"class":190},[163,608,609,611,613],{"class":165,"line":353},[163,610,364],{"class":293},[163,612,297],{"class":190},[163,614,369],{"class":173},[163,616,617,619],{"class":165,"line":361},[163,618,375],{"class":293},[163,620,310],{"class":190},[163,622,623,625,627,629],{"class":165,"line":372},[163,624,339],{"class":190},[163,626,385],{"class":293},[163,628,297],{"class":190},[163,630,557],{"class":173},[163,632,633,635,637],{"class":165,"line":380},[163,634,395],{"class":293},[163,636,297],{"class":190},[163,638,400],{"class":173},[163,640,641,643],{"class":165,"line":392},[163,642,406],{"class":293},[163,644,310],{"class":190},[163,646,647],{"class":165,"line":403},[163,648,414],{"class":332},[163,650,651,653,655],{"class":165,"line":411},[163,652,420],{"class":293},[163,654,297],{"class":190},[163,656,425],{"class":173},[163,658,659,661],{"class":165,"line":417},[163,660,431],{"class":293},[163,662,310],{"class":190},[163,664,665],{"class":165,"line":428},[163,666,667],{"class":332},"                  # 注意替换为你的 Gitee源仓库地址\n",[163,669,670,672,674],{"class":165,"line":436},[163,671,445],{"class":293},[163,673,297],{"class":190},[163,675,467],{"class":173},[163,677,678],{"class":165,"line":442},[163,679,680],{"class":332},"                  # 注意替换为你的 Github 目标仓库地址\n",[163,682,683,685,687],{"class":165,"line":453},[163,684,462],{"class":293},[163,686,297],{"class":190},[163,688,450],{"class":173},[13,690,691,692,695],{},"上面配置了",[42,693,694],{"color":44},"on:schedule:cron","定时任务，时间到了定时任务设置的时间，便会触发workflow执行。",[101,697,698],{"color":103,"icon":104},[13,699,107,700,702,703,705,706,708],{},[109,701],{},"\n1 设定的 schedule 是UCT时间，比如设置的08:00（也就是北京时间的16:00），但实际运行的时间为北京时间17:00 至 17：20 不等。",[109,704],{},"\n2 Schedule在 GitHub 操作工作流运行的高负载期间，事件可能会延迟。高负载时间包括每小时开始。为了减少延迟的可能性，请安排您的工作流在不同的时间运行。实测延迟的时间为几十分钟，或者超过一个小时，甚至在某种极端情况下，将不会执行。",[109,707],{},"\n3 Schedule 设置的 cron 时刻，仅仅是工作流进行计划排队的时刻，而不是准确的运行时刻。",[13,710,711],{},"GitHub Actions 默认使用 UTC 时间。如果你的工作流程应该在特定本地时间执行，你可以在 YAML 文件中指定时区：",[154,713,715],{"className":283,"code":714,"language":286,"meta":159,"style":159},"jobs:\n  job1:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Set Timezone\n      uses: szenius\u002Fset-timezone@v1.0\n      with:\n        timezoneLinux: \"Asia\u002FShanghai\"\n\n",[42,716,717,723,730,739,746,757,767,774],{"__ignoreMap":159},[163,718,719,721],{"class":165,"line":166},[163,720,348],{"class":293},[163,722,310],{"class":190},[163,724,725,728],{"class":165,"line":303},[163,726,727],{"class":293},"  job1",[163,729,310],{"class":190},[163,731,732,735,737],{"class":165,"line":313},[163,733,734],{"class":293},"    runs-on",[163,736,297],{"class":190},[163,738,369],{"class":173},[163,740,741,744],{"class":165,"line":321},[163,742,743],{"class":293},"    steps",[163,745,310],{"class":190},[163,747,748,750,752,754],{"class":165,"line":329},[163,749,580],{"class":190},[163,751,385],{"class":293},[163,753,297],{"class":190},[163,755,756],{"class":173}," Set Timezone\n",[163,758,759,762,764],{"class":165,"line":336},[163,760,761],{"class":293},"      uses",[163,763,297],{"class":190},[163,765,766],{"class":173}," szenius\u002Fset-timezone@v1.0\n",[163,768,769,772],{"class":165,"line":345},[163,770,771],{"class":293},"      with",[163,773,310],{"class":190},[163,775,776,779,781,783,786],{"class":165,"line":353},[163,777,778],{"class":293},"        timezoneLinux",[163,780,297],{"class":190},[163,782,191],{"class":190},[163,784,785],{"class":173},"Asia\u002FShanghai",[163,787,197],{"class":190},[13,789,790],{},"对于一些不需要实时进行同步的需求，可以采用定时延迟同步方案，方便配置也简单。",[84,792,794],{"id":793},"_22-实时同步方式","2.2 实时同步方式",[13,796,797],{},"Webhook是一种事件驱动的通知机制，当发生特定事件（如代码推送、拉取请求创建等）时，会自动向指定的外部服务器发送HTTP POST请求（通常包含事件数据）。这种机制替代了轮询API的实时通知方式，允许开发者在事件发生时立即触发外部操作（如部署、测试或发送通知）。",[13,799,800],{},"Github和Gitee都支持Webhook功能，因此我们可以借助这一功能，在gitee代码发生提交时，触发gitee配置的自定义webhook，webhook通过调用github Api触发github actions自动执行，从而实现实时同步方式。",[23,802,804],{"id":803},"_3-gitee自动同步github","3 Gitee自动同步Github",[23,806,808],{"id":807},"_4-gitee从github同步更新","4 Gitee从Github同步更新",[810,811,812],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":159,"searchDepth":313,"depth":313,"links":814},[815,823,827,828],{"id":25,"depth":303,"text":26,"children":816},[817,818,819,820,821,822],{"id":86,"depth":313,"text":87},{"id":139,"depth":313,"text":140},{"id":228,"depth":313,"text":229},{"id":239,"depth":313,"text":240},{"id":249,"depth":313,"text":250},{"id":259,"depth":313,"text":260},{"id":473,"depth":303,"text":474,"children":824},[825,826],{"id":497,"depth":313,"text":498},{"id":793,"depth":313,"text":794},{"id":803,"depth":303,"text":804},{"id":807,"depth":303,"text":808},{"name":830,"symbol":831,"icon":832,"id":329},"仓库同步","repository","material-symbols-light:sync-desktop-outline","2025-09-09 21:55:35","‌日常工作中，可能会遇到github代码仓库需要自动同步代码至gitee仓库，或者gitee仓库代码同步至github仓库的应用场景，针对这个应用场景，下面便归纳一下github和gitee仓库实现双向自动同步方案和步骤。",{"type":10,"value":836},[837],[13,838,839],{},"到目前为止，想要实现在本地提交代码至Github仓库时，同时更新至Gitee仓库是可行的。相反，想要在本地提交代码至Gitee仓库时，同时更新至Github仓库也可以实现的。无论我们使用Github还是Gitee任意一个远程仓库，在代码提交时，头可以自动同步到另一个远程仓库，即所谓的双向自动同步(可推送至远端仓库，亦可从远端仓库同步更新)。当然，日常开发中，按照个人喜好，我们只需要选择一种即可。","md",false,true,{},"\u002Fblog\u002Fgithubgitee","---\ntitle: github仓库代码和gitee仓库代码双向自动同步方案\ndescription: ‌日常工作中，可能会遇到github代码仓库需要自动同步代码至gitee仓库，或者gitee仓库代码同步至github仓库的应用场景，针对这个应用场景，下面便归纳一下github和gitee仓库实现双向自动同步方案和步骤。\narticleId: github-gitee-sync\ncreateDate: \"2025-09-09 21:55:35\"\nupdateDate: \"2026-03-12 10:02:23\"\narticleThumb: https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20260327203348205.webp\ntagIds: \n    - 2\n    - 3\ncategoryId: 5\nisTop: false\nisRecommend: false\nisHot: true\nisHidden: false\nnavigation: true\n---\n\n到目前为止，想要实现在本地提交代码至Github仓库时，同时更新至Gitee仓库是可行的。相反，想要在本地提交代码至Gitee仓库时，同时更新至Github仓库也可以实现的。无论我们使用Github还是Gitee任意一个远程仓库，在代码提交时，头可以自动同步到另一个远程仓库，即所谓的双向自动同步(可推送至远端仓库，亦可从远端仓库同步更新)。当然，日常开发中，按照个人喜好，我们只需要选择一种即可。\n\u003C!--more-->\n\n\nGithub同步代码至Gitee或者是从Gitee更新代码，都需要借助Github Actions功能。GitHub Actions‌ 是 GitHub 提供的持续集成与持续交付（CI\u002FCD）平台，主要用于自动化软件开发流程中的构建、测试和部署任务。通过yml格式文件配置workflow实现自动化操作，可以实现一次提交，多仓库代码同步。目前Github Actions在实现本地代码提交Github仓库时，同步代码到Gitee，可以自动触发workflow执行，实现自动同步功能。反过来，当Gitee代码库存在更新，Github从Gitee同步更新时，借助Github Actions无法实现自动，只能借助定时任务，定时从Gitee同步代码。\n\n\n相对于Github同步方案而言，Gitee同步代码至Github或者从Github更新代码非常简单。借助Gitee提供的强大的仓库镜像功能，只需要在Gitee的源仓库上进行简单配置就可以实现。\n\n\n下面介绍Github和Gitee双向同步的具体实现步骤。\n\n\n## 1 Github自动同步Gitee\nGithub自动同步Gitee是指在本地提交代码至Github远程仓库时，同步将代码更新至Gitee仓库。目前通过Github Actions 实现Github仓库同步到gitee的Actions有很多，本文主要讲述如何使用【[git-mirror-action](https:\u002F\u002Fgithub.com\u002Fwearerequired\u002Fgit-mirror-action){target=_blank}】镜像来实现同步操作。\n\n`git-mirror-action`{color=primary} 是一个用于自动同步 GitHub 和 Gitee（或其他 Git 平台）仓库的 GitHub Action，通过配置工作流（workflow）实现自动化部署。github仓库地址为：【[wearerequired\u002Fgit-mirror-action@master](https:\u002F\u002Fgithub.com\u002Fwearerequired\u002Fgit-mirror-action){target=_blank}】\n \n**核心功能如下：**\n- 双向同步：支持将 GitHub 仓库同步到 Gitee 或其他平台，也可实现反向同步。\n- ‌灵活配置‌：通过 .yml 文件定义同步规则，支持源仓库与目标仓库的地址映射。\n- 自动化部署‌：结合 GitHub Actions 工作流，可在代码更新时自动触发同步操作。\n\n**同步的原理**：利用 SSH 公私钥配对的方式拉取 Github 仓库的代码并推送到 Gitee 仓库中。\n\n\n详细配置参考：[github源码仓库文档](https:\u002F\u002Fgithub.com\u002Fwearerequired\u002Fgit-mirror-action)\n\n\n### 1.1 创建Gitee目标仓库\n在实现github仓库同步到gitee仓库之前，首先需要在gitee初始化一个仓库，作为同步的目标仓库。可以直接创建一个空仓库，或者直接通过gitee自带的导入github仓库代码功能。具体导入github仓库步骤如下：\n\n\n① 登陆[[gitee](https:\u002F\u002Fgitee.com\u002Flogin){target=_blank}] -> 右上角加号 -> 从github\u002Fgitlab导入仓库。进入页面后，可直接通过url输入，也可以通过github授权后，批量选择导入的仓库。\n::callout{icon=\"material-symbols:info-outline\" color=\"warning\"}\n温馨提示\u003C\u002Fbr>\n1 从url导入私有仓库时，需要填写仓库的账户和个人令牌。此处的令牌并非密码(github现在不建议直接账户密码认证)，而是GitHub 的「**Personal access token**」。点击前往设置【[Personal access token](https:\u002F\u002Fgithub.com\u002Fsettings\u002Ftokens){target=_blank}】\u003C\u002Fbr>\n2 从github导入仓库，需要github授权才行，授权后可以选择需要导入的仓库。\n::\n② 仓库导入成功后，进入仓库，仓库名称后面会显示一个 :icon{name=\"material-symbols-light:refresh-rounded\"} 符号，点击便可手动同步github仓库代码至gitee。私有仓库同步时，需要输入账号和个人令牌(github的**Personal access token**)\n\n\n至此，github仓库同步gitee仓库前期准备工作已准备完毕，下面开始介绍如何配置github自动同步代码至gitee。\n\n\n### 1.2 创建SSH公私钥\nSSH密钥对由公钥和私钥组成，公钥用于加密数据，私钥用于解密数据，通过非对称加密确保通信安全。使用 SSH 协议可以连接远程服务器和服务并向它们验证。 利用 SSH 密钥可以连接到 GitHub，而无需在每次访问时都提供用户名和 personal access token。GitHub通过公钥加密信息，私钥用于解密，从而确认用户身份。\n\n\n①在本地机器上打开终端应用程序  \n②生成SSH密钥对  \n使用ssh-keygen命令来生成密钥对。您可以通过以下命令开始：\n```shell\nssh-keygen -t rsa  -b 4096 -C \"youremail@example.com\"\n```\n这里，-t rsa指定了密钥的类型为RSA，-b 4096指定了密钥的位长度为4096位。RSA密钥的长度可以是1024位、2048位或4096位，推荐使用2048位或更高的安全性。-C（Comment）是 ssh-keygen 命令的一个选项，用于为生成的 SSH 密钥对添加注释。注释内容可以是任意文本，但通常推荐使用邮箱地址或密钥用途描述。注释仅保存在公钥文件中，私钥文件不包含此信息。\n\n\n执行上述命令后，系统会询问您希望将密钥存储在哪个文件中，默认是~\u002F.ssh\u002Fid_rsa（私钥）和~\u002F.ssh\u002Fid_rsa.pub（公钥）。您可以按回车接受默认设置，或者输入其他路径来存储密钥文件。\n\n接下来，系统会询问您是否要为密钥设置密码（passphrase）。强烈建议设置密码，因为即使私钥文件被保护，知道密码仍然可以访问您的服务器。如果您不想设置密码，可以按回车跳过此步骤。\n\n\n如果连续三次回车，生成的秘钥对存储默认位置。`id_rsa`{color=primary} 为私钥，`id_rsa.pub`{color=primary}为公钥。如果提示：already exists（已经存在），则可以到电脑位置：C:\\Users\\电脑账号名\\ .ssh 直接使用。\n\n\n具体图文参考：【[生成\u002F添加SSH公钥](https:\u002F\u002Fgitee.com\u002Fhelp\u002Farticles\u002F4181#article-header0){target=_blank}】\n\n\n### 1.3 GitHub配置SSH私钥\n打开Github需要同步的项目，点击Settings->Secrets->Actions，设置私钥名称为：GITEE_RSA_PRIVATE_KEY，值为：上面生成SSH的私钥，即id_rsa 文件的内容\n![](https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910125740784.webp)\n\n\n### 1.4 GitHub配置SSH公钥\n在Github点击右上角用户图像，点击Settings->SSH and GPG keys->New SSH key，名称为：GITEE_RSA_PUBLIC_KEY，值为：上面生成SSH的公钥，即id_rsa_pub文件内容\n![](https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910130224827.webp)\n\n\n### 1.5 Gitee配置SSH公钥\n在Gitee 设置->安全设置->SSH公钥，标题为：GITEE_RSA_PUBLIC_KEY，值为：上面生成SSH的公钥，即id_rsa_pub文件内容\n![](https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910130741176.webp)\n\n\n### 1.6 创建Github workflow\ngithub创建workflow有2种方式，第一种直接在github项目中，点击actions创建新的workflow，第二种就是将项目clone本地，进行配置。二者大同小异，都是配置相应的设置。\n\n\n①第一种：点击actions创建新的workflow\n![](https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910131433758.webp)\n![](https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910131520866.webp)\n![](https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20250910131747275.webp)\n②第二种：将项目clone到本地，用vscode打开，根目录下创建文件`.github\\workflows\\sync-to-gitee.yml`{color=primary}，内容如下：\n```yml[sync-to-gitee.yml]\nname: Sync to Gitee\non:\n    push:\n        branches:\n            # 或者你想要同步的分支\n            - main\njobs:\n    sync:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Sync to Gitee\n              uses: wearerequired\u002Fgit-mirror-action@master\n              env:\n                  # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY\n                  SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}\n              with:\n                  # 注意替换为你的 GitHub 源仓库地址\n                  source-repo: git@github.com:AnyFork\u002Fanyfork-docs-client.git\n                  # 注意替换为你的 Gitee 目标仓库地址\n                  destination-repo: git@gitee.com:AnyFork\u002Fanyfork-docs-client.git\n```\n至此，github workflow配置完毕。以后提交代码至github仓库时，便会触发workflow自动执行，自动将github仓库代码同步至gitee仓库中。\n\n\n## 2 Github从Gitee同步更新\nGithub仓库从Gitee仓库同步代码，也是借助Github Actions功能进行实现，能够实现此功能的Actions较多，本文还是采用\n【[Yikun\u002Fgitee-mirror-action](https:\u002F\u002Fgithub.com\u002FYikun\u002Fhub-mirror-action){target=_blank}】进行实现。\n\n由于Github Actions在Gitee代码仓库更新时，无法自动感知gitee仓库发生更新，从而不能自动触发workflow的执行，因此形成了2种不同的同步方式：**webhooks实时同步方式**和**定时延迟同步方式**。下面分别介绍这2种同步方式的具体实现步骤。\n\n### 2.1 定时延迟同步方式\n定时延迟同步方式是最简单的一种方式，虽然存在延迟，但大部分情况下对代码同步的实时性要求并不高。可以根据实际需求，调整定时任务频率，增加同步次数。\n\n**1 创建Github目标仓库**\n\n 在实现从Gitee同步代码至目标仓库Github之前，需要在Github上创建一个空的仓库作为同步的目标仓库。登陆【[gihub](https:\u002F\u002Fgithub.com\u002F){target=_blank}】->右上角加号->创建仓库，私有或者公有仓库均可。\n\n\n**2 目标仓库配置SSH私钥**\n\n\ngithub目标仓库创建好后，需要配置仓库SSH私钥，具体步骤参考：【[1.3 github配置ssh私钥](#_13-github配置ssh私钥)】\n\n**3 目标仓库创建Github workflow**\n\n在【[1.6 创建Github workflow](#_16-创建github-workflow)】已经讲述了如何创建Github workflow，此处不在赘述。下面采用第二种方式进行配置。将项目clone到本地，用vscode打开，根目录下创建文件`.github\\workflows\\sync-to-github.yml`{color=primary}，内容如下：\n```yml[sync-to-github.yml]\nname: Sync to Github\non:\n   schedule:\n    # 每个小时10-59分钟之间，从10分钟开始，每5分钟执行一次\n    - cron:  '10\u002F5 * * * *'\njobs:\n    sync:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Sync to Github\n              uses: wearerequired\u002Fgit-mirror-action@master\n              env:\n                  # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY\n                  SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}\n              with:\n                  # 注意替换为你的 Gitee源仓库地址\n                  source-repo: git@gitee.com:AnyFork\u002Fanyfork-docs-client.git\n                  # 注意替换为你的 Github 目标仓库地址\n                  destination-repo: git@github.com:AnyFork\u002Fanyfork-docs-client.git\n```\n上面配置了`on:schedule:cron`{color=primary}定时任务，时间到了定时任务设置的时间，便会触发workflow执行。\n::callout{icon=\"material-symbols:info-outline\" color=\"warning\"}\n温馨提示\u003C\u002Fbr>\n1 设定的 schedule 是UCT时间，比如设置的08:00（也就是北京时间的16:00），但实际运行的时间为北京时间17:00 至 17：20 不等。\u003C\u002Fbr>\n2 Schedule在 GitHub 操作工作流运行的高负载期间，事件可能会延迟。高负载时间包括每小时开始。为了减少延迟的可能性，请安排您的工作流在不同的时间运行。实测延迟的时间为几十分钟，或者超过一个小时，甚至在某种极端情况下，将不会执行。\u003C\u002Fbr>\n3 Schedule 设置的 cron 时刻，仅仅是工作流进行计划排队的时刻，而不是准确的运行时刻。\n::\nGitHub Actions 默认使用 UTC 时间。如果你的工作流程应该在特定本地时间执行，你可以在 YAML 文件中指定时区：\n```yml\njobs:\n  job1:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Set Timezone\n      uses: szenius\u002Fset-timezone@v1.0\n      with:\n        timezoneLinux: \"Asia\u002FShanghai\"\n\n```\n对于一些不需要实时进行同步的需求，可以采用定时延迟同步方案，方便配置也简单。\n\n\n### 2.2 实时同步方式\nWebhook是一种事件驱动的通知机制，当发生特定事件（如代码推送、拉取请求创建等）时，会自动向指定的外部服务器发送HTTP POST请求（通常包含事件数据）。这种机制替代了轮询API的实时通知方式，允许开发者在事件发生时立即触发外部操作（如部署、测试或发送通知）。\n\n\nGithub和Gitee都支持Webhook功能，因此我们可以借助这一功能，在gitee代码发生提交时，触发gitee配置的自定义webhook，webhook通过调用github Api触发github actions自动执行，从而实现实时同步方式。\n\n\n\n\n## 3 Gitee自动同步Github\n## 4 Gitee从Github同步更新\n",{"title":6,"description":834},{"loc":844,"images":848},[849,850,851,852,853,854],{"loc":236},{"loc":246},{"loc":256},{"loc":269},{"loc":272},{"loc":275},"blog\u002F2.github仓库代码和gitee仓库代码双向自动同步方案",[303,313],[858,860],{"name":98,"icon":859,"symbol":98,"id":303},"simple-icons:gitee",{"name":861,"symbol":861,"icon":862,"id":313},"github","ri:github-fill","2026-03-12 10:02:23",2880,"HXvbCFijzlprXNJ_6Ke-eAlsWps6lq7wkNS4-bskXuk",{"id":867,"title":868,"articleId":869,"articleThumb":870,"body":871,"categoryId":372,"categoryInfo":6537,"createDate":6541,"description":879,"excerpt":6542,"extension":840,"isHidden":841,"isHot":842,"isRecommend":841,"isTop":841,"meta":6550,"navigation":842,"path":6551,"rawbody":6552,"readingTime":6553,"seo":6554,"sitemap":6555,"stem":6556,"tagIds":6557,"tagInfo":6558,"updateDate":6563,"wordCount":6564,"__hash__":6565},"blog\u002Fblog\u002F9.Web Crypto API浏览器原生加密的终极指南.md","Web Crypto API 浏览器原生加密的终极指南","web-crypto-api","https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20260825111119031.webp",{"type":10,"value":872,"toc":6513},[873,877,880,883,886,889,892,897,901,904,913,927,934,948,956,970,977,990,997,1011,1015,1018,1032,1036,1039,1043,1046,1079,1083,1086,1142,1146,1149,1191,1195,1198,1224,1228,1231,1263,1267,1352,1356,1363,1423,1434,1441,1471,1474,1481,1551,1562,1569,1625,1628,1635,1692,1703,1707,1756,1760,1763,1767,1770,1773,1781,2160,2168,2325,2333,2636,2644,2893,2899,2903,2906,2913,3115,3122,3267,3274,3411,3415,3418,3425,3590,3597,3753,3760,3936,3940,3943,3946,3953,4064,4071,4240,4244,4251,4278,4285,4319,4326,4337,4341,4348,4351,4439,4442,4514,4520,4526,4533,4536,4547,4551,4554,4557,6051,6054,6057,6510],[23,874,876],{"id":875},"_1-什么是-web-crypto-api","1. 什么是 Web Crypto API？",[13,878,879],{},"Web Crypto API 是浏览器原生提供的一套加密接口，允许 JavaScript 脚本使用密码学原语来构建基于加密的系统。它自 2015 年 7 月起已得到各大浏览器的广泛支持，是一个相当成熟且稳定的 Web 标准。",[13,881,882],{},"在 Web Crypto 规范诞生前，部分浏览器为了满足加密需求，在全局实现了一个名为 Crypto 的接口(即 window.crypto)。但正由于这样，当时各大浏览器的实现缺乏统一的 W3C 标准，定义模糊，且在密码学意义上是不够严谨和安全的（not cryptographically sound）。",[13,884,885],{},"当正式的 Web Crypto API 标准（支持 encrypt、decrypt、sign、verify 等现代异步加密操作）推出时，为了彻底避免和老旧、不安全的旧接口方法发生命名混淆与冲突，W3C 规范决定将这些低底层的密码学核心方法群全部打包进一个全新的独立接口中，这就是 SubtleCrypto。你可以通过全局对象的 crypto.subtle 属性来访问它。",[13,887,888],{},"由于旧的全局 crypto 对象并没有被废除（它依然保留了诸如密码学安全的伪随机数生成器 crypto.getRandomValues() 和 crypto.randomUUID() 等简单的同步方法），因此所有新版的 SubtleCrypto 复杂加密算法全部被挂载到了它的下级属性中。你必须通过 crypto.subtle 属性才能访问到这一整套现代加解密方法。",[13,890,891],{},"Node.js 最早在 v15.0.0 引入了 Web Crypto API 接口。直到 Node.js 19.0.0，官方正式去掉了实验性警告，将其标记为 稳定版本（Stable），并且默认在全局作用域挂载了 globalThis.crypto。",[103,893,894],{},[13,895,896],{},"重要提示：在现代浏览器中，为了保障密钥安全，crypto.subtle 属性被限制为只能在安全上下文（HTTPS 协议或 localhost）下才能被成功访问。如果你在普通的非加密 HTTP 网页里调用 crypto.subtle，它会直接返回 undefined。",[23,898,900],{"id":899},"_2-为什么选择-web-crypto-api","2. 为什么选择 Web Crypto API？",[13,902,903],{},"在决定是否在 Web 项目中使用 Web Crypto API 时，通常是因为它相比传统的第三方加密库（如 CryptoJS, jsencrypt, forge 等）在安全性、性能和原生支持度上带来了降维打击的优势。",[905,906,907],"ol",{},[61,908,909,912],{},[54,910,911],{},"防 XSS 攻击的“密钥黑盒”机制（核心安全优势）"," : 这是选择Web Crypto API最关键的安全理由。",[58,914,915,921],{},[61,916,917,920],{},[54,918,919],{},"传统加密库的痛点："," 如果你用 CryptoJS，密钥必须以明文字符串或 Uint8Array 的形式存在于 JavaScript 变量、localStorage 或 sessionStorage 中。一旦网站遭受 XSS（跨站脚本）攻击，黑客可以直接读取并盗走你的密钥。",[61,922,923,926],{},[54,924,925],{},"Web Crypto 的解决办法："," 它引入了 CryptoKey 对象。在生成或导入密钥时，如果设置 extractable: false，这个密钥在创建后将永远无法通过 JavaScript 读取、打印或导出。它被死死封锁在浏览器的底层安全内存中。黑客即使通过 XSS 控制了你的页面，也拿不到密钥本身，只能在本地调用它，大大降低了密钥泄露的风险。",[905,928,929],{"start":303},[61,930,931],{},[54,932,933],{},"硬件级的高性能与非阻塞（异步原生）",[58,935,936,942],{},[61,937,938,941],{},[54,939,940],{},"原生 C++ \u002F 汇编实现："," Web Crypto API 是由浏览器底层（基于 OpenSSL 或操作系统自带的密码学库）用原生代码实现的。相比纯 JavaScript 编写的加密库，它的运行速度快了数十倍到上百倍。",[61,943,944,947],{},[54,945,946],{},"异步非阻塞："," 传统的加密库（如 CryptoJS）大都是同步执行的。当计算大文件哈希，或者进行复杂的 RSA 大素数生成时，会直接卡死浏览器的 UI 主线程，导致页面瞬间卡顿或未响应。而 Web Crypto 的所有核心方法（如 encrypt、digest）全部是异步 Promise 机制，浏览器会将其交给底层的系统线程去算，完全不影响前端界面的流畅度。",[905,949,950],{"start":313},[61,951,952,955],{},[54,953,954],{},"密码学安全的伪随机数生成器 (CSPRNG)",": 在密码学中，随机数的质量决定了加密的生死。",[58,957,958,964],{},[61,959,960,963],{},[54,961,962],{},"传统的隐患："," JavaScript 原生的 Math.random() 是非密码学安全的，它的随机轨迹可以被算法预测，绝不能用于生成加密密钥、IV（初始化向量）或盐值。",[61,965,966,969],{},[54,967,968],{},"Web Crypto 的保障："," 提供了 crypto.getRandomValues()，它直接调用操作系统底层的硬件熵源（如系统的 \u002Fdev\u002Furandom 或 CAPI），生成密码学安全的高强度真随机数，保障加密系统的根基不可被破解。",[905,971,972],{"start":321},[61,973,974],{},[54,975,976],{},"零依赖，极致减小打包体积 (Zero-Bundle)",[58,978,979,984],{},[61,980,981,983],{},[54,982,919],{}," 引入 CryptoJS、JSEncrypt 或 Forge 会让前端的 Vendor 打包体积（Bundle Size）平白无故增加几十 KB 甚至几百 KB，影响首屏加载速度。",[61,985,986,989],{},[54,987,988],{},"Web Crypto 的优势："," 它是现代浏览器的标准内置 API，不需要 npm install 任何第三方包。直接开箱即用，让前端工程更加轻量、纯净。",[905,991,992],{"start":329},[61,993,994],{},[54,995,996],{},"行业标准规范，跨端兼容性完美",[58,998,999,1005],{},[61,1000,1001,1004],{},[54,1002,1003],{},"大厂和 W3C 背书："," 它遵循严格的 W3C 规范，由各大浏览器厂商共同维护和不断优化。",[61,1006,1007,1010],{},[54,1008,1009],{},"环境通用性："," 目前 Web Crypto API 的覆盖率已接近 100%。不仅在 Chrome、Safari、Edge、Firefox 浏览器中完美支持，在现代的 Node.js (v15+)、Deno、Bun 以及 Cloudflare Workers 等服务端\u002F边缘计算环境中也实现了完全一致的 API。这使得你写的一套加解密代码，可以无缝在前端和服务端之间复用。",[23,1012,1014],{"id":1013},"_3-什么时候不应该选择它","3. 什么时候不应该选择它？",[13,1016,1017],{},"使用 Web Crypto API 有唯一的限制条件：",[58,1019,1020,1026],{},[61,1021,1022,1025],{},[54,1023,1024],{},"非安全上下文限制："," 它强制要求 HTTPS 环境（本地开发支持 localhost 或 127.0.0.1）。如果你的网站部署在纯 HTTP 生产环境下，crypto.subtle 会直接返回 undefined 无法工作。",[61,1027,1028,1031],{},[54,1029,1030],{},"需要高层封装："," 它提供的是非常底层的“密码学原语”（Raw Primitives），不提供像“一键生成 JWT”或者“一键做密码信封”这样的高级功能，开发者需要自己处理字节对齐、IV 传递等微秒的细节。",[23,1033,1035],{"id":1034},"_4-web-crypto-api核心接口","4. Web Crypto API核心接口",[13,1037,1038],{},"Web Crypto API（通常称为 crypto.subtle）的核心接口主要由以下 5 个标准接口（Interfaces） 和 1 个全局入口 组成。它们构成了浏览器原生密码学生态的基石。",[84,1040,1042],{"id":1041},"_41-全局入口对象crypto","4.1 全局入口对象Crypto",[13,1044,1045],{},"虽然这不是单独的加解密接口，但它是所有 Web 加密功能的总入口，挂载在全局 window.crypto（或 globalThis.crypto）上。Crypto提供基础的加密功能，主要包括：",[58,1047,1048,1054],{},[61,1049,1050,1053],{},[54,1051,1052],{},"核心作用："," 提供基础的同步安全原语。",[61,1055,1056,1059],{},[54,1057,1058],{},"常用方法：",[58,1060,1061,1067,1073],{},[61,1062,1063,1066],{},[54,1064,1065],{},"crypto.getRandomValues()："," 生成密码学安全的伪随机数（常用于生成 IV 或盐）。",[61,1068,1069,1072],{},[54,1070,1071],{},"crypto.randomUUID()："," 快速生成一个符合 RFC 4122 标准的随机 UUID 字符串。",[61,1074,1075,1078],{},[54,1076,1077],{},"crypto.subtle："," 指向核心异步加密接口 SubtleCrypto 的指针。",[84,1080,1082],{"id":1081},"_42-核心操作员subtlecrypto","4.2  核心操作员SubtleCrypto",[13,1084,1085],{},"这是整个 Web Crypto API 中最重要、最核心的接口。所有的实际异步加解密、签名、密钥派生等操作都在这里执行。SubtleCrypto 提供的主要方法包括：",[58,1087,1088,1093],{},[61,1089,1090,1092],{},[54,1091,1052],{}," 提供一系列底层、底层的异步密码学原语方法（全部返回 Promise）。",[61,1094,1095,1098],{},[54,1096,1097],{},"核心方法（按功能分类）：",[58,1099,1100,1106,1112,1118,1124,1130,1136],{},[61,1101,1102,1105],{},[54,1103,1104],{},"加密与解密："," encrypt() \u002F decrypt()",[61,1107,1108,1111],{},[54,1109,1110],{},"签名与验证："," sign() \u002F verify()",[61,1113,1114,1117],{},[54,1115,1116],{},"哈希计算："," digest()",[61,1119,1120,1123],{},[54,1121,1122],{},"密钥生成："," generateKey()",[61,1125,1126,1129],{},[54,1127,1128],{},"密钥导入与导出："," importKey() \u002F exportKey()",[61,1131,1132,1135],{},[54,1133,1134],{},"密钥包装与解包："," wrapKey() \u002F unwrapKey()",[61,1137,1138,1141],{},[54,1139,1140],{},"密钥派生："," deriveKey() \u002F deriveBits()",[84,1143,1145],{"id":1144},"_43-密钥实体cryptokey","4.3 密钥实体CryptoKey",[13,1147,1148],{},"该接口代表一个在浏览器中生成的、受保护的密钥对象。为了安全，浏览器的 SubtleCrypto 方法不直接操作纯文本的密钥字符串（如 \"my-secret-key\"），而是操作 CryptoKey 实例。",[58,1150,1151,1156],{},[61,1152,1153,1155],{},[54,1154,1052],{}," 作为参数传递给 SubtleCrypto 的各项方法。",[61,1157,1158,1161],{},[54,1159,1160],{},"核心属性（只读）：",[58,1162,1163,1169,1175,1181],{},[61,1164,1165,1168],{},[54,1166,1167],{},"type："," 密钥类型（\"secret\" 对称加密、\"private\" 私钥、\"public\" 公钥）。",[61,1170,1171,1174],{},[54,1172,1173],{},"extractable："," 布尔值。如果为 false，该密钥在创建后绝对无法从内存中被导出或用 JS 打印出来，防止 XSS 攻击窃取密钥。",[61,1176,1177,1180],{},[54,1178,1179],{},"algorithm："," 绑定该密钥的算法对象（例如绑定的 AES-GCM 或 RSA-PSS 等参数）。",[61,1182,1183,1186,1187,1190],{},[54,1184,1185],{},"usages："," 该密钥被允许的用途数组（例如 ",[163,1188,1189],{},"'encrypt', 'decrypt'","）。",[84,1192,1194],{"id":1193},"_44-非对称密钥对cryptokeypair","4.4 非对称密钥对CryptoKeyPair",[13,1196,1197],{},"当使用 RSA、ECDSA 等非对称加密算法时，crypto.subtle.generateKey() 返回的不是单个密钥，而是一个包含公钥和私钥的 CryptoKeyPair 对象。",[58,1199,1200,1205],{},[61,1201,1202,1204],{},[54,1203,1052],{}," 容器对象，封装非对称密钥对。",[61,1206,1207,1210],{},[54,1208,1209],{},"核心属性：",[58,1211,1212,1218],{},[61,1213,1214,1217],{},[54,1215,1216],{},"publicKey："," 一个 CryptoKey 实例，代表公钥（用于加密或验证签名）。",[61,1219,1220,1223],{},[54,1221,1222],{},"privateKey："," 一个 CryptoKey 实例，代表私钥（用于解密或生成签名）。",[84,1225,1227],{"id":1226},"_45-算法配置对象algorithmidentifier","4.5 算法配置对象AlgorithmIdentifier",[13,1229,1230],{},"虽然它在技术上是一个规范定义的字典对象（Dictionary），而不是传统的类或接口，但它是每个 API 都必传的灵魂参数。",[58,1232,1233,1238],{},[61,1234,1235,1237],{},[54,1236,1052],{}," 用来指定具体的密码学算法名称以及其特有的初始化附加参数（如 IV、盐值、计数器等）。",[61,1239,1240,1243],{},[54,1241,1242],{},"常见表现形式：",[58,1244,1245,1251,1257],{},[61,1246,1247,1250],{},[54,1248,1249],{},"哈希配置："," { name: \"SHA-256\" }",[61,1252,1253,1256],{},[54,1254,1255],{},"AES-GCM 配置："," { name: \"AES-GCM\", iv: Uint8Array, tagLength: 128 }",[61,1258,1259,1262],{},[54,1260,1261],{},"PBKDF2（密码派生）配置："," { name: \"PBKDF2\", salt: Uint8Array, iterations: 100000, hash: \"SHA-256\" }",[84,1264,1266],{"id":1265},"_46-核心接口对比速查表","4.6 核心接口对比速查表",[1268,1269,1270,1289],"table",{},[1271,1272,1273],"thead",{},[1274,1275,1276,1280,1283,1286],"tr",{},[1277,1278,1279],"th",{},"接口名称",[1277,1281,1282],{},"角色定位",[1277,1284,1285],{},"非安全上下文（HTTP）使用规则",[1277,1287,1288],{},"典型返回值 \u002F 用途",[1290,1291,1292,1307,1325,1339],"tbody",{},[1274,1293,1294,1298,1301,1304],{},[1295,1296,1297],"td",{},"Crypto",[1295,1299,1300],{},"全局大管家",[1295,1302,1303],{},"是（部分方法受限）",[1295,1305,1306],{},"提供crypto.subtle入口、用于生成UUID",[1274,1308,1309,1312,1315,1318],{},[1295,1310,1311],{},"SubtleCrypto",[1295,1313,1314],{},"核心工具库",[1295,1316,1317],{},"否（必须在HTTPS\u002Flocalhost环境下使用）",[1295,1319,1320,1321,1324],{},"返回",[42,1322,1323],{},"Promise\u003CArrayBuffer>","，用于生成密文、哈希等加密核心操作",[1274,1326,1327,1330,1333,1336],{},[1295,1328,1329],{},"CryptoKey",[1295,1331,1332],{},"密钥黑盒",[1295,1334,1335],{},"否",[1295,1337,1338],{},"作为安全载体，直接传递给加解密函数使用",[1274,1340,1341,1344,1347,1349],{},[1295,1342,1343],{},"CryptoKeyPair",[1295,1345,1346],{},"密钥对容器",[1295,1348,1335],{},[1295,1350,1351],{},"封装返回包含publicKey和privateKey的密钥对对象",[23,1353,1355],{"id":1354},"_5-web-crypto-api支持的主流算法","5. Web Crypto API支持的主流算法",[905,1357,1358],{},[61,1359,1360],{},[54,1361,1362],{},"对称加密（Symmetric）表",[1268,1364,1365,1381],{},[1271,1366,1367],{},[1274,1368,1369,1372,1375,1378],{},[1277,1370,1371],{},"算法标识符",[1277,1373,1374],{},"支持的操作",[1277,1376,1377],{},"推荐搭配与技术参数",[1277,1379,1380],{},"典型应用场景",[1290,1382,1383,1397,1410],{},[1274,1384,1385,1388,1391,1394],{},[1295,1386,1387],{},"AES-GCM（强烈推荐）",[1295,1389,1390],{},"encrypt、decrypt、generateKey、importKey、exportKey、wrapKey、unwrapKey",[1295,1392,1393],{},"密钥支持128\u002F192\u002F256位，IV推荐96位（12字节）",[1295,1395,1396],{},"传输数据加解密、本地缓存加密，自带防篡改认证标签",[1274,1398,1399,1402,1404,1407],{},[1295,1400,1401],{},"AES-CBC",[1295,1403,1390],{},[1295,1405,1406],{},"密钥支持128\u002F192\u002F256位，IV固定128位（16字节）",[1295,1408,1409],{},"兼容老旧遗留系统的对称加解密业务",[1274,1411,1412,1415,1417,1420],{},[1295,1413,1414],{},"AES-CTR",[1295,1416,1390],{},[1295,1418,1419],{},"计数器模式，支持流式处理",[1295,1421,1422],{},"高频非对齐的流式数据区块加解密",[58,1424,1425,1428,1431],{},[61,1426,1427],{},"AES-GCM 是当前Web场景下的首选方案，硬件加速支持度高，同时自带消息认证能力，无需额外实现哈希校验逻辑，安全性和开发效率都更优。",[61,1429,1430],{},"不同算法的IV长度要求严格，比如AES-CBC必须使用固定16字节的IV，否则会直接触发解密失败。",[61,1432,1433],{},"三款算法都完整覆盖了密钥生成、导入导出、包裹\u002F解包裹密钥的全流程操作，完全适配Web Crypto的全场景开发需求。",[905,1435,1436],{"start":303},[61,1437,1438],{},[54,1439,1440],{},"非对称加密算法（Asymmetric）表",[1268,1442,1443,1455],{},[1271,1444,1445],{},[1274,1446,1447,1449,1451,1453],{},[1277,1448,1371],{},[1277,1450,1374],{},[1277,1452,1377],{},[1277,1454,1380],{},[1290,1456,1457],{},[1274,1458,1459,1462,1465,1468],{},[1295,1460,1461],{},"RSA-OAEP（强烈推荐）",[1295,1463,1464],{},"encrypt, decrypt, generateKey, importKey, exportKey, wrapKey, unwrapKey",[1295,1466,1467],{},"模数：2048, 4096 位哈希：推荐 SHA-256",[1295,1469,1470],{},"混合加密体系中用于加密“对称密钥”，或加密超短敏感字段",[13,1472,1473],{},"RSA-OAEP是Web场景下非对称加密的首选方案，它通过最优非对称加密填充机制，解决了传统“教科书RSA”的密文可延展性安全缺陷，在随机预言模型下可以抵御适应性选择密文攻击。由于非对称加密的特性，它不适合直接加密大体积数据，行业通用的最佳实践就是用它加密AES等对称密钥，再用对称算法加密实际业务数据，兼顾安全性和性能效率。",[905,1475,1476],{"start":313},[61,1477,1478],{},[54,1479,1480],{},"数字签名算法（Signature）表",[1268,1482,1483,1496],{},[1271,1484,1485],{},[1274,1486,1487,1490,1492,1494],{},[1277,1488,1489],{},"算法名称",[1277,1491,1374],{},[1277,1493,1377],{},[1277,1495,1380],{},[1290,1497,1498,1512,1525,1538],{},[1274,1499,1500,1503,1506,1509],{},[1295,1501,1502],{},"ECDSA（强烈推荐）",[1295,1504,1505],{},"sign、verify、generateKey、importKey、exportKey",[1295,1507,1508],{},"椭圆曲线：P-256、P-384、P-521",[1295,1510,1511],{},"现代高并发身份验签、JWT签名、区块链节点验签",[1274,1513,1514,1517,1519,1522],{},[1295,1515,1516],{},"RSASSA-PKCS1-v1_5",[1295,1518,1505],{},[1295,1520,1521],{},"模数≥2048位，哈希推荐SHA-256",[1295,1523,1524],{},"传统企业级API签名、老旧版本JWT签名验签",[1274,1526,1527,1530,1532,1535],{},[1295,1528,1529],{},"RSA-PSS",[1295,1531,1505],{},[1295,1533,1534],{},"RSA概率签名方案，密码学严谨度极高",[1295,1536,1537],{},"金融级或司法级等高安全要求的数字签名场景",[1274,1539,1540,1543,1545,1548],{},[1295,1541,1542],{},"HMAC",[1295,1544,1505],{},[1295,1546,1547],{},"基于哈希的密钥消息认证码",[1295,1549,1550],{},"前后端API请求防篡改哈希签名，如AWS签名机制",[58,1552,1553,1556,1559],{},[61,1554,1555],{},"ECDSA 是Web场景下的首选签名方案，密钥体积小、验签速度快，非常适配高并发的现代Web服务。",[61,1557,1558],{},"RSA系列算法兼容性极强，适合需要对接老旧系统的业务场景，其中RSA-PSS的抗攻击能力远高于传统的RSASSA-PKCS1-v1_5。",[61,1560,1561],{},"HMAC实现成本低，是轻量API防篡改签名的最优选择。",[905,1563,1564],{"start":321},[61,1565,1566],{},[54,1567,1568],{},"单向哈希算法（Digest）表",[1268,1570,1571,1583],{},[1271,1572,1573],{},[1274,1574,1575,1577,1579,1581],{},[1277,1576,1489],{},[1277,1578,1374],{},[1277,1580,1377],{},[1277,1582,1380],{},[1290,1584,1585,1599,1612],{},[1274,1586,1587,1590,1593,1596],{},[1295,1588,1589],{},"SHA-256",[1295,1591,1592],{},"digest",[1295,1594,1595],{},"无需密钥，单向不可逆",[1295,1597,1598],{},"文件完整性校验（秒传场景）、数据防篡改指纹生成",[1274,1600,1601,1604,1606,1609],{},[1295,1602,1603],{},"SHA-384",[1295,1605,1592],{},[1295,1607,1608],{},"无需密钥，单向不可逆，安全强度高于SHA-256",[1295,1610,1611],{},"高敏感数据校验、金融级业务的完整性验证",[1274,1613,1614,1617,1619,1622],{},[1295,1615,1616],{},"SHA-512",[1295,1618,1592],{},[1295,1620,1621],{},"无需密钥，单向不可逆，安全强度最高",[1295,1623,1624],{},"司法级、军工级等高安全要求场景的哈希校验",[13,1626,1627],{},"这三款同属SHA-2家族哈希算法，都具备单向不可逆的核心特性，无法从最终哈希值反推原始输入内容，仅用于生成数据唯一指纹，不能直接用于加解密还原原始数据。安全强度随输出位数提升而升高，同时计算性能也会小幅下降，普通业务场景优先选择SHA-256即可满足需求。",[905,1629,1630],{"start":329},[61,1631,1632],{},[54,1633,1634],{},"密钥派生算法（Key Derivation）表",[1268,1636,1637,1649],{},[1271,1638,1639],{},[1274,1640,1641,1643,1645,1647],{},[1277,1642,1489],{},[1277,1644,1374],{},[1277,1646,1377],{},[1277,1648,1380],{},[1290,1650,1651,1665,1679],{},[1274,1652,1653,1656,1659,1662],{},[1295,1654,1655],{},"ECDH",[1295,1657,1658],{},"deriveKey、deriveBits、generateKey、importKey、exportKey",[1295,1660,1661],{},"椭圆曲线：Diffie-Hellman",[1295,1663,1664],{},"两个终端在不安全信道中，安全地协商出一致的对称密钥",[1274,1666,1667,1670,1673,1676],{},[1295,1668,1669],{},"PBKDF2",[1295,1671,1672],{},"deriveKey、deriveBits、importKey",[1295,1674,1675],{},"基于密码的密钥派生，推荐迭代≥100000次",[1295,1677,1678],{},"将用户输入的简短文明密码，转换为符合AES标准的高强度密钥",[1274,1680,1681,1684,1686,1689],{},[1295,1682,1683],{},"HKDF",[1295,1685,1672],{},[1295,1687,1688],{},"基于HMAC的密钥扩展算法",[1295,1690,1691],{},"从一个基础主密钥，安全地扩展派生出多个子密钥",[58,1693,1694,1697,1700],{},[61,1695,1696],{},"ECDH 是现代网络通信中密钥协商的核心方案，双方全程不需要直接传输最终的共享密钥，仅通过交换椭圆曲线公钥就能各自计算出完全一致的会话密钥，安全性极高。",[61,1698,1699],{},"PBKDF2 专门针对低熵的用户密码设计，通过高迭代次数大幅提升暴力破解的成本，是密码类密钥衍生的行业标准方案。",[61,1701,1702],{},"HKDF 面向已经具备高熵的主密钥材料，能高效地将单个主密钥拆分扩展为多个用途独立的子密钥，广泛应用于TLS等现代加密协议中。",[23,1704,1706],{"id":1705},"_6-web-crypto-api-不支持的主流算法","6. Web Crypto API 不支持的主流算法",[1268,1708,1709,1721],{},[1271,1710,1711],{},[1274,1712,1713,1715,1718],{},[1277,1714,1489],{},[1277,1716,1717],{},"不支持原因",[1277,1719,1720],{},"替代实现方案",[1290,1722,1723,1734,1745],{},[1274,1724,1725,1728,1731],{},[1295,1726,1727],{},"MD5",[1295,1729,1730],{},"已被密码学界证实存在碰撞漏洞，安全性完全失效",[1295,1732,1733],{},"引入crypto-js等第三方库实现或使用 SHA-256",[1274,1735,1736,1739,1742],{},[1295,1737,1738],{},"SM2\u002FSM3\u002FSM4国密系列",[1295,1740,1741],{},"浏览器国际标准未原生内置国密算法",[1295,1743,1744],{},"引入sm-crypto等国密第三方库实现",[1274,1746,1747,1750,1753],{},[1295,1748,1749],{},"ChaCha20-Poly1305\u002FEd25519",[1295,1751,1752],{},"目前尚未完全普及到所有主流浏览器的 crypto.subtle 中",[1295,1754,1755],{},"引入第三方加密库补充实现",[23,1757,1759],{"id":1758},"_7-常用操作示例","7. 常用操作示例",[13,1761,1762],{},"以下是整理的 Web Crypto API (crypto.subtle) 最核心、最常用的四大标准代码示例（哈希生成、密钥生成、加密、解密）。需要特别提醒的是：所有 crypto.subtle 的操作都是异步的，都会返回一个 Promise，因此必须配合 async\u002Fawait 使用。",[84,1764,1766],{"id":1765},"_71-对称加密aes-gcm示例","7.1 对称加密AES-GCM示例",[13,1768,1769],{},"AES-GCM (Galois\u002FCounter Mode) 是一种非常流行且强大的认证加密（AEAD, Authenticated Encryption with Associated Data） 模式。你可以把它理解为一种“二合一”的加密模式：它在高效加密数据的同时，还能确保数据没有被篡改。",[13,1771,1772],{},"AES-GCM 是兼具高性能和高安全性的认证加密模式，特别适合在 Web Crypto API 这类原生环境中使用。它的安全性高度依赖于每次加密都使用唯一且不可预测的 IV。遵循这一核心原则，并妥善管理密钥、认真验证认证标签，就能为你的 Web 应用构建起一道坚实的数据安全防线。",[905,1774,1775],{},[61,1776,1777,1780],{},[54,1778,1779],{},"计算文本哈希摘要 (Digest)","：这是最简单的操作，常用于生成文件的 SHA-256。它不需要密钥。",[154,1782,1786],{"className":1783,"code":1784,"language":1785,"meta":159,"style":159},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F**\n * 计算字符串的 SHA-256 哈希值\n * @param {string} text - 输入的文本\n * @returns {Promise\u003Cstring>} Hex 格式的哈希字符串\n *\u002F\nasync function generateHash(text) {\n    \u002F\u002F 1. 将文本编码为 Uint8Array 字节流\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    \u002F\u002F 2. 使用 crypto.subtle.digest 生成哈希（返回 ArrayBuffer）\n    const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n\n    \u002F\u002F 3. 将 ArrayBuffer 转换为常见的十六进制 (Hex) 字符串\n    const hashArray = Array.from(new Uint8Array(hashBuffer));\n    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n    \n    return hashHex;\n}\n\n\u002F\u002F 使用示例：\nconst sha256Result = await generateHash(\"Hello World\");\n","js",[42,1787,1788,1793,1798,1828,1847,1852,1876,1881,1905,1930,1935,1940,1982,1986,1991,2026,2101,2106,2115,2120,2124,2130],{"__ignoreMap":159},[163,1789,1790],{"class":165,"line":166},[163,1791,1792],{"class":332},"\u002F**\n",[163,1794,1795],{"class":165,"line":303},[163,1796,1797],{"class":332}," * 计算字符串的 SHA-256 哈希值\n",[163,1799,1800,1803,1807,1811,1814,1818,1821,1825],{"class":165,"line":313},[163,1801,1802],{"class":332}," * ",[163,1804,1806],{"class":1805},"s7zQu","@",[163,1808,1810],{"class":1809},"s6hCs","param",[163,1812,1813],{"class":1805}," {",[163,1815,1817],{"class":1816},"sFweD","string",[163,1819,1820],{"class":1805},"}",[163,1822,1824],{"class":1823},"sHdIc"," text",[163,1826,1827],{"class":332}," - 输入的文本\n",[163,1829,1830,1832,1834,1837,1839,1842,1844],{"class":165,"line":321},[163,1831,1802],{"class":332},[163,1833,1806],{"class":1805},[163,1835,1836],{"class":1809},"returns",[163,1838,1813],{"class":1805},[163,1840,1841],{"class":1816},"Promise\u003Cstring>",[163,1843,1820],{"class":1805},[163,1845,1846],{"class":332}," Hex 格式的哈希字符串\n",[163,1848,1849],{"class":165,"line":329},[163,1850,1851],{"class":332}," *\u002F\n",[163,1853,1854,1858,1861,1865,1868,1871,1873],{"class":165,"line":336},[163,1855,1857],{"class":1856},"spNyl","async",[163,1859,1860],{"class":1856}," function",[163,1862,1864],{"class":1863},"s2Zo4"," generateHash",[163,1866,1867],{"class":190},"(",[163,1869,1870],{"class":1823},"text",[163,1872,133],{"class":190},[163,1874,1875],{"class":190}," {\n",[163,1877,1878],{"class":165,"line":345},[163,1879,1880],{"class":332},"    \u002F\u002F 1. 将文本编码为 Uint8Array 字节流\n",[163,1882,1883,1886,1890,1893,1896,1899,1902],{"class":165,"line":353},[163,1884,1885],{"class":1856},"    const",[163,1887,1889],{"class":1888},"sTEyZ"," encoder",[163,1891,1892],{"class":190}," =",[163,1894,1895],{"class":190}," new",[163,1897,1898],{"class":1863}," TextEncoder",[163,1900,1901],{"class":293},"()",[163,1903,1904],{"class":190},";\n",[163,1906,1907,1909,1912,1914,1916,1919,1922,1924,1926,1928],{"class":165,"line":361},[163,1908,1885],{"class":1856},[163,1910,1911],{"class":1888}," data",[163,1913,1892],{"class":190},[163,1915,1889],{"class":1888},[163,1917,1918],{"class":190},".",[163,1920,1921],{"class":1863},"encode",[163,1923,1867],{"class":293},[163,1925,1870],{"class":1888},[163,1927,133],{"class":293},[163,1929,1904],{"class":190},[163,1931,1932],{"class":165,"line":372},[163,1933,1934],{"emptyLinePlaceholder":842},"\n",[163,1936,1937],{"class":165,"line":380},[163,1938,1939],{"class":332},"    \u002F\u002F 2. 使用 crypto.subtle.digest 生成哈希（返回 ArrayBuffer）\n",[163,1941,1942,1944,1947,1949,1952,1955,1957,1960,1962,1964,1966,1969,1971,1973,1976,1978,1980],{"class":165,"line":392},[163,1943,1885],{"class":1856},[163,1945,1946],{"class":1888}," hashBuffer",[163,1948,1892],{"class":190},[163,1950,1951],{"class":1805}," await",[163,1953,1954],{"class":1888}," crypto",[163,1956,1918],{"class":190},[163,1958,1959],{"class":1888},"subtle",[163,1961,1918],{"class":190},[163,1963,1592],{"class":1863},[163,1965,1867],{"class":293},[163,1967,1968],{"class":190},"'",[163,1970,1589],{"class":173},[163,1972,1968],{"class":190},[163,1974,1975],{"class":190},",",[163,1977,1911],{"class":1888},[163,1979,133],{"class":293},[163,1981,1904],{"class":190},[163,1983,1984],{"class":165,"line":403},[163,1985,1934],{"emptyLinePlaceholder":842},[163,1987,1988],{"class":165,"line":411},[163,1989,1990],{"class":332},"    \u002F\u002F 3. 将 ArrayBuffer 转换为常见的十六进制 (Hex) 字符串\n",[163,1992,1993,1995,1998,2000,2003,2005,2008,2010,2013,2016,2018,2021,2024],{"class":165,"line":417},[163,1994,1885],{"class":1856},[163,1996,1997],{"class":1888}," hashArray",[163,1999,1892],{"class":190},[163,2001,2002],{"class":1888}," Array",[163,2004,1918],{"class":190},[163,2006,2007],{"class":1863},"from",[163,2009,1867],{"class":293},[163,2011,2012],{"class":190},"new",[163,2014,2015],{"class":1863}," Uint8Array",[163,2017,1867],{"class":293},[163,2019,2020],{"class":1888},"hashBuffer",[163,2022,2023],{"class":293},"))",[163,2025,1904],{"class":190},[163,2027,2028,2030,2033,2035,2037,2039,2042,2044,2047,2050,2053,2055,2058,2060,2063,2065,2067,2070,2072,2075,2077,2080,2083,2085,2087,2089,2092,2094,2097,2099],{"class":165,"line":428},[163,2029,1885],{"class":1856},[163,2031,2032],{"class":1888}," hashHex",[163,2034,1892],{"class":190},[163,2036,1997],{"class":1888},[163,2038,1918],{"class":190},[163,2040,2041],{"class":1863},"map",[163,2043,1867],{"class":293},[163,2045,2046],{"class":1823},"b",[163,2048,2049],{"class":1856}," =>",[163,2051,2052],{"class":1888}," b",[163,2054,1918],{"class":190},[163,2056,2057],{"class":1863},"toString",[163,2059,1867],{"class":293},[163,2061,2062],{"class":183},"16",[163,2064,133],{"class":293},[163,2066,1918],{"class":190},[163,2068,2069],{"class":1863},"padStart",[163,2071,1867],{"class":293},[163,2073,2074],{"class":183},"2",[163,2076,1975],{"class":190},[163,2078,2079],{"class":190}," '",[163,2081,2082],{"class":173},"0",[163,2084,1968],{"class":190},[163,2086,2023],{"class":293},[163,2088,1918],{"class":190},[163,2090,2091],{"class":1863},"join",[163,2093,1867],{"class":293},[163,2095,2096],{"class":190},"''",[163,2098,133],{"class":293},[163,2100,1904],{"class":190},[163,2102,2103],{"class":165,"line":436},[163,2104,2105],{"class":293},"    \n",[163,2107,2108,2111,2113],{"class":165,"line":442},[163,2109,2110],{"class":1805},"    return",[163,2112,2032],{"class":1888},[163,2114,1904],{"class":190},[163,2116,2117],{"class":165,"line":453},[163,2118,2119],{"class":190},"}\n",[163,2121,2122],{"class":165,"line":459},[163,2123,1934],{"emptyLinePlaceholder":842},[163,2125,2127],{"class":165,"line":2126},21,[163,2128,2129],{"class":332},"\u002F\u002F 使用示例：\n",[163,2131,2133,2136,2139,2142,2144,2146,2148,2151,2154,2156,2158],{"class":165,"line":2132},22,[163,2134,2135],{"class":1856},"const",[163,2137,2138],{"class":1888}," sha256Result ",[163,2140,2141],{"class":190},"=",[163,2143,1951],{"class":1805},[163,2145,1864],{"class":1863},[163,2147,1867],{"class":1888},[163,2149,2150],{"class":190},"\"",[163,2152,2153],{"class":173},"Hello World",[163,2155,2150],{"class":190},[163,2157,133],{"class":1888},[163,2159,1904],{"class":190},[905,2161,2162],{"start":303},[61,2163,2164,2167],{},[54,2165,2166],{},"生成对称加密密钥 (Generate Key)","：在加密数据之前，必须先通过 subtle 算法生成一个标准的加密密钥对象（CryptoKey）。这里以主流的 AES-GCM 算法为例：",[154,2169,2171],{"className":1783,"code":2170,"language":1785,"meta":159,"style":159},"\u002F**\n * 生成一个用于 AES-GCM 加密的 256 位随机密钥\n * @returns {Promise\u003CCryptoKey>} 返回 CryptoKey 对象\n *\u002F\nasync function generateAESKey() {\n    return await crypto.subtle.generateKey(\n        {\n        name: 'AES-GCM',\n        length: 256 \u002F\u002F 密钥长度支持 128, 192, 或 256 位\n        },\n        false, \u002F\u002F 是否允许该密钥被导出（如果设为 false，密钥无法脱离当前浏览器，安全性极高）\n        ['encrypt', 'decrypt'] \u002F\u002F 该密钥被允许用来做什么操作\n    );\n}\n\n",[42,2172,2173,2177,2182,2200,2204,2217,2237,2242,2259,2272,2277,2287,2314,2321],{"__ignoreMap":159},[163,2174,2175],{"class":165,"line":166},[163,2176,1792],{"class":332},[163,2178,2179],{"class":165,"line":303},[163,2180,2181],{"class":332}," * 生成一个用于 AES-GCM 加密的 256 位随机密钥\n",[163,2183,2184,2186,2188,2190,2192,2195,2197],{"class":165,"line":313},[163,2185,1802],{"class":332},[163,2187,1806],{"class":1805},[163,2189,1836],{"class":1809},[163,2191,1813],{"class":1805},[163,2193,2194],{"class":1816},"Promise\u003CCryptoKey>",[163,2196,1820],{"class":1805},[163,2198,2199],{"class":332}," 返回 CryptoKey 对象\n",[163,2201,2202],{"class":165,"line":321},[163,2203,1851],{"class":332},[163,2205,2206,2208,2210,2213,2215],{"class":165,"line":329},[163,2207,1857],{"class":1856},[163,2209,1860],{"class":1856},[163,2211,2212],{"class":1863}," generateAESKey",[163,2214,1901],{"class":190},[163,2216,1875],{"class":190},[163,2218,2219,2221,2223,2225,2227,2229,2231,2234],{"class":165,"line":336},[163,2220,2110],{"class":1805},[163,2222,1951],{"class":1805},[163,2224,1954],{"class":1888},[163,2226,1918],{"class":190},[163,2228,1959],{"class":1888},[163,2230,1918],{"class":190},[163,2232,2233],{"class":1863},"generateKey",[163,2235,2236],{"class":293},"(\n",[163,2238,2239],{"class":165,"line":345},[163,2240,2241],{"class":190},"        {\n",[163,2243,2244,2247,2249,2251,2254,2256],{"class":165,"line":353},[163,2245,2246],{"class":293},"        name",[163,2248,297],{"class":190},[163,2250,2079],{"class":190},[163,2252,2253],{"class":173},"AES-GCM",[163,2255,1968],{"class":190},[163,2257,2258],{"class":190},",\n",[163,2260,2261,2264,2266,2269],{"class":165,"line":361},[163,2262,2263],{"class":293},"        length",[163,2265,297],{"class":190},[163,2267,2268],{"class":183}," 256",[163,2270,2271],{"class":332}," \u002F\u002F 密钥长度支持 128, 192, 或 256 位\n",[163,2273,2274],{"class":165,"line":372},[163,2275,2276],{"class":190},"        },\n",[163,2278,2279,2282,2284],{"class":165,"line":380},[163,2280,2281],{"class":306},"        false",[163,2283,1975],{"class":190},[163,2285,2286],{"class":332}," \u002F\u002F 是否允许该密钥被导出（如果设为 false，密钥无法脱离当前浏览器，安全性极高）\n",[163,2288,2289,2292,2294,2297,2299,2301,2303,2306,2308,2311],{"class":165,"line":392},[163,2290,2291],{"class":293},"        [",[163,2293,1968],{"class":190},[163,2295,2296],{"class":173},"encrypt",[163,2298,1968],{"class":190},[163,2300,1975],{"class":190},[163,2302,2079],{"class":190},[163,2304,2305],{"class":173},"decrypt",[163,2307,1968],{"class":190},[163,2309,2310],{"class":293},"] ",[163,2312,2313],{"class":332},"\u002F\u002F 该密钥被允许用来做什么操作\n",[163,2315,2316,2319],{"class":165,"line":403},[163,2317,2318],{"class":293},"    )",[163,2320,1904],{"class":190},[163,2322,2323],{"class":165,"line":411},[163,2324,2119],{"class":190},[905,2326,2327],{"start":313},[61,2328,2329,2332],{},[54,2330,2331],{},"数据加密 Encrypt","：使用生成的密钥和 AES-GCM 算法对敏感文本进行加密。为了安全，AES-GCM 要求每次加密必须配合一个随机的初始化向量 (IV)。",[154,2334,2336],{"className":1783,"code":2335,"language":1785,"meta":159,"style":159},"\u002F**\n * 对文本进行 AES-GCM 加密\n * @param {string} text - 待加密的明文\n * @param {CryptoKey} key - 上一步生成的 CryptoKey 对象\n * @returns {Promise\u003C{cipherText: ArrayBuffer, iv: Uint8Array}>} 返回密文和IV\n *\u002F\nasync function encryptText(text, key) {\n    const encoder = new TextEncoder();\n    const encodedText = encoder.encode(text);\n\n    \u002F\u002F AES-GCM 推荐使用 12位（96位）的高安全伪随机数作为 IV\n    \u002F\u002F 注意：这里使用的是外部同步方法 getRandomValues 生成随机数\n    const iv = crypto.getRandomValues(new Uint8Array(12));\n\n    \u002F\u002F 执行加密\n    const cipherText = await crypto.subtle.encrypt(\n        {\n        name: 'AES-GCM',\n        iv: iv \u002F\u002F 加密参数必须包含 IV\n        },\n        key, \u002F\u002F 密钥\n        encodedText \u002F\u002F 明文数据\n    );\n\n    \u002F\u002F 需要同时把密文和 IV 返回，因为解密时必须提供一模一样的 IV\n    return { cipherText, iv };\n}\n\n",[42,2337,2338,2342,2347,2366,2386,2404,2408,2429,2445,2468,2472,2477,2482,2513,2517,2522,2545,2549,2563,2575,2579,2589,2597,2604,2609,2615,2631],{"__ignoreMap":159},[163,2339,2340],{"class":165,"line":166},[163,2341,1792],{"class":332},[163,2343,2344],{"class":165,"line":303},[163,2345,2346],{"class":332}," * 对文本进行 AES-GCM 加密\n",[163,2348,2349,2351,2353,2355,2357,2359,2361,2363],{"class":165,"line":313},[163,2350,1802],{"class":332},[163,2352,1806],{"class":1805},[163,2354,1810],{"class":1809},[163,2356,1813],{"class":1805},[163,2358,1817],{"class":1816},[163,2360,1820],{"class":1805},[163,2362,1824],{"class":1823},[163,2364,2365],{"class":332}," - 待加密的明文\n",[163,2367,2368,2370,2372,2374,2376,2378,2380,2383],{"class":165,"line":321},[163,2369,1802],{"class":332},[163,2371,1806],{"class":1805},[163,2373,1810],{"class":1809},[163,2375,1813],{"class":1805},[163,2377,1329],{"class":1816},[163,2379,1820],{"class":1805},[163,2381,2382],{"class":1823}," key",[163,2384,2385],{"class":332}," - 上一步生成的 CryptoKey 对象\n",[163,2387,2388,2390,2392,2394,2396,2399,2401],{"class":165,"line":329},[163,2389,1802],{"class":332},[163,2391,1806],{"class":1805},[163,2393,1836],{"class":1809},[163,2395,1813],{"class":1805},[163,2397,2398],{"class":1816},"Promise\u003C{cipherText: ArrayBuffer, iv: Uint8Array}>",[163,2400,1820],{"class":1805},[163,2402,2403],{"class":332}," 返回密文和IV\n",[163,2405,2406],{"class":165,"line":336},[163,2407,1851],{"class":332},[163,2409,2410,2412,2414,2417,2419,2421,2423,2425,2427],{"class":165,"line":345},[163,2411,1857],{"class":1856},[163,2413,1860],{"class":1856},[163,2415,2416],{"class":1863}," encryptText",[163,2418,1867],{"class":190},[163,2420,1870],{"class":1823},[163,2422,1975],{"class":190},[163,2424,2382],{"class":1823},[163,2426,133],{"class":190},[163,2428,1875],{"class":190},[163,2430,2431,2433,2435,2437,2439,2441,2443],{"class":165,"line":353},[163,2432,1885],{"class":1856},[163,2434,1889],{"class":1888},[163,2436,1892],{"class":190},[163,2438,1895],{"class":190},[163,2440,1898],{"class":1863},[163,2442,1901],{"class":293},[163,2444,1904],{"class":190},[163,2446,2447,2449,2452,2454,2456,2458,2460,2462,2464,2466],{"class":165,"line":361},[163,2448,1885],{"class":1856},[163,2450,2451],{"class":1888}," encodedText",[163,2453,1892],{"class":190},[163,2455,1889],{"class":1888},[163,2457,1918],{"class":190},[163,2459,1921],{"class":1863},[163,2461,1867],{"class":293},[163,2463,1870],{"class":1888},[163,2465,133],{"class":293},[163,2467,1904],{"class":190},[163,2469,2470],{"class":165,"line":372},[163,2471,1934],{"emptyLinePlaceholder":842},[163,2473,2474],{"class":165,"line":380},[163,2475,2476],{"class":332},"    \u002F\u002F AES-GCM 推荐使用 12位（96位）的高安全伪随机数作为 IV\n",[163,2478,2479],{"class":165,"line":392},[163,2480,2481],{"class":332},"    \u002F\u002F 注意：这里使用的是外部同步方法 getRandomValues 生成随机数\n",[163,2483,2484,2486,2489,2491,2493,2495,2498,2500,2502,2504,2506,2509,2511],{"class":165,"line":403},[163,2485,1885],{"class":1856},[163,2487,2488],{"class":1888}," iv",[163,2490,1892],{"class":190},[163,2492,1954],{"class":1888},[163,2494,1918],{"class":190},[163,2496,2497],{"class":1863},"getRandomValues",[163,2499,1867],{"class":293},[163,2501,2012],{"class":190},[163,2503,2015],{"class":1863},[163,2505,1867],{"class":293},[163,2507,2508],{"class":183},"12",[163,2510,2023],{"class":293},[163,2512,1904],{"class":190},[163,2514,2515],{"class":165,"line":411},[163,2516,1934],{"emptyLinePlaceholder":842},[163,2518,2519],{"class":165,"line":417},[163,2520,2521],{"class":332},"    \u002F\u002F 执行加密\n",[163,2523,2524,2526,2529,2531,2533,2535,2537,2539,2541,2543],{"class":165,"line":428},[163,2525,1885],{"class":1856},[163,2527,2528],{"class":1888}," cipherText",[163,2530,1892],{"class":190},[163,2532,1951],{"class":1805},[163,2534,1954],{"class":1888},[163,2536,1918],{"class":190},[163,2538,1959],{"class":1888},[163,2540,1918],{"class":190},[163,2542,2296],{"class":1863},[163,2544,2236],{"class":293},[163,2546,2547],{"class":165,"line":436},[163,2548,2241],{"class":190},[163,2550,2551,2553,2555,2557,2559,2561],{"class":165,"line":442},[163,2552,2246],{"class":293},[163,2554,297],{"class":190},[163,2556,2079],{"class":190},[163,2558,2253],{"class":173},[163,2560,1968],{"class":190},[163,2562,2258],{"class":190},[163,2564,2565,2568,2570,2572],{"class":165,"line":453},[163,2566,2567],{"class":293},"        iv",[163,2569,297],{"class":190},[163,2571,2488],{"class":1888},[163,2573,2574],{"class":332}," \u002F\u002F 加密参数必须包含 IV\n",[163,2576,2577],{"class":165,"line":459},[163,2578,2276],{"class":190},[163,2580,2581,2584,2586],{"class":165,"line":2126},[163,2582,2583],{"class":1888},"        key",[163,2585,1975],{"class":190},[163,2587,2588],{"class":332}," \u002F\u002F 密钥\n",[163,2590,2591,2594],{"class":165,"line":2132},[163,2592,2593],{"class":1888},"        encodedText",[163,2595,2596],{"class":332}," \u002F\u002F 明文数据\n",[163,2598,2600,2602],{"class":165,"line":2599},23,[163,2601,2318],{"class":293},[163,2603,1904],{"class":190},[163,2605,2607],{"class":165,"line":2606},24,[163,2608,1934],{"emptyLinePlaceholder":842},[163,2610,2612],{"class":165,"line":2611},25,[163,2613,2614],{"class":332},"    \u002F\u002F 需要同时把密文和 IV 返回，因为解密时必须提供一模一样的 IV\n",[163,2616,2618,2620,2622,2624,2626,2628],{"class":165,"line":2617},26,[163,2619,2110],{"class":1805},[163,2621,1813],{"class":190},[163,2623,2528],{"class":1888},[163,2625,1975],{"class":190},[163,2627,2488],{"class":1888},[163,2629,2630],{"class":190}," };\n",[163,2632,2634],{"class":165,"line":2633},27,[163,2635,2119],{"class":190},[905,2637,2638],{"start":321},[61,2639,2640,2643],{},[54,2641,2642],{},"数据解密 Decrypt","：传入加密时使用的 CryptoKey、IV 和密文数组，将其还原为人类可读的明文字符串。",[154,2645,2647],{"className":1783,"code":2646,"language":1785,"meta":159,"style":159},"\u002F**\n * 对 AES-GCM 密文进行解密\n * @param {ArrayBuffer} cipherText - 密文数据\n * @param {CryptoKey} key - 加密时使用的同一个密钥\n * @param {Uint8Array} iv - 加密时对应的随机 IV\n * @returns {Promise\u003Cstring>} 解密后的明文字符串\n *\u002F\nasync function decryptText(cipherText, key, iv) {\n    \u002F\u002F 执行解密\n    const decryptedBuffer = await crypto.subtle.decrypt(\n        {\n        name: 'AES-GCM',\n        iv: iv \u002F\u002F 必须传入与加密时完全相同的 IV\n        },\n        key,\n        cipherText\n    );\n\n    \u002F\u002F 将 ArrayBuffer 字节解码还原为字符串\n    const decoder = new TextDecoder();\n    return decoder.decode(decryptedBuffer);\n}\n",[42,2648,2649,2653,2658,2678,2697,2717,2734,2738,2764,2769,2792,2796,2810,2821,2825,2831,2836,2842,2846,2851,2869,2889],{"__ignoreMap":159},[163,2650,2651],{"class":165,"line":166},[163,2652,1792],{"class":332},[163,2654,2655],{"class":165,"line":303},[163,2656,2657],{"class":332}," * 对 AES-GCM 密文进行解密\n",[163,2659,2660,2662,2664,2666,2668,2671,2673,2675],{"class":165,"line":313},[163,2661,1802],{"class":332},[163,2663,1806],{"class":1805},[163,2665,1810],{"class":1809},[163,2667,1813],{"class":1805},[163,2669,2670],{"class":1816},"ArrayBuffer",[163,2672,1820],{"class":1805},[163,2674,2528],{"class":1823},[163,2676,2677],{"class":332}," - 密文数据\n",[163,2679,2680,2682,2684,2686,2688,2690,2692,2694],{"class":165,"line":321},[163,2681,1802],{"class":332},[163,2683,1806],{"class":1805},[163,2685,1810],{"class":1809},[163,2687,1813],{"class":1805},[163,2689,1329],{"class":1816},[163,2691,1820],{"class":1805},[163,2693,2382],{"class":1823},[163,2695,2696],{"class":332}," - 加密时使用的同一个密钥\n",[163,2698,2699,2701,2703,2705,2707,2710,2712,2714],{"class":165,"line":329},[163,2700,1802],{"class":332},[163,2702,1806],{"class":1805},[163,2704,1810],{"class":1809},[163,2706,1813],{"class":1805},[163,2708,2709],{"class":1816},"Uint8Array",[163,2711,1820],{"class":1805},[163,2713,2488],{"class":1823},[163,2715,2716],{"class":332}," - 加密时对应的随机 IV\n",[163,2718,2719,2721,2723,2725,2727,2729,2731],{"class":165,"line":336},[163,2720,1802],{"class":332},[163,2722,1806],{"class":1805},[163,2724,1836],{"class":1809},[163,2726,1813],{"class":1805},[163,2728,1841],{"class":1816},[163,2730,1820],{"class":1805},[163,2732,2733],{"class":332}," 解密后的明文字符串\n",[163,2735,2736],{"class":165,"line":345},[163,2737,1851],{"class":332},[163,2739,2740,2742,2744,2747,2749,2752,2754,2756,2758,2760,2762],{"class":165,"line":353},[163,2741,1857],{"class":1856},[163,2743,1860],{"class":1856},[163,2745,2746],{"class":1863}," decryptText",[163,2748,1867],{"class":190},[163,2750,2751],{"class":1823},"cipherText",[163,2753,1975],{"class":190},[163,2755,2382],{"class":1823},[163,2757,1975],{"class":190},[163,2759,2488],{"class":1823},[163,2761,133],{"class":190},[163,2763,1875],{"class":190},[163,2765,2766],{"class":165,"line":361},[163,2767,2768],{"class":332},"    \u002F\u002F 执行解密\n",[163,2770,2771,2773,2776,2778,2780,2782,2784,2786,2788,2790],{"class":165,"line":372},[163,2772,1885],{"class":1856},[163,2774,2775],{"class":1888}," decryptedBuffer",[163,2777,1892],{"class":190},[163,2779,1951],{"class":1805},[163,2781,1954],{"class":1888},[163,2783,1918],{"class":190},[163,2785,1959],{"class":1888},[163,2787,1918],{"class":190},[163,2789,2305],{"class":1863},[163,2791,2236],{"class":293},[163,2793,2794],{"class":165,"line":380},[163,2795,2241],{"class":190},[163,2797,2798,2800,2802,2804,2806,2808],{"class":165,"line":392},[163,2799,2246],{"class":293},[163,2801,297],{"class":190},[163,2803,2079],{"class":190},[163,2805,2253],{"class":173},[163,2807,1968],{"class":190},[163,2809,2258],{"class":190},[163,2811,2812,2814,2816,2818],{"class":165,"line":403},[163,2813,2567],{"class":293},[163,2815,297],{"class":190},[163,2817,2488],{"class":1888},[163,2819,2820],{"class":332}," \u002F\u002F 必须传入与加密时完全相同的 IV\n",[163,2822,2823],{"class":165,"line":411},[163,2824,2276],{"class":190},[163,2826,2827,2829],{"class":165,"line":417},[163,2828,2583],{"class":1888},[163,2830,2258],{"class":190},[163,2832,2833],{"class":165,"line":428},[163,2834,2835],{"class":1888},"        cipherText\n",[163,2837,2838,2840],{"class":165,"line":436},[163,2839,2318],{"class":293},[163,2841,1904],{"class":190},[163,2843,2844],{"class":165,"line":442},[163,2845,1934],{"emptyLinePlaceholder":842},[163,2847,2848],{"class":165,"line":453},[163,2849,2850],{"class":332},"    \u002F\u002F 将 ArrayBuffer 字节解码还原为字符串\n",[163,2852,2853,2855,2858,2860,2862,2865,2867],{"class":165,"line":459},[163,2854,1885],{"class":1856},[163,2856,2857],{"class":1888}," decoder",[163,2859,1892],{"class":190},[163,2861,1895],{"class":190},[163,2863,2864],{"class":1863}," TextDecoder",[163,2866,1901],{"class":293},[163,2868,1904],{"class":190},[163,2870,2871,2873,2875,2877,2880,2882,2885,2887],{"class":165,"line":2126},[163,2872,2110],{"class":1805},[163,2874,2857],{"class":1888},[163,2876,1918],{"class":190},[163,2878,2879],{"class":1863},"decode",[163,2881,1867],{"class":293},[163,2883,2884],{"class":1888},"decryptedBuffer",[163,2886,133],{"class":293},[163,2888,1904],{"class":190},[163,2890,2891],{"class":165,"line":2132},[163,2892,2119],{"class":190},[2894,2895,2896],"tip",{},[13,2897,2898],{},"如果要在项目中保存或传输生成的密钥和密文，通常需要通过 crypto.subtle.exportKey('raw', key) 将密钥导出成字节，再用 btoa() 或 Base64 方案 转换成可传输的字符串格式。",[84,2900,2902],{"id":2901},"_72-非对称加密rsa-oaep示例","7.2 非对称加密RSA-OAEP示例",[13,2904,2905],{},"非对称加密使用公钥加密、私钥解密，常用于非对称场景或加密对称密钥（如密钥交换）。这里使用最安全的 RSA-OAEP 算法。",[905,2907,2908],{},[61,2909,2910],{},[54,2911,2912],{},"生成 RSA 密钥对",[154,2914,2916],{"className":1783,"code":2915,"language":1785,"meta":159,"style":159},"\u002F**\n * 生成用于 RSA-OAEP 加解密的 2048 位密钥对\n * @returns {Promise\u003CCryptoKeyPair>} 返回包含公钥和私钥的对象\n *\u002F\nasync function generateRSAKeyPair() {\n    return await crypto.subtle.generateKey(\n        {\n            name: \"RSA-OAEP\",\n            modulusLength: 2048, \u002F\u002F 密钥长度，2048 位是目前的标准安全线\n            publicExponent: new Uint8Array([1, 0, 1]), \u002F\u002F 等于 65537，固定的密码学标准常数\n            hash: \"SHA-256\", \u002F\u002F 内部使用的哈希算法\n        },\n        true, \u002F\u002F 是否允许导出\n        [\"encrypt\", \"decrypt\"] \u002F\u002F 用途分配\n    );\n}\n",[42,2917,2918,2922,2927,2945,2949,2962,2980,2984,3000,3015,3050,3068,3072,3082,3105,3111],{"__ignoreMap":159},[163,2919,2920],{"class":165,"line":166},[163,2921,1792],{"class":332},[163,2923,2924],{"class":165,"line":303},[163,2925,2926],{"class":332}," * 生成用于 RSA-OAEP 加解密的 2048 位密钥对\n",[163,2928,2929,2931,2933,2935,2937,2940,2942],{"class":165,"line":313},[163,2930,1802],{"class":332},[163,2932,1806],{"class":1805},[163,2934,1836],{"class":1809},[163,2936,1813],{"class":1805},[163,2938,2939],{"class":1816},"Promise\u003CCryptoKeyPair>",[163,2941,1820],{"class":1805},[163,2943,2944],{"class":332}," 返回包含公钥和私钥的对象\n",[163,2946,2947],{"class":165,"line":321},[163,2948,1851],{"class":332},[163,2950,2951,2953,2955,2958,2960],{"class":165,"line":329},[163,2952,1857],{"class":1856},[163,2954,1860],{"class":1856},[163,2956,2957],{"class":1863}," generateRSAKeyPair",[163,2959,1901],{"class":190},[163,2961,1875],{"class":190},[163,2963,2964,2966,2968,2970,2972,2974,2976,2978],{"class":165,"line":336},[163,2965,2110],{"class":1805},[163,2967,1951],{"class":1805},[163,2969,1954],{"class":1888},[163,2971,1918],{"class":190},[163,2973,1959],{"class":1888},[163,2975,1918],{"class":190},[163,2977,2233],{"class":1863},[163,2979,2236],{"class":293},[163,2981,2982],{"class":165,"line":345},[163,2983,2241],{"class":190},[163,2985,2986,2989,2991,2993,2996,2998],{"class":165,"line":353},[163,2987,2988],{"class":293},"            name",[163,2990,297],{"class":190},[163,2992,191],{"class":190},[163,2994,2995],{"class":173},"RSA-OAEP",[163,2997,2150],{"class":190},[163,2999,2258],{"class":190},[163,3001,3002,3005,3007,3010,3012],{"class":165,"line":361},[163,3003,3004],{"class":293},"            modulusLength",[163,3006,297],{"class":190},[163,3008,3009],{"class":183}," 2048",[163,3011,1975],{"class":190},[163,3013,3014],{"class":332}," \u002F\u002F 密钥长度，2048 位是目前的标准安全线\n",[163,3016,3017,3020,3022,3024,3026,3029,3032,3034,3037,3039,3042,3045,3047],{"class":165,"line":372},[163,3018,3019],{"class":293},"            publicExponent",[163,3021,297],{"class":190},[163,3023,1895],{"class":190},[163,3025,2015],{"class":1863},[163,3027,3028],{"class":293},"([",[163,3030,3031],{"class":183},"1",[163,3033,1975],{"class":190},[163,3035,3036],{"class":183}," 0",[163,3038,1975],{"class":190},[163,3040,3041],{"class":183}," 1",[163,3043,3044],{"class":293},"])",[163,3046,1975],{"class":190},[163,3048,3049],{"class":332}," \u002F\u002F 等于 65537，固定的密码学标准常数\n",[163,3051,3052,3055,3057,3059,3061,3063,3065],{"class":165,"line":380},[163,3053,3054],{"class":293},"            hash",[163,3056,297],{"class":190},[163,3058,191],{"class":190},[163,3060,1589],{"class":173},[163,3062,2150],{"class":190},[163,3064,1975],{"class":190},[163,3066,3067],{"class":332}," \u002F\u002F 内部使用的哈希算法\n",[163,3069,3070],{"class":165,"line":392},[163,3071,2276],{"class":190},[163,3073,3074,3077,3079],{"class":165,"line":403},[163,3075,3076],{"class":306},"        true",[163,3078,1975],{"class":190},[163,3080,3081],{"class":332}," \u002F\u002F 是否允许导出\n",[163,3083,3084,3086,3088,3090,3092,3094,3096,3098,3100,3102],{"class":165,"line":411},[163,3085,2291],{"class":293},[163,3087,2150],{"class":190},[163,3089,2296],{"class":173},[163,3091,2150],{"class":190},[163,3093,1975],{"class":190},[163,3095,191],{"class":190},[163,3097,2305],{"class":173},[163,3099,2150],{"class":190},[163,3101,2310],{"class":293},[163,3103,3104],{"class":332},"\u002F\u002F 用途分配\n",[163,3106,3107,3109],{"class":165,"line":417},[163,3108,2318],{"class":293},[163,3110,1904],{"class":190},[163,3112,3113],{"class":165,"line":428},[163,3114,2119],{"class":190},[905,3116,3117],{"start":303},[61,3118,3119],{},[54,3120,3121],{},"使用公钥加密",[154,3123,3125],{"className":1783,"code":3124,"language":1785,"meta":159,"style":159},"\u002F**\n * 使用 RSA 公钥加密文本\n *\u002F\nasync function rsaEncrypt(text, publicKey) {\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    \u002F\u002F 返回 ArrayBuffer 格式的密文\n    return await crypto.subtle.encrypt(\n        { name: \"RSA-OAEP\" },\n        publicKey,\n        data\n    );\n}\n",[42,3126,3127,3131,3136,3140,3162,3178,3200,3204,3209,3227,3245,3252,3257,3263],{"__ignoreMap":159},[163,3128,3129],{"class":165,"line":166},[163,3130,1792],{"class":332},[163,3132,3133],{"class":165,"line":303},[163,3134,3135],{"class":332}," * 使用 RSA 公钥加密文本\n",[163,3137,3138],{"class":165,"line":313},[163,3139,1851],{"class":332},[163,3141,3142,3144,3146,3149,3151,3153,3155,3158,3160],{"class":165,"line":321},[163,3143,1857],{"class":1856},[163,3145,1860],{"class":1856},[163,3147,3148],{"class":1863}," rsaEncrypt",[163,3150,1867],{"class":190},[163,3152,1870],{"class":1823},[163,3154,1975],{"class":190},[163,3156,3157],{"class":1823}," publicKey",[163,3159,133],{"class":190},[163,3161,1875],{"class":190},[163,3163,3164,3166,3168,3170,3172,3174,3176],{"class":165,"line":329},[163,3165,1885],{"class":1856},[163,3167,1889],{"class":1888},[163,3169,1892],{"class":190},[163,3171,1895],{"class":190},[163,3173,1898],{"class":1863},[163,3175,1901],{"class":293},[163,3177,1904],{"class":190},[163,3179,3180,3182,3184,3186,3188,3190,3192,3194,3196,3198],{"class":165,"line":336},[163,3181,1885],{"class":1856},[163,3183,1911],{"class":1888},[163,3185,1892],{"class":190},[163,3187,1889],{"class":1888},[163,3189,1918],{"class":190},[163,3191,1921],{"class":1863},[163,3193,1867],{"class":293},[163,3195,1870],{"class":1888},[163,3197,133],{"class":293},[163,3199,1904],{"class":190},[163,3201,3202],{"class":165,"line":345},[163,3203,1934],{"emptyLinePlaceholder":842},[163,3205,3206],{"class":165,"line":353},[163,3207,3208],{"class":332},"    \u002F\u002F 返回 ArrayBuffer 格式的密文\n",[163,3210,3211,3213,3215,3217,3219,3221,3223,3225],{"class":165,"line":361},[163,3212,2110],{"class":1805},[163,3214,1951],{"class":1805},[163,3216,1954],{"class":1888},[163,3218,1918],{"class":190},[163,3220,1959],{"class":1888},[163,3222,1918],{"class":190},[163,3224,2296],{"class":1863},[163,3226,2236],{"class":293},[163,3228,3229,3232,3234,3236,3238,3240,3242],{"class":165,"line":372},[163,3230,3231],{"class":190},"        {",[163,3233,385],{"class":293},[163,3235,297],{"class":190},[163,3237,191],{"class":190},[163,3239,2995],{"class":173},[163,3241,2150],{"class":190},[163,3243,3244],{"class":190}," },\n",[163,3246,3247,3250],{"class":165,"line":380},[163,3248,3249],{"class":1888},"        publicKey",[163,3251,2258],{"class":190},[163,3253,3254],{"class":165,"line":392},[163,3255,3256],{"class":1888},"        data\n",[163,3258,3259,3261],{"class":165,"line":403},[163,3260,2318],{"class":293},[163,3262,1904],{"class":190},[163,3264,3265],{"class":165,"line":411},[163,3266,2119],{"class":190},[905,3268,3269],{"start":313},[61,3270,3271],{},[54,3272,3273],{},"使用私钥解密",[154,3275,3277],{"className":1783,"code":3276,"language":1785,"meta":159,"style":159},"\u002F**\n * 使用 RSA 私钥解密文本\n *\u002F\nasync function rsaDecrypt(cipherText, privateKey) {\n    const decryptedBuffer = await crypto.subtle.decrypt(\n        { name: \"RSA-OAEP\" },\n        privateKey,\n        cipherText\n    );\n\n    const decoder = new TextDecoder();\n    return decoder.decode(decryptedBuffer);\n}\n",[42,3278,3279,3283,3288,3292,3314,3336,3352,3359,3363,3369,3373,3389,3407],{"__ignoreMap":159},[163,3280,3281],{"class":165,"line":166},[163,3282,1792],{"class":332},[163,3284,3285],{"class":165,"line":303},[163,3286,3287],{"class":332}," * 使用 RSA 私钥解密文本\n",[163,3289,3290],{"class":165,"line":313},[163,3291,1851],{"class":332},[163,3293,3294,3296,3298,3301,3303,3305,3307,3310,3312],{"class":165,"line":321},[163,3295,1857],{"class":1856},[163,3297,1860],{"class":1856},[163,3299,3300],{"class":1863}," rsaDecrypt",[163,3302,1867],{"class":190},[163,3304,2751],{"class":1823},[163,3306,1975],{"class":190},[163,3308,3309],{"class":1823}," privateKey",[163,3311,133],{"class":190},[163,3313,1875],{"class":190},[163,3315,3316,3318,3320,3322,3324,3326,3328,3330,3332,3334],{"class":165,"line":329},[163,3317,1885],{"class":1856},[163,3319,2775],{"class":1888},[163,3321,1892],{"class":190},[163,3323,1951],{"class":1805},[163,3325,1954],{"class":1888},[163,3327,1918],{"class":190},[163,3329,1959],{"class":1888},[163,3331,1918],{"class":190},[163,3333,2305],{"class":1863},[163,3335,2236],{"class":293},[163,3337,3338,3340,3342,3344,3346,3348,3350],{"class":165,"line":336},[163,3339,3231],{"class":190},[163,3341,385],{"class":293},[163,3343,297],{"class":190},[163,3345,191],{"class":190},[163,3347,2995],{"class":173},[163,3349,2150],{"class":190},[163,3351,3244],{"class":190},[163,3353,3354,3357],{"class":165,"line":345},[163,3355,3356],{"class":1888},"        privateKey",[163,3358,2258],{"class":190},[163,3360,3361],{"class":165,"line":353},[163,3362,2835],{"class":1888},[163,3364,3365,3367],{"class":165,"line":361},[163,3366,2318],{"class":293},[163,3368,1904],{"class":190},[163,3370,3371],{"class":165,"line":372},[163,3372,1934],{"emptyLinePlaceholder":842},[163,3374,3375,3377,3379,3381,3383,3385,3387],{"class":165,"line":380},[163,3376,1885],{"class":1856},[163,3378,2857],{"class":1888},[163,3380,1892],{"class":190},[163,3382,1895],{"class":190},[163,3384,2864],{"class":1863},[163,3386,1901],{"class":293},[163,3388,1904],{"class":190},[163,3390,3391,3393,3395,3397,3399,3401,3403,3405],{"class":165,"line":392},[163,3392,2110],{"class":1805},[163,3394,2857],{"class":1888},[163,3396,1918],{"class":190},[163,3398,2879],{"class":1863},[163,3400,1867],{"class":293},[163,3402,2884],{"class":1888},[163,3404,133],{"class":293},[163,3406,1904],{"class":190},[163,3408,3409],{"class":165,"line":403},[163,3410,2119],{"class":190},[84,3412,3414],{"id":3413},"_73-数字签名与验证-rsassa-pkcs1-v1_5","7.3  数字签名与验证 (RSASSA-PKCS1-v1_5)",[13,3416,3417],{},"数字签名常用于防止数据被篡改并确认发送者身份。其逻辑是：私钥签名、公钥验证。这里使用最通用的 RSASSA-PKCS1-v1_5 算法。",[905,3419,3420],{},[61,3421,3422],{},[54,3423,3424],{},"生成数字签名的 RSA 密钥对",[154,3426,3428],{"className":1783,"code":3427,"language":1785,"meta":159,"style":159},"\u002F**\n * 生成用于数字签名的 RSA 密钥对\n *\u002F\nasync function generateSignKeyPair() {\n    return await crypto.subtle.generateKey(\n        {\n        name: \"RSASSA-PKCS1-v1_5\",\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: \"SHA-256\",\n        },\n        true,\n        [\"sign\", \"verify\"] \u002F\u002F 注意：这里的用途是签名和验证\n    );\n}\n",[42,3429,3430,3434,3439,3443,3456,3474,3478,3492,3503,3530,3545,3549,3555,3580,3586],{"__ignoreMap":159},[163,3431,3432],{"class":165,"line":166},[163,3433,1792],{"class":332},[163,3435,3436],{"class":165,"line":303},[163,3437,3438],{"class":332}," * 生成用于数字签名的 RSA 密钥对\n",[163,3440,3441],{"class":165,"line":313},[163,3442,1851],{"class":332},[163,3444,3445,3447,3449,3452,3454],{"class":165,"line":321},[163,3446,1857],{"class":1856},[163,3448,1860],{"class":1856},[163,3450,3451],{"class":1863}," generateSignKeyPair",[163,3453,1901],{"class":190},[163,3455,1875],{"class":190},[163,3457,3458,3460,3462,3464,3466,3468,3470,3472],{"class":165,"line":329},[163,3459,2110],{"class":1805},[163,3461,1951],{"class":1805},[163,3463,1954],{"class":1888},[163,3465,1918],{"class":190},[163,3467,1959],{"class":1888},[163,3469,1918],{"class":190},[163,3471,2233],{"class":1863},[163,3473,2236],{"class":293},[163,3475,3476],{"class":165,"line":336},[163,3477,2241],{"class":190},[163,3479,3480,3482,3484,3486,3488,3490],{"class":165,"line":345},[163,3481,2246],{"class":293},[163,3483,297],{"class":190},[163,3485,191],{"class":190},[163,3487,1516],{"class":173},[163,3489,2150],{"class":190},[163,3491,2258],{"class":190},[163,3493,3494,3497,3499,3501],{"class":165,"line":353},[163,3495,3496],{"class":293},"        modulusLength",[163,3498,297],{"class":190},[163,3500,3009],{"class":183},[163,3502,2258],{"class":190},[163,3504,3505,3508,3510,3512,3514,3516,3518,3520,3522,3524,3526,3528],{"class":165,"line":361},[163,3506,3507],{"class":293},"        publicExponent",[163,3509,297],{"class":190},[163,3511,1895],{"class":190},[163,3513,2015],{"class":1863},[163,3515,3028],{"class":293},[163,3517,3031],{"class":183},[163,3519,1975],{"class":190},[163,3521,3036],{"class":183},[163,3523,1975],{"class":190},[163,3525,3041],{"class":183},[163,3527,3044],{"class":293},[163,3529,2258],{"class":190},[163,3531,3532,3535,3537,3539,3541,3543],{"class":165,"line":372},[163,3533,3534],{"class":293},"        hash",[163,3536,297],{"class":190},[163,3538,191],{"class":190},[163,3540,1589],{"class":173},[163,3542,2150],{"class":190},[163,3544,2258],{"class":190},[163,3546,3547],{"class":165,"line":380},[163,3548,2276],{"class":190},[163,3550,3551,3553],{"class":165,"line":392},[163,3552,3076],{"class":306},[163,3554,2258],{"class":190},[163,3556,3557,3559,3561,3564,3566,3568,3570,3573,3575,3577],{"class":165,"line":403},[163,3558,2291],{"class":293},[163,3560,2150],{"class":190},[163,3562,3563],{"class":173},"sign",[163,3565,2150],{"class":190},[163,3567,1975],{"class":190},[163,3569,191],{"class":190},[163,3571,3572],{"class":173},"verify",[163,3574,2150],{"class":190},[163,3576,2310],{"class":293},[163,3578,3579],{"class":332},"\u002F\u002F 注意：这里的用途是签名和验证\n",[163,3581,3582,3584],{"class":165,"line":411},[163,3583,2318],{"class":293},[163,3585,1904],{"class":190},[163,3587,3588],{"class":165,"line":417},[163,3589,2119],{"class":190},[905,3591,3592],{"start":303},[61,3593,3594],{},[54,3595,3596],{},"使用私钥对数据生成签名",[154,3598,3600],{"className":1783,"code":3599,"language":1785,"meta":159,"style":159},"\n\u002F**\n * 使用私钥对数据生成签名\n * @returns {Promise\u003CArrayBuffer>} 返回签名摘要\n *\u002F\nasync function signData(text, privateKey) {\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    return await crypto.subtle.sign(\n        { name: \"RSASSA-PKCS1-v1_5\" },\n        privateKey,\n        data\n    );\n}\n",[42,3601,3602,3606,3610,3615,3632,3636,3657,3673,3695,3699,3717,3733,3739,3743,3749],{"__ignoreMap":159},[163,3603,3604],{"class":165,"line":166},[163,3605,1934],{"emptyLinePlaceholder":842},[163,3607,3608],{"class":165,"line":303},[163,3609,1792],{"class":332},[163,3611,3612],{"class":165,"line":313},[163,3613,3614],{"class":332}," * 使用私钥对数据生成签名\n",[163,3616,3617,3619,3621,3623,3625,3627,3629],{"class":165,"line":321},[163,3618,1802],{"class":332},[163,3620,1806],{"class":1805},[163,3622,1836],{"class":1809},[163,3624,1813],{"class":1805},[163,3626,1323],{"class":1816},[163,3628,1820],{"class":1805},[163,3630,3631],{"class":332}," 返回签名摘要\n",[163,3633,3634],{"class":165,"line":329},[163,3635,1851],{"class":332},[163,3637,3638,3640,3642,3645,3647,3649,3651,3653,3655],{"class":165,"line":336},[163,3639,1857],{"class":1856},[163,3641,1860],{"class":1856},[163,3643,3644],{"class":1863}," signData",[163,3646,1867],{"class":190},[163,3648,1870],{"class":1823},[163,3650,1975],{"class":190},[163,3652,3309],{"class":1823},[163,3654,133],{"class":190},[163,3656,1875],{"class":190},[163,3658,3659,3661,3663,3665,3667,3669,3671],{"class":165,"line":345},[163,3660,1885],{"class":1856},[163,3662,1889],{"class":1888},[163,3664,1892],{"class":190},[163,3666,1895],{"class":190},[163,3668,1898],{"class":1863},[163,3670,1901],{"class":293},[163,3672,1904],{"class":190},[163,3674,3675,3677,3679,3681,3683,3685,3687,3689,3691,3693],{"class":165,"line":353},[163,3676,1885],{"class":1856},[163,3678,1911],{"class":1888},[163,3680,1892],{"class":190},[163,3682,1889],{"class":1888},[163,3684,1918],{"class":190},[163,3686,1921],{"class":1863},[163,3688,1867],{"class":293},[163,3690,1870],{"class":1888},[163,3692,133],{"class":293},[163,3694,1904],{"class":190},[163,3696,3697],{"class":165,"line":361},[163,3698,1934],{"emptyLinePlaceholder":842},[163,3700,3701,3703,3705,3707,3709,3711,3713,3715],{"class":165,"line":372},[163,3702,2110],{"class":1805},[163,3704,1951],{"class":1805},[163,3706,1954],{"class":1888},[163,3708,1918],{"class":190},[163,3710,1959],{"class":1888},[163,3712,1918],{"class":190},[163,3714,3563],{"class":1863},[163,3716,2236],{"class":293},[163,3718,3719,3721,3723,3725,3727,3729,3731],{"class":165,"line":380},[163,3720,3231],{"class":190},[163,3722,385],{"class":293},[163,3724,297],{"class":190},[163,3726,191],{"class":190},[163,3728,1516],{"class":173},[163,3730,2150],{"class":190},[163,3732,3244],{"class":190},[163,3734,3735,3737],{"class":165,"line":392},[163,3736,3356],{"class":1888},[163,3738,2258],{"class":190},[163,3740,3741],{"class":165,"line":403},[163,3742,3256],{"class":1888},[163,3744,3745,3747],{"class":165,"line":411},[163,3746,2318],{"class":293},[163,3748,1904],{"class":190},[163,3750,3751],{"class":165,"line":417},[163,3752,2119],{"class":190},[905,3754,3755],{"start":313},[61,3756,3757],{},[54,3758,3759],{},"公钥验证签名是否有效",[154,3761,3763],{"className":1783,"code":3762,"language":1785,"meta":159,"style":159},"\n\u002F**\n * 使用公钥验证签名是否有效\n * @returns {Promise\u003Cboolean>} 验证成功返回 true，被篡改则返回 false\n *\u002F\nasync function verifyData(text, signature, publicKey) {\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    return await crypto.subtle.verify(\n        { name: \"RSASSA-PKCS1-v1_5\" },\n        publicKey,\n        signature, \u002F\u002F 之前生成的签名 ArrayBuffer\n        data \u002F\u002F 原始数据\n    );\n}\n",[42,3764,3765,3769,3773,3778,3796,3800,3826,3842,3864,3868,3886,3902,3908,3918,3926,3932],{"__ignoreMap":159},[163,3766,3767],{"class":165,"line":166},[163,3768,1934],{"emptyLinePlaceholder":842},[163,3770,3771],{"class":165,"line":303},[163,3772,1792],{"class":332},[163,3774,3775],{"class":165,"line":313},[163,3776,3777],{"class":332}," * 使用公钥验证签名是否有效\n",[163,3779,3780,3782,3784,3786,3788,3791,3793],{"class":165,"line":321},[163,3781,1802],{"class":332},[163,3783,1806],{"class":1805},[163,3785,1836],{"class":1809},[163,3787,1813],{"class":1805},[163,3789,3790],{"class":1816},"Promise\u003Cboolean>",[163,3792,1820],{"class":1805},[163,3794,3795],{"class":332}," 验证成功返回 true，被篡改则返回 false\n",[163,3797,3798],{"class":165,"line":329},[163,3799,1851],{"class":332},[163,3801,3802,3804,3806,3809,3811,3813,3815,3818,3820,3822,3824],{"class":165,"line":336},[163,3803,1857],{"class":1856},[163,3805,1860],{"class":1856},[163,3807,3808],{"class":1863}," verifyData",[163,3810,1867],{"class":190},[163,3812,1870],{"class":1823},[163,3814,1975],{"class":190},[163,3816,3817],{"class":1823}," signature",[163,3819,1975],{"class":190},[163,3821,3157],{"class":1823},[163,3823,133],{"class":190},[163,3825,1875],{"class":190},[163,3827,3828,3830,3832,3834,3836,3838,3840],{"class":165,"line":345},[163,3829,1885],{"class":1856},[163,3831,1889],{"class":1888},[163,3833,1892],{"class":190},[163,3835,1895],{"class":190},[163,3837,1898],{"class":1863},[163,3839,1901],{"class":293},[163,3841,1904],{"class":190},[163,3843,3844,3846,3848,3850,3852,3854,3856,3858,3860,3862],{"class":165,"line":353},[163,3845,1885],{"class":1856},[163,3847,1911],{"class":1888},[163,3849,1892],{"class":190},[163,3851,1889],{"class":1888},[163,3853,1918],{"class":190},[163,3855,1921],{"class":1863},[163,3857,1867],{"class":293},[163,3859,1870],{"class":1888},[163,3861,133],{"class":293},[163,3863,1904],{"class":190},[163,3865,3866],{"class":165,"line":361},[163,3867,1934],{"emptyLinePlaceholder":842},[163,3869,3870,3872,3874,3876,3878,3880,3882,3884],{"class":165,"line":372},[163,3871,2110],{"class":1805},[163,3873,1951],{"class":1805},[163,3875,1954],{"class":1888},[163,3877,1918],{"class":190},[163,3879,1959],{"class":1888},[163,3881,1918],{"class":190},[163,3883,3572],{"class":1863},[163,3885,2236],{"class":293},[163,3887,3888,3890,3892,3894,3896,3898,3900],{"class":165,"line":380},[163,3889,3231],{"class":190},[163,3891,385],{"class":293},[163,3893,297],{"class":190},[163,3895,191],{"class":190},[163,3897,1516],{"class":173},[163,3899,2150],{"class":190},[163,3901,3244],{"class":190},[163,3903,3904,3906],{"class":165,"line":392},[163,3905,3249],{"class":1888},[163,3907,2258],{"class":190},[163,3909,3910,3913,3915],{"class":165,"line":403},[163,3911,3912],{"class":1888},"        signature",[163,3914,1975],{"class":190},[163,3916,3917],{"class":332}," \u002F\u002F 之前生成的签名 ArrayBuffer\n",[163,3919,3920,3923],{"class":165,"line":411},[163,3921,3922],{"class":1888},"        data",[163,3924,3925],{"class":332}," \u002F\u002F 原始数据\n",[163,3927,3928,3930],{"class":165,"line":417},[163,3929,2318],{"class":293},[163,3931,1904],{"class":190},[163,3933,3934],{"class":165,"line":428},[163,3935,2119],{"class":190},[84,3937,3939],{"id":3938},"_74-密钥的导入与导出-import-export","7.4 密钥的导入与导出 (Import \u002F Export)",[13,3941,3942],{},"在实际业务中，密钥不能只存在于浏览器内存中，我们需要将其导出为字符串传给后端，或者将后端的字符串密钥导入为浏览器可识别的 CryptoKey 对象。",[13,3944,3945],{},"Web Crypto 支持多种格式，最常用的是 JWK (JSON Web Key) 格式（易读、跨平台）和 raw\u002Fspki\u002Fpkcs8（二进制字节流）。",[905,3947,3948],{},[61,3949,3950],{},[54,3951,3952],{},"导出密钥为 JSON (JWK 格式)",[154,3954,3956],{"className":1783,"code":3955,"language":1785,"meta":159,"style":159},"\u002F**\n * 将 CryptoKey 密钥对象导出为标准的 JSON 对象 (JWK)\n * @param {CryptoKey} key \n * @returns {Promise\u003CObject>} 可直接用于传输或 JSON.stringify 的对象\n *\u002F\nasync function exportKeyToJWK(key) {\n    return await crypto.subtle.exportKey(\"jwk\", key);\n}\n",[42,3957,3958,3962,3967,3986,4004,4008,4026,4060],{"__ignoreMap":159},[163,3959,3960],{"class":165,"line":166},[163,3961,1792],{"class":332},[163,3963,3964],{"class":165,"line":303},[163,3965,3966],{"class":332}," * 将 CryptoKey 密钥对象导出为标准的 JSON 对象 (JWK)\n",[163,3968,3969,3971,3973,3975,3977,3979,3981,3983],{"class":165,"line":313},[163,3970,1802],{"class":332},[163,3972,1806],{"class":1805},[163,3974,1810],{"class":1809},[163,3976,1813],{"class":1805},[163,3978,1329],{"class":1816},[163,3980,1820],{"class":1805},[163,3982,2382],{"class":1823},[163,3984,3985],{"class":332}," \n",[163,3987,3988,3990,3992,3994,3996,3999,4001],{"class":165,"line":321},[163,3989,1802],{"class":332},[163,3991,1806],{"class":1805},[163,3993,1836],{"class":1809},[163,3995,1813],{"class":1805},[163,3997,3998],{"class":1816},"Promise\u003CObject>",[163,4000,1820],{"class":1805},[163,4002,4003],{"class":332}," 可直接用于传输或 JSON.stringify 的对象\n",[163,4005,4006],{"class":165,"line":329},[163,4007,1851],{"class":332},[163,4009,4010,4012,4014,4017,4019,4022,4024],{"class":165,"line":336},[163,4011,1857],{"class":1856},[163,4013,1860],{"class":1856},[163,4015,4016],{"class":1863}," exportKeyToJWK",[163,4018,1867],{"class":190},[163,4020,4021],{"class":1823},"key",[163,4023,133],{"class":190},[163,4025,1875],{"class":190},[163,4027,4028,4030,4032,4034,4036,4038,4040,4043,4045,4047,4050,4052,4054,4056,4058],{"class":165,"line":345},[163,4029,2110],{"class":1805},[163,4031,1951],{"class":1805},[163,4033,1954],{"class":1888},[163,4035,1918],{"class":190},[163,4037,1959],{"class":1888},[163,4039,1918],{"class":190},[163,4041,4042],{"class":1863},"exportKey",[163,4044,1867],{"class":293},[163,4046,2150],{"class":190},[163,4048,4049],{"class":173},"jwk",[163,4051,2150],{"class":190},[163,4053,1975],{"class":190},[163,4055,2382],{"class":1888},[163,4057,133],{"class":293},[163,4059,1904],{"class":190},[163,4061,4062],{"class":165,"line":353},[163,4063,2119],{"class":190},[905,4065,4066],{"start":303},[61,4067,4068],{},[54,4069,4070],{},"将 JSON 导入回密钥对象",[154,4072,4074],{"className":1783,"code":4073,"language":1785,"meta":159,"style":159},"\u002F**\n * 将 JWK 格式的 JSON 对象导入还原为可用的 AES-GCM 密钥\n * @param {Object} jwkObject \n * @returns {Promise\u003CCryptoKey>}\n *\u002F\nasync function importKeyFromJWK(jwkObject) {\n    return await crypto.subtle.importKey(\n        \"jwk\",\n        jwkObject,\n        { name: \"AES-GCM\" }, \u002F\u002F 必须指定对应的算法\n        true, \u002F\u002F 是否允许再次导出\n        [\"encrypt\", \"decrypt\"] \u002F\u002F 该密钥允许的用途\n    );\n}\n",[42,4075,4076,4080,4085,4105,4119,4123,4141,4160,4171,4178,4198,4207,4230,4236],{"__ignoreMap":159},[163,4077,4078],{"class":165,"line":166},[163,4079,1792],{"class":332},[163,4081,4082],{"class":165,"line":303},[163,4083,4084],{"class":332}," * 将 JWK 格式的 JSON 对象导入还原为可用的 AES-GCM 密钥\n",[163,4086,4087,4089,4091,4093,4095,4098,4100,4103],{"class":165,"line":313},[163,4088,1802],{"class":332},[163,4090,1806],{"class":1805},[163,4092,1810],{"class":1809},[163,4094,1813],{"class":1805},[163,4096,4097],{"class":1816},"Object",[163,4099,1820],{"class":1805},[163,4101,4102],{"class":1823}," jwkObject",[163,4104,3985],{"class":332},[163,4106,4107,4109,4111,4113,4115,4117],{"class":165,"line":321},[163,4108,1802],{"class":332},[163,4110,1806],{"class":1805},[163,4112,1836],{"class":1809},[163,4114,1813],{"class":1805},[163,4116,2194],{"class":1816},[163,4118,2119],{"class":1805},[163,4120,4121],{"class":165,"line":329},[163,4122,1851],{"class":332},[163,4124,4125,4127,4129,4132,4134,4137,4139],{"class":165,"line":336},[163,4126,1857],{"class":1856},[163,4128,1860],{"class":1856},[163,4130,4131],{"class":1863}," importKeyFromJWK",[163,4133,1867],{"class":190},[163,4135,4136],{"class":1823},"jwkObject",[163,4138,133],{"class":190},[163,4140,1875],{"class":190},[163,4142,4143,4145,4147,4149,4151,4153,4155,4158],{"class":165,"line":345},[163,4144,2110],{"class":1805},[163,4146,1951],{"class":1805},[163,4148,1954],{"class":1888},[163,4150,1918],{"class":190},[163,4152,1959],{"class":1888},[163,4154,1918],{"class":190},[163,4156,4157],{"class":1863},"importKey",[163,4159,2236],{"class":293},[163,4161,4162,4165,4167,4169],{"class":165,"line":353},[163,4163,4164],{"class":190},"        \"",[163,4166,4049],{"class":173},[163,4168,2150],{"class":190},[163,4170,2258],{"class":190},[163,4172,4173,4176],{"class":165,"line":361},[163,4174,4175],{"class":1888},"        jwkObject",[163,4177,2258],{"class":190},[163,4179,4180,4182,4184,4186,4188,4190,4192,4195],{"class":165,"line":372},[163,4181,3231],{"class":190},[163,4183,385],{"class":293},[163,4185,297],{"class":190},[163,4187,191],{"class":190},[163,4189,2253],{"class":173},[163,4191,2150],{"class":190},[163,4193,4194],{"class":190}," },",[163,4196,4197],{"class":332}," \u002F\u002F 必须指定对应的算法\n",[163,4199,4200,4202,4204],{"class":165,"line":380},[163,4201,3076],{"class":306},[163,4203,1975],{"class":190},[163,4205,4206],{"class":332}," \u002F\u002F 是否允许再次导出\n",[163,4208,4209,4211,4213,4215,4217,4219,4221,4223,4225,4227],{"class":165,"line":392},[163,4210,2291],{"class":293},[163,4212,2150],{"class":190},[163,4214,2296],{"class":173},[163,4216,2150],{"class":190},[163,4218,1975],{"class":190},[163,4220,191],{"class":190},[163,4222,2305],{"class":173},[163,4224,2150],{"class":190},[163,4226,2310],{"class":293},[163,4228,4229],{"class":332},"\u002F\u002F 该密钥允许的用途\n",[163,4231,4232,4234],{"class":165,"line":403},[163,4233,2318],{"class":293},[163,4235,1904],{"class":190},[163,4237,4238],{"class":165,"line":411},[163,4239,2119],{"class":190},[23,4241,4243],{"id":4242},"_8-避坑指南","8 避坑指南",[905,4245,4246],{},[61,4247,4248],{},[54,4249,4250],{},"陷阱一：IV 重复使用",[58,4252,4253,4259],{},[61,4254,4255,4258],{},[54,4256,4257],{},"问题："," 在 AES-GCM 等对称加密中，重复使用相同的初始化向量（IV）会导致密钥流重复，严重破坏加密安全性。",[61,4260,4261,4264],{},[54,4262,4263],{},"正确做法：",[58,4265,4266,4269,4272,4275],{},[61,4267,4268],{},"每次加密都使用 crypto.getRandomValues() 生成新的随机 IV",[61,4270,4271],{},"AES-GCM 模式推荐使用 12 字节（非 16 字节！）的 IV",[61,4273,4274],{},"同一密钥下 IV 重复概率应低于 2⁻³²",[61,4276,4277],{},"IV 不需要保密，但必须唯一——记得将 IV 与密文一起存储",[905,4279,4280],{"start":303},[61,4281,4282],{},[54,4283,4284],{},"陷阱二：密钥管理不当",[58,4286,4287,4303],{},[61,4288,4289,4292],{},[54,4290,4291],{},"常见错误：",[58,4293,4294,4297,4300],{},[61,4295,4296],{},"将密钥存储在 localStorage 或 Cookie 中",[61,4298,4299],{},"使用不安全的密钥传输方式",[61,4301,4302],{},"未设置适当的密钥使用限制",[61,4304,4305,4308],{},[54,4306,4307],{},"安全建议：",[58,4309,4310,4313,4316],{},[61,4311,4312],{},"敏感会话密钥存储在 Web Worker 内存中",[61,4314,4315],{},"需要持久化的密钥使用 IndexedDB",[61,4317,4318],{},"使用 exportKey 和 importKey 安全地传输公钥",[905,4320,4321],{"start":313},[61,4322,4323],{},[54,4324,4325],{},"陷阱三：算法选择失误",[58,4327,4328,4331,4334],{},[61,4329,4330],{},"不要使用 SHA-1 或 MD5 进行哈希",[61,4332,4333],{},"不要使用 ECB 模式的 AES 加密",[61,4335,4336],{},"RSA 密钥长度至少 2048 位",[23,4338,4340],{"id":4339},"_9-最佳实践","9 最佳实践",[905,4342,4343],{},[61,4344,4345],{},[54,4346,4347],{},"使用方式选型指南推荐",[13,4349,4350],{},"自 Node.js 19+ 起，Web Crypto API 已成为内置标准，可通过以下方式访问：",[154,4352,4354],{"className":1783,"code":4353,"language":1785,"meta":159,"style":159},"\u002F\u002F 方式一：使用 globalThis\nconst { subtle } = globalThis.crypto;\n\n\u002F\u002F 方式二：使用 Node.js 模块\nconst { webcrypto } = require('node:crypto');\nconst { subtle } = webcrypto;\n",[42,4355,4356,4361,4384,4388,4393,4422],{"__ignoreMap":159},[163,4357,4358],{"class":165,"line":166},[163,4359,4360],{"class":332},"\u002F\u002F 方式一：使用 globalThis\n",[163,4362,4363,4365,4367,4370,4372,4374,4377,4379,4382],{"class":165,"line":303},[163,4364,2135],{"class":1856},[163,4366,1813],{"class":190},[163,4368,4369],{"class":1888}," subtle ",[163,4371,1820],{"class":190},[163,4373,1892],{"class":190},[163,4375,4376],{"class":1888}," globalThis",[163,4378,1918],{"class":190},[163,4380,4381],{"class":1888},"crypto",[163,4383,1904],{"class":190},[163,4385,4386],{"class":165,"line":313},[163,4387,1934],{"emptyLinePlaceholder":842},[163,4389,4390],{"class":165,"line":321},[163,4391,4392],{"class":332},"\u002F\u002F 方式二：使用 Node.js 模块\n",[163,4394,4395,4397,4399,4402,4404,4406,4409,4411,4413,4416,4418,4420],{"class":165,"line":329},[163,4396,2135],{"class":1856},[163,4398,1813],{"class":190},[163,4400,4401],{"class":1888}," webcrypto ",[163,4403,1820],{"class":190},[163,4405,1892],{"class":190},[163,4407,4408],{"class":1863}," require",[163,4410,1867],{"class":1888},[163,4412,1968],{"class":190},[163,4414,4415],{"class":173},"node:crypto",[163,4417,1968],{"class":190},[163,4419,133],{"class":1888},[163,4421,1904],{"class":190},[163,4423,4424,4426,4428,4430,4432,4434,4437],{"class":165,"line":336},[163,4425,2135],{"class":1856},[163,4427,1813],{"class":190},[163,4429,4369],{"class":1888},[163,4431,1820],{"class":190},[163,4433,1892],{"class":190},[163,4435,4436],{"class":1888}," webcrypto",[163,4438,1904],{"class":190},[13,4440,4441],{},"这两种方式虽然最终都能让你获取到 subtle 接口，但它们在设计哲学、模块机制、环境通用性以及底层依赖上有着本质的区别。",[1268,4443,4444,4457],{},[1271,4445,4446],{},[1274,4447,4448,4451,4454],{},[1277,4449,4450],{},"对比维度",[1277,4452,4453],{},"方式一：globalThis.crypto",[1277,4455,4456],{},"方式二：require('node:crypto')",[1290,4458,4459,4470,4481,4492,4503],{},[1274,4460,4461,4464,4467],{},[1295,4462,4463],{},"本质定位",[1295,4465,4466],{},"Web标准API（与浏览器对齐）",[1295,4468,4469],{},"Node.js原生模块API（特定服务端）",[1274,4471,4472,4475,4478],{},[1295,4473,4474],{},"引入方式",[1295,4476,4477],{},"零依赖，直接全局访问",[1295,4479,4480],{},"需要显式通过require引入模块",[1274,4482,4483,4486,4489],{},[1295,4484,4485],{},"跨端通用性",[1295,4487,4488],{},"极高（同构JavaScript）",[1295,4490,4491],{},"极低（只能在Node.js运行）",[1274,4493,4494,4497,4500],{},[1295,4495,4496],{},"底层实现",[1295,4498,4499],{},"浏览器内核（前端）\u002F Node V8底层封装（后端）",[1295,4501,4502],{},"强依赖Node.js内置的OpenSSL库",[1274,4504,4505,4508,4511],{},[1295,4506,4507],{},"执行上下文",[1295,4509,4510],{},"必须在安全上下文（前端限制）",[1295,4512,4513],{},"服务端环境，无任何安全上下文限制",[13,4515,4516,4519],{},[54,4517,4518],{},"推荐使用\"globalThis.crypto\"的场景："," 正在编写纯前端项目、前后端全栈通用工具库（Isomorphic Lib），或者代码需要部署在 Cloudflare Workers、Vercel Edge 等现代边缘计算架构中。",[13,4521,4522,4525],{},[54,4523,4524],{},"推荐使用\"require('node:crypto')\"的场景："," 正在编写纯 Node.js 服务端项目（如大型后端服务、CLI 工具），且除了 Web Crypto 之外，还需要用到 Node.js 特有的高级安全特性（如流式加密大文件、硬件安全模块 HSM 交互等）。",[905,4527,4528],{"start":303},[61,4529,4530],{},[54,4531,4532],{},"使用细节建议",[13,4534,4535],{},"Web Crypto API 是浏览器原生加密的黄金标准。它以零依赖、高性能、高安全性的优势，正在逐步取代各种第三方加密库。然而，正如其名 \"subtle\" 所警示的，加密是一个极其专业的领域，细微的错误就可能导致整个系统安全性完全失效。在使用 Web Crypto API 时：",[58,4537,4538,4541,4544],{},[61,4539,4540],{},"始终在 HTTPS 环境下使用",[61,4542,4543],{},"选择正确的算法和参数（如 AES-GCM + 12 字节 IV）",[61,4545,4546],{},"妥善管理密钥的生命周期",[23,4548,4550],{"id":4549},"_10-加解密模板工具","10 加解密模板工具",[13,4552,4553],{},"以下为您编写的这一套 AES-GCM 高级对称加密与解密工具函数。它做到了100% 零依赖、纯原生，通过 globalThis.crypto 实现完美同构。你不需要进行任何环境判断，同一段代码可以无缝运行在浏览器（安全上下文）、Node.js（19+）、Deno、Bun 以及边缘计算（如 Cloudflare Workers）中。",[13,4555,4556],{},"为了解决数据传输与存储的问题，工具函数内部自动处理了 ArrayBuffer 与 Base64 字符串之间的相互转换。可以直接复制使用，代码如下：",[154,4558,4563],{"className":4559,"code":4560,"filename":4561,"language":4562,"meta":159,"style":159},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F**\n * 完美的同构 Web Crypto 安全加解密工具库 (TypeScript 箭头函数版本)\n * 适用环境：Browser (HTTPS), Node.js 19+, Deno, Bun, Cloudflare Workers\n *\u002F\n\n\u002F\u002F 1. 获取全局同构的 subtle 对象\nconst cryptoProvider = globalThis.crypto\nif (!cryptoProvider) {\n    throw new Error('当前运行环境不支持 Web Crypto API。请确保在 HTTPS、localhost 或现代 Node.js 19+ 环境下运行。')\n}\nconst subtle: SubtleCrypto = cryptoProvider.subtle\n\n\u002F**\n * 辅助函数：将 ArrayBuffer 转换为可传输的 Base64 字符串\n * 使用现代批处理写法apply，100% 消除严格模式下的 undefined 类型报错\n *\u002F\nconst bufferToBase64 = (buffer: ArrayBuffer): string => {\n    const bytes = new Uint8Array(buffer)\n    const binary = String.fromCharCode.apply(null, bytes as unknown as number[])\n    return btoa(binary)\n}\n\n\u002F**\n * 辅助函数：将 Base64 字符串还原为 ArrayBuffer\n * 使用 Array.from 映射，确保严格类型安全\n *\u002F\nconst base64ToBuffer = (base64: string): ArrayBuffer => {\n    const binaryString = atob(base64)\n    const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0))\n    return bytes.buffer\n}\n\n\u002F**\n * [同构] 生成 AES-GCM 256位加密密钥\n *\u002F\nexport const generateAESKey = async (): Promise\u003CCryptoKey> => {\n    return await subtle.generateKey(\n        { name: 'AES-GCM', length: 256 },\n        true, \u002F\u002F 允许导出\n        ['encrypt', 'decrypt']\n    )\n}\n\n\u002F**\n * [同构] 导出 CryptoKey 密钥为可传输的 Base64 字符串\n * @param cryptoKey - 浏览器或服务端生成的 CryptoKey 实体\n *\u002F\nexport const exportKeyToBase64 = async (cryptoKey: CryptoKey): Promise\u003Cstring> => {\n    const exported = await subtle.exportKey('raw', cryptoKey)\n    return bufferToBase64(exported)\n}\n\n\u002F**\n * [同构] 从 Base64 字符串导入还原为 CryptoKey 密钥对象\n * @param base64Key - Base64 格式的密钥字符串\n *\u002F\nexport const importKeyFromBase64 = async (base64Key: string): Promise\u003CCryptoKey> => {\n    const rawKeyBuffer = base64ToBuffer(base64Key)\n    return await subtle.importKey('raw', rawKeyBuffer, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'])\n}\n\n\u002F**\n * [同构] 使用 AES-GCM 算法加密字符串文本\n * @param text - 待加密的明文文本\n * @param key - CryptoKey 密钥对象\n * @returns 返回打包了 \"IV\" 和 \"密文\" 的统一 Base64 字符串\n *\u002F\nexport const encryptText = async (text: string, key: CryptoKey): Promise\u003Cstring> => {\n    const encoder = new TextEncoder()\n    const encodedText: Uint8Array\u003CArrayBuffer> = encoder.encode(text)\n    \u002F\u002F 生成 12 字节的密码学安全伪随机数作为 IV (AES-GCM 标准推荐)\n    const iv: Uint8Array\u003CArrayBuffer> = cryptoProvider.getRandomValues(new Uint8Array(12))\n    \u002F\u002F 执行加密，返回密文 ArrayBuffer\n    const cipherBuffer: ArrayBuffer = await subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, encodedText)\n    \u002F\u002F 将 IV（12字节）和密文拼接，方便单字符串传输\n    const combined = new Uint8Array(iv.length + cipherBuffer.byteLength)\n    combined.set(iv, 0)\n    combined.set(new Uint8Array(cipherBuffer), iv.length)\n    return bufferToBase64(combined.buffer)\n}\n\n\u002F**\n * [同构] 使用 AES-GCM 算法解密文本\n * @param combinedBase64 - 由 encryptText 生成的统一加密 Base64 字符串（内部含 IV）\n * @param key - 对应的 CryptoKey 密钥对象\n *\u002F\nexport const decryptText = async (combinedBase64: string, key: CryptoKey): Promise\u003Cstring> => {\n    const combinedBuffer = base64ToBuffer(combinedBase64)\n    const combinedBytes = new Uint8Array(combinedBuffer)\n    \u002F\u002F 从头部切出前 12 字节作为 IV\n    const iv: Uint8Array\u003CArrayBuffer> = combinedBytes.slice(0, 12)\n    \u002F\u002F 剩下的部分作为真正的密文\n    const cipherText: Uint8Array\u003CArrayBuffer> = combinedBytes.slice(12)\n    \u002F\u002F 执行解密\n    const decryptedBuffer: ArrayBuffer = await subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, cipherText.buffer)\n    const decoder = new TextDecoder()\n    return decoder.decode(decryptedBuffer)\n}\n","crypto.ts","ts",[42,4564,4565,4569,4574,4579,4583,4587,4592,4608,4625,4647,4651,4673,4677,4681,4686,4691,4695,4724,4743,4786,4800,4804,4808,4812,4817,4822,4826,4852,4871,4917,4929,4934,4939,4944,4950,4955,4990,5005,5031,5041,5063,5069,5074,5079,5084,5090,5105,5110,5148,5181,5196,5201,5206,5211,5217,5232,5237,5274,5293,5359,5364,5369,5374,5380,5394,5408,5420,5425,5469,5485,5516,5522,5559,5565,5620,5626,5662,5683,5715,5733,5738,5743,5748,5754,5769,5783,5788,5833,5851,5872,5878,5915,5921,5952,5957,6014,6029,6046],{"__ignoreMap":159},[163,4566,4567],{"class":165,"line":166},[163,4568,1792],{"class":332},[163,4570,4571],{"class":165,"line":303},[163,4572,4573],{"class":332}," * 完美的同构 Web Crypto 安全加解密工具库 (TypeScript 箭头函数版本)\n",[163,4575,4576],{"class":165,"line":313},[163,4577,4578],{"class":332}," * 适用环境：Browser (HTTPS), Node.js 19+, Deno, Bun, Cloudflare Workers\n",[163,4580,4581],{"class":165,"line":321},[163,4582,1851],{"class":332},[163,4584,4585],{"class":165,"line":329},[163,4586,1934],{"emptyLinePlaceholder":842},[163,4588,4589],{"class":165,"line":336},[163,4590,4591],{"class":332},"\u002F\u002F 1. 获取全局同构的 subtle 对象\n",[163,4593,4594,4596,4599,4601,4603,4605],{"class":165,"line":345},[163,4595,2135],{"class":1856},[163,4597,4598],{"class":1888}," cryptoProvider ",[163,4600,2141],{"class":190},[163,4602,4376],{"class":1888},[163,4604,1918],{"class":190},[163,4606,4607],{"class":1888},"crypto\n",[163,4609,4610,4613,4616,4619,4622],{"class":165,"line":353},[163,4611,4612],{"class":1805},"if",[163,4614,4615],{"class":1888}," (",[163,4617,4618],{"class":190},"!",[163,4620,4621],{"class":1888},"cryptoProvider) ",[163,4623,4624],{"class":190},"{\n",[163,4626,4627,4630,4632,4635,4637,4639,4642,4644],{"class":165,"line":361},[163,4628,4629],{"class":1805},"    throw",[163,4631,1895],{"class":190},[163,4633,4634],{"class":1863}," Error",[163,4636,1867],{"class":293},[163,4638,1968],{"class":190},[163,4640,4641],{"class":173},"当前运行环境不支持 Web Crypto API。请确保在 HTTPS、localhost 或现代 Node.js 19+ 环境下运行。",[163,4643,1968],{"class":190},[163,4645,4646],{"class":293},")\n",[163,4648,4649],{"class":165,"line":372},[163,4650,2119],{"class":190},[163,4652,4653,4655,4658,4660,4663,4665,4668,4670],{"class":165,"line":380},[163,4654,2135],{"class":1856},[163,4656,4657],{"class":1888}," subtle",[163,4659,297],{"class":190},[163,4661,4662],{"class":169}," SubtleCrypto",[163,4664,1892],{"class":190},[163,4666,4667],{"class":1888}," cryptoProvider",[163,4669,1918],{"class":190},[163,4671,4672],{"class":1888},"subtle\n",[163,4674,4675],{"class":165,"line":392},[163,4676,1934],{"emptyLinePlaceholder":842},[163,4678,4679],{"class":165,"line":403},[163,4680,1792],{"class":332},[163,4682,4683],{"class":165,"line":411},[163,4684,4685],{"class":332}," * 辅助函数：将 ArrayBuffer 转换为可传输的 Base64 字符串\n",[163,4687,4688],{"class":165,"line":417},[163,4689,4690],{"class":332}," * 使用现代批处理写法apply，100% 消除严格模式下的 undefined 类型报错\n",[163,4692,4693],{"class":165,"line":428},[163,4694,1851],{"class":332},[163,4696,4697,4699,4702,4704,4706,4709,4711,4714,4717,4720,4722],{"class":165,"line":436},[163,4698,2135],{"class":1856},[163,4700,4701],{"class":1888}," bufferToBase64 ",[163,4703,2141],{"class":190},[163,4705,4615],{"class":190},[163,4707,4708],{"class":1823},"buffer",[163,4710,297],{"class":190},[163,4712,4713],{"class":169}," ArrayBuffer",[163,4715,4716],{"class":190},"):",[163,4718,4719],{"class":169}," string",[163,4721,2049],{"class":1856},[163,4723,1875],{"class":190},[163,4725,4726,4728,4731,4733,4735,4737,4739,4741],{"class":165,"line":442},[163,4727,1885],{"class":1856},[163,4729,4730],{"class":1888}," bytes",[163,4732,1892],{"class":190},[163,4734,1895],{"class":190},[163,4736,2015],{"class":1863},[163,4738,1867],{"class":293},[163,4740,4708],{"class":1888},[163,4742,4646],{"class":293},[163,4744,4745,4747,4750,4752,4755,4757,4760,4762,4765,4767,4770,4772,4775,4778,4780,4783],{"class":165,"line":453},[163,4746,1885],{"class":1856},[163,4748,4749],{"class":1888}," binary",[163,4751,1892],{"class":190},[163,4753,4754],{"class":1888}," String",[163,4756,1918],{"class":190},[163,4758,4759],{"class":1888},"fromCharCode",[163,4761,1918],{"class":190},[163,4763,4764],{"class":1863},"apply",[163,4766,1867],{"class":293},[163,4768,4769],{"class":190},"null,",[163,4771,4730],{"class":1888},[163,4773,4774],{"class":1805}," as",[163,4776,4777],{"class":169}," unknown",[163,4779,4774],{"class":1805},[163,4781,4782],{"class":169}," number",[163,4784,4785],{"class":293},"[])\n",[163,4787,4788,4790,4793,4795,4798],{"class":165,"line":459},[163,4789,2110],{"class":1805},[163,4791,4792],{"class":1863}," btoa",[163,4794,1867],{"class":293},[163,4796,4797],{"class":1888},"binary",[163,4799,4646],{"class":293},[163,4801,4802],{"class":165,"line":2126},[163,4803,2119],{"class":190},[163,4805,4806],{"class":165,"line":2132},[163,4807,1934],{"emptyLinePlaceholder":842},[163,4809,4810],{"class":165,"line":2599},[163,4811,1792],{"class":332},[163,4813,4814],{"class":165,"line":2606},[163,4815,4816],{"class":332}," * 辅助函数：将 Base64 字符串还原为 ArrayBuffer\n",[163,4818,4819],{"class":165,"line":2611},[163,4820,4821],{"class":332}," * 使用 Array.from 映射，确保严格类型安全\n",[163,4823,4824],{"class":165,"line":2617},[163,4825,1851],{"class":332},[163,4827,4828,4830,4833,4835,4837,4840,4842,4844,4846,4848,4850],{"class":165,"line":2633},[163,4829,2135],{"class":1856},[163,4831,4832],{"class":1888}," base64ToBuffer ",[163,4834,2141],{"class":190},[163,4836,4615],{"class":190},[163,4838,4839],{"class":1823},"base64",[163,4841,297],{"class":190},[163,4843,4719],{"class":169},[163,4845,4716],{"class":190},[163,4847,4713],{"class":169},[163,4849,2049],{"class":1856},[163,4851,1875],{"class":190},[163,4853,4855,4857,4860,4862,4865,4867,4869],{"class":165,"line":4854},28,[163,4856,1885],{"class":1856},[163,4858,4859],{"class":1888}," binaryString",[163,4861,1892],{"class":190},[163,4863,4864],{"class":1863}," atob",[163,4866,1867],{"class":293},[163,4868,4839],{"class":1888},[163,4870,4646],{"class":293},[163,4872,4874,4876,4878,4880,4882,4884,4886,4888,4891,4893,4895,4898,4900,4902,4905,4907,4910,4912,4914],{"class":165,"line":4873},29,[163,4875,1885],{"class":1856},[163,4877,4730],{"class":1888},[163,4879,1892],{"class":190},[163,4881,2015],{"class":1888},[163,4883,1918],{"class":190},[163,4885,2007],{"class":1863},[163,4887,1867],{"class":293},[163,4889,4890],{"class":1888},"binaryString",[163,4892,1975],{"class":190},[163,4894,4615],{"class":190},[163,4896,4897],{"class":1823},"char",[163,4899,133],{"class":190},[163,4901,2049],{"class":1856},[163,4903,4904],{"class":1888}," char",[163,4906,1918],{"class":190},[163,4908,4909],{"class":1863},"charCodeAt",[163,4911,1867],{"class":293},[163,4913,2082],{"class":183},[163,4915,4916],{"class":293},"))\n",[163,4918,4920,4922,4924,4926],{"class":165,"line":4919},30,[163,4921,2110],{"class":1805},[163,4923,4730],{"class":1888},[163,4925,1918],{"class":190},[163,4927,4928],{"class":1888},"buffer\n",[163,4930,4932],{"class":165,"line":4931},31,[163,4933,2119],{"class":190},[163,4935,4937],{"class":165,"line":4936},32,[163,4938,1934],{"emptyLinePlaceholder":842},[163,4940,4942],{"class":165,"line":4941},33,[163,4943,1792],{"class":332},[163,4945,4947],{"class":165,"line":4946},34,[163,4948,4949],{"class":332}," * [同构] 生成 AES-GCM 256位加密密钥\n",[163,4951,4953],{"class":165,"line":4952},35,[163,4954,1851],{"class":332},[163,4956,4958,4961,4964,4967,4969,4972,4975,4978,4981,4983,4986,4988],{"class":165,"line":4957},36,[163,4959,4960],{"class":1805},"export",[163,4962,4963],{"class":1856}," const",[163,4965,4966],{"class":1888}," generateAESKey ",[163,4968,2141],{"class":190},[163,4970,4971],{"class":1856}," async",[163,4973,4974],{"class":190}," ():",[163,4976,4977],{"class":169}," Promise",[163,4979,4980],{"class":190},"\u003C",[163,4982,1329],{"class":169},[163,4984,4985],{"class":190},">",[163,4987,2049],{"class":1856},[163,4989,1875],{"class":190},[163,4991,4993,4995,4997,4999,5001,5003],{"class":165,"line":4992},37,[163,4994,2110],{"class":1805},[163,4996,1951],{"class":1805},[163,4998,4657],{"class":1888},[163,5000,1918],{"class":190},[163,5002,2233],{"class":1863},[163,5004,2236],{"class":293},[163,5006,5008,5010,5012,5014,5016,5018,5020,5022,5025,5027,5029],{"class":165,"line":5007},38,[163,5009,3231],{"class":190},[163,5011,385],{"class":293},[163,5013,297],{"class":190},[163,5015,2079],{"class":190},[163,5017,2253],{"class":173},[163,5019,1968],{"class":190},[163,5021,1975],{"class":190},[163,5023,5024],{"class":293}," length",[163,5026,297],{"class":190},[163,5028,2268],{"class":183},[163,5030,3244],{"class":190},[163,5032,5034,5036,5038],{"class":165,"line":5033},39,[163,5035,3076],{"class":306},[163,5037,1975],{"class":190},[163,5039,5040],{"class":332}," \u002F\u002F 允许导出\n",[163,5042,5044,5046,5048,5050,5052,5054,5056,5058,5060],{"class":165,"line":5043},40,[163,5045,2291],{"class":293},[163,5047,1968],{"class":190},[163,5049,2296],{"class":173},[163,5051,1968],{"class":190},[163,5053,1975],{"class":190},[163,5055,2079],{"class":190},[163,5057,2305],{"class":173},[163,5059,1968],{"class":190},[163,5061,5062],{"class":293},"]\n",[163,5064,5066],{"class":165,"line":5065},41,[163,5067,5068],{"class":293},"    )\n",[163,5070,5072],{"class":165,"line":5071},42,[163,5073,2119],{"class":190},[163,5075,5077],{"class":165,"line":5076},43,[163,5078,1934],{"emptyLinePlaceholder":842},[163,5080,5082],{"class":165,"line":5081},44,[163,5083,1792],{"class":332},[163,5085,5087],{"class":165,"line":5086},45,[163,5088,5089],{"class":332}," * [同构] 导出 CryptoKey 密钥为可传输的 Base64 字符串\n",[163,5091,5093,5095,5097,5099,5102],{"class":165,"line":5092},46,[163,5094,1802],{"class":332},[163,5096,1806],{"class":1805},[163,5098,1810],{"class":1809},[163,5100,5101],{"class":1823}," cryptoKey",[163,5103,5104],{"class":332}," - 浏览器或服务端生成的 CryptoKey 实体\n",[163,5106,5108],{"class":165,"line":5107},47,[163,5109,1851],{"class":332},[163,5111,5113,5115,5117,5120,5122,5124,5126,5129,5131,5134,5136,5138,5140,5142,5144,5146],{"class":165,"line":5112},48,[163,5114,4960],{"class":1805},[163,5116,4963],{"class":1856},[163,5118,5119],{"class":1888}," exportKeyToBase64 ",[163,5121,2141],{"class":190},[163,5123,4971],{"class":1856},[163,5125,4615],{"class":190},[163,5127,5128],{"class":1823},"cryptoKey",[163,5130,297],{"class":190},[163,5132,5133],{"class":169}," CryptoKey",[163,5135,4716],{"class":190},[163,5137,4977],{"class":169},[163,5139,4980],{"class":190},[163,5141,1817],{"class":169},[163,5143,4985],{"class":190},[163,5145,2049],{"class":1856},[163,5147,1875],{"class":190},[163,5149,5151,5153,5156,5158,5160,5162,5164,5166,5168,5170,5173,5175,5177,5179],{"class":165,"line":5150},49,[163,5152,1885],{"class":1856},[163,5154,5155],{"class":1888}," exported",[163,5157,1892],{"class":190},[163,5159,1951],{"class":1805},[163,5161,4657],{"class":1888},[163,5163,1918],{"class":190},[163,5165,4042],{"class":1863},[163,5167,1867],{"class":293},[163,5169,1968],{"class":190},[163,5171,5172],{"class":173},"raw",[163,5174,1968],{"class":190},[163,5176,1975],{"class":190},[163,5178,5101],{"class":1888},[163,5180,4646],{"class":293},[163,5182,5184,5186,5189,5191,5194],{"class":165,"line":5183},50,[163,5185,2110],{"class":1805},[163,5187,5188],{"class":1863}," bufferToBase64",[163,5190,1867],{"class":293},[163,5192,5193],{"class":1888},"exported",[163,5195,4646],{"class":293},[163,5197,5199],{"class":165,"line":5198},51,[163,5200,2119],{"class":190},[163,5202,5204],{"class":165,"line":5203},52,[163,5205,1934],{"emptyLinePlaceholder":842},[163,5207,5209],{"class":165,"line":5208},53,[163,5210,1792],{"class":332},[163,5212,5214],{"class":165,"line":5213},54,[163,5215,5216],{"class":332}," * [同构] 从 Base64 字符串导入还原为 CryptoKey 密钥对象\n",[163,5218,5220,5222,5224,5226,5229],{"class":165,"line":5219},55,[163,5221,1802],{"class":332},[163,5223,1806],{"class":1805},[163,5225,1810],{"class":1809},[163,5227,5228],{"class":1823}," base64Key",[163,5230,5231],{"class":332}," - Base64 格式的密钥字符串\n",[163,5233,5235],{"class":165,"line":5234},56,[163,5236,1851],{"class":332},[163,5238,5240,5242,5244,5247,5249,5251,5253,5256,5258,5260,5262,5264,5266,5268,5270,5272],{"class":165,"line":5239},57,[163,5241,4960],{"class":1805},[163,5243,4963],{"class":1856},[163,5245,5246],{"class":1888}," importKeyFromBase64 ",[163,5248,2141],{"class":190},[163,5250,4971],{"class":1856},[163,5252,4615],{"class":190},[163,5254,5255],{"class":1823},"base64Key",[163,5257,297],{"class":190},[163,5259,4719],{"class":169},[163,5261,4716],{"class":190},[163,5263,4977],{"class":169},[163,5265,4980],{"class":190},[163,5267,1329],{"class":169},[163,5269,4985],{"class":190},[163,5271,2049],{"class":1856},[163,5273,1875],{"class":190},[163,5275,5277,5279,5282,5284,5287,5289,5291],{"class":165,"line":5276},58,[163,5278,1885],{"class":1856},[163,5280,5281],{"class":1888}," rawKeyBuffer",[163,5283,1892],{"class":190},[163,5285,5286],{"class":1863}," base64ToBuffer",[163,5288,1867],{"class":293},[163,5290,5255],{"class":1888},[163,5292,4646],{"class":293},[163,5294,5296,5298,5300,5302,5304,5306,5308,5310,5312,5314,5316,5318,5320,5322,5324,5326,5328,5330,5332,5334,5337,5339,5342,5344,5346,5348,5350,5352,5354,5356],{"class":165,"line":5295},59,[163,5297,2110],{"class":1805},[163,5299,1951],{"class":1805},[163,5301,4657],{"class":1888},[163,5303,1918],{"class":190},[163,5305,4157],{"class":1863},[163,5307,1867],{"class":293},[163,5309,1968],{"class":190},[163,5311,5172],{"class":173},[163,5313,1968],{"class":190},[163,5315,1975],{"class":190},[163,5317,5281],{"class":1888},[163,5319,1975],{"class":190},[163,5321,1813],{"class":190},[163,5323,385],{"class":293},[163,5325,297],{"class":190},[163,5327,2079],{"class":190},[163,5329,2253],{"class":173},[163,5331,1968],{"class":190},[163,5333,4194],{"class":190},[163,5335,5336],{"class":306}," true",[163,5338,1975],{"class":190},[163,5340,5341],{"class":293}," [",[163,5343,1968],{"class":190},[163,5345,2296],{"class":173},[163,5347,1968],{"class":190},[163,5349,1975],{"class":190},[163,5351,2079],{"class":190},[163,5353,2305],{"class":173},[163,5355,1968],{"class":190},[163,5357,5358],{"class":293},"])\n",[163,5360,5362],{"class":165,"line":5361},60,[163,5363,2119],{"class":190},[163,5365,5367],{"class":165,"line":5366},61,[163,5368,1934],{"emptyLinePlaceholder":842},[163,5370,5372],{"class":165,"line":5371},62,[163,5373,1792],{"class":332},[163,5375,5377],{"class":165,"line":5376},63,[163,5378,5379],{"class":332}," * [同构] 使用 AES-GCM 算法加密字符串文本\n",[163,5381,5383,5385,5387,5389,5391],{"class":165,"line":5382},64,[163,5384,1802],{"class":332},[163,5386,1806],{"class":1805},[163,5388,1810],{"class":1809},[163,5390,1824],{"class":1823},[163,5392,5393],{"class":332}," - 待加密的明文文本\n",[163,5395,5397,5399,5401,5403,5405],{"class":165,"line":5396},65,[163,5398,1802],{"class":332},[163,5400,1806],{"class":1805},[163,5402,1810],{"class":1809},[163,5404,2382],{"class":1823},[163,5406,5407],{"class":332}," - CryptoKey 密钥对象\n",[163,5409,5411,5413,5415,5417],{"class":165,"line":5410},66,[163,5412,1802],{"class":332},[163,5414,1806],{"class":1805},[163,5416,1836],{"class":1809},[163,5418,5419],{"class":332}," 返回打包了 \"IV\" 和 \"密文\" 的统一 Base64 字符串\n",[163,5421,5423],{"class":165,"line":5422},67,[163,5424,1851],{"class":332},[163,5426,5428,5430,5432,5435,5437,5439,5441,5443,5445,5447,5449,5451,5453,5455,5457,5459,5461,5463,5465,5467],{"class":165,"line":5427},68,[163,5429,4960],{"class":1805},[163,5431,4963],{"class":1856},[163,5433,5434],{"class":1888}," encryptText ",[163,5436,2141],{"class":190},[163,5438,4971],{"class":1856},[163,5440,4615],{"class":190},[163,5442,1870],{"class":1823},[163,5444,297],{"class":190},[163,5446,4719],{"class":169},[163,5448,1975],{"class":190},[163,5450,2382],{"class":1823},[163,5452,297],{"class":190},[163,5454,5133],{"class":169},[163,5456,4716],{"class":190},[163,5458,4977],{"class":169},[163,5460,4980],{"class":190},[163,5462,1817],{"class":169},[163,5464,4985],{"class":190},[163,5466,2049],{"class":1856},[163,5468,1875],{"class":190},[163,5470,5472,5474,5476,5478,5480,5482],{"class":165,"line":5471},69,[163,5473,1885],{"class":1856},[163,5475,1889],{"class":1888},[163,5477,1892],{"class":190},[163,5479,1895],{"class":190},[163,5481,1898],{"class":1863},[163,5483,5484],{"class":293},"()\n",[163,5486,5488,5490,5492,5494,5496,5498,5500,5502,5504,5506,5508,5510,5512,5514],{"class":165,"line":5487},70,[163,5489,1885],{"class":1856},[163,5491,2451],{"class":1888},[163,5493,297],{"class":190},[163,5495,2015],{"class":169},[163,5497,4980],{"class":190},[163,5499,2670],{"class":169},[163,5501,4985],{"class":190},[163,5503,1892],{"class":190},[163,5505,1889],{"class":1888},[163,5507,1918],{"class":190},[163,5509,1921],{"class":1863},[163,5511,1867],{"class":293},[163,5513,1870],{"class":1888},[163,5515,4646],{"class":293},[163,5517,5519],{"class":165,"line":5518},71,[163,5520,5521],{"class":332},"    \u002F\u002F 生成 12 字节的密码学安全伪随机数作为 IV (AES-GCM 标准推荐)\n",[163,5523,5525,5527,5529,5531,5533,5535,5537,5539,5541,5543,5545,5547,5549,5551,5553,5555,5557],{"class":165,"line":5524},72,[163,5526,1885],{"class":1856},[163,5528,2488],{"class":1888},[163,5530,297],{"class":190},[163,5532,2015],{"class":169},[163,5534,4980],{"class":190},[163,5536,2670],{"class":169},[163,5538,4985],{"class":190},[163,5540,1892],{"class":190},[163,5542,4667],{"class":1888},[163,5544,1918],{"class":190},[163,5546,2497],{"class":1863},[163,5548,1867],{"class":293},[163,5550,2012],{"class":190},[163,5552,2015],{"class":1863},[163,5554,1867],{"class":293},[163,5556,2508],{"class":183},[163,5558,4916],{"class":293},[163,5560,5562],{"class":165,"line":5561},73,[163,5563,5564],{"class":332},"    \u002F\u002F 执行加密，返回密文 ArrayBuffer\n",[163,5566,5568,5570,5573,5575,5577,5579,5581,5583,5585,5587,5589,5592,5594,5596,5598,5600,5602,5604,5606,5608,5610,5612,5614,5616,5618],{"class":165,"line":5567},74,[163,5569,1885],{"class":1856},[163,5571,5572],{"class":1888}," cipherBuffer",[163,5574,297],{"class":190},[163,5576,4713],{"class":169},[163,5578,1892],{"class":190},[163,5580,1951],{"class":1805},[163,5582,4657],{"class":1888},[163,5584,1918],{"class":190},[163,5586,2296],{"class":1863},[163,5588,1867],{"class":293},[163,5590,5591],{"class":190},"{",[163,5593,385],{"class":293},[163,5595,297],{"class":190},[163,5597,2079],{"class":190},[163,5599,2253],{"class":173},[163,5601,1968],{"class":190},[163,5603,1975],{"class":190},[163,5605,2488],{"class":293},[163,5607,297],{"class":190},[163,5609,2488],{"class":1888},[163,5611,4194],{"class":190},[163,5613,2382],{"class":1888},[163,5615,1975],{"class":190},[163,5617,2451],{"class":1888},[163,5619,4646],{"class":293},[163,5621,5623],{"class":165,"line":5622},75,[163,5624,5625],{"class":332},"    \u002F\u002F 将 IV（12字节）和密文拼接，方便单字符串传输\n",[163,5627,5629,5631,5634,5636,5638,5640,5642,5645,5647,5650,5653,5655,5657,5660],{"class":165,"line":5628},76,[163,5630,1885],{"class":1856},[163,5632,5633],{"class":1888}," combined",[163,5635,1892],{"class":190},[163,5637,1895],{"class":190},[163,5639,2015],{"class":1863},[163,5641,1867],{"class":293},[163,5643,5644],{"class":1888},"iv",[163,5646,1918],{"class":190},[163,5648,5649],{"class":1888},"length",[163,5651,5652],{"class":190}," +",[163,5654,5572],{"class":1888},[163,5656,1918],{"class":190},[163,5658,5659],{"class":1888},"byteLength",[163,5661,4646],{"class":293},[163,5663,5665,5668,5670,5673,5675,5677,5679,5681],{"class":165,"line":5664},77,[163,5666,5667],{"class":1888},"    combined",[163,5669,1918],{"class":190},[163,5671,5672],{"class":1863},"set",[163,5674,1867],{"class":293},[163,5676,5644],{"class":1888},[163,5678,1975],{"class":190},[163,5680,3036],{"class":183},[163,5682,4646],{"class":293},[163,5684,5686,5688,5690,5692,5694,5696,5698,5700,5703,5705,5707,5709,5711,5713],{"class":165,"line":5685},78,[163,5687,5667],{"class":1888},[163,5689,1918],{"class":190},[163,5691,5672],{"class":1863},[163,5693,1867],{"class":293},[163,5695,2012],{"class":190},[163,5697,2015],{"class":1863},[163,5699,1867],{"class":293},[163,5701,5702],{"class":1888},"cipherBuffer",[163,5704,133],{"class":293},[163,5706,1975],{"class":190},[163,5708,2488],{"class":1888},[163,5710,1918],{"class":190},[163,5712,5649],{"class":1888},[163,5714,4646],{"class":293},[163,5716,5718,5720,5722,5724,5727,5729,5731],{"class":165,"line":5717},79,[163,5719,2110],{"class":1805},[163,5721,5188],{"class":1863},[163,5723,1867],{"class":293},[163,5725,5726],{"class":1888},"combined",[163,5728,1918],{"class":190},[163,5730,4708],{"class":1888},[163,5732,4646],{"class":293},[163,5734,5736],{"class":165,"line":5735},80,[163,5737,2119],{"class":190},[163,5739,5741],{"class":165,"line":5740},81,[163,5742,1934],{"emptyLinePlaceholder":842},[163,5744,5746],{"class":165,"line":5745},82,[163,5747,1792],{"class":332},[163,5749,5751],{"class":165,"line":5750},83,[163,5752,5753],{"class":332}," * [同构] 使用 AES-GCM 算法解密文本\n",[163,5755,5757,5759,5761,5763,5766],{"class":165,"line":5756},84,[163,5758,1802],{"class":332},[163,5760,1806],{"class":1805},[163,5762,1810],{"class":1809},[163,5764,5765],{"class":1823}," combinedBase64",[163,5767,5768],{"class":332}," - 由 encryptText 生成的统一加密 Base64 字符串（内部含 IV）\n",[163,5770,5772,5774,5776,5778,5780],{"class":165,"line":5771},85,[163,5773,1802],{"class":332},[163,5775,1806],{"class":1805},[163,5777,1810],{"class":1809},[163,5779,2382],{"class":1823},[163,5781,5782],{"class":332}," - 对应的 CryptoKey 密钥对象\n",[163,5784,5786],{"class":165,"line":5785},86,[163,5787,1851],{"class":332},[163,5789,5791,5793,5795,5798,5800,5802,5804,5807,5809,5811,5813,5815,5817,5819,5821,5823,5825,5827,5829,5831],{"class":165,"line":5790},87,[163,5792,4960],{"class":1805},[163,5794,4963],{"class":1856},[163,5796,5797],{"class":1888}," decryptText ",[163,5799,2141],{"class":190},[163,5801,4971],{"class":1856},[163,5803,4615],{"class":190},[163,5805,5806],{"class":1823},"combinedBase64",[163,5808,297],{"class":190},[163,5810,4719],{"class":169},[163,5812,1975],{"class":190},[163,5814,2382],{"class":1823},[163,5816,297],{"class":190},[163,5818,5133],{"class":169},[163,5820,4716],{"class":190},[163,5822,4977],{"class":169},[163,5824,4980],{"class":190},[163,5826,1817],{"class":169},[163,5828,4985],{"class":190},[163,5830,2049],{"class":1856},[163,5832,1875],{"class":190},[163,5834,5836,5838,5841,5843,5845,5847,5849],{"class":165,"line":5835},88,[163,5837,1885],{"class":1856},[163,5839,5840],{"class":1888}," combinedBuffer",[163,5842,1892],{"class":190},[163,5844,5286],{"class":1863},[163,5846,1867],{"class":293},[163,5848,5806],{"class":1888},[163,5850,4646],{"class":293},[163,5852,5854,5856,5859,5861,5863,5865,5867,5870],{"class":165,"line":5853},89,[163,5855,1885],{"class":1856},[163,5857,5858],{"class":1888}," combinedBytes",[163,5860,1892],{"class":190},[163,5862,1895],{"class":190},[163,5864,2015],{"class":1863},[163,5866,1867],{"class":293},[163,5868,5869],{"class":1888},"combinedBuffer",[163,5871,4646],{"class":293},[163,5873,5875],{"class":165,"line":5874},90,[163,5876,5877],{"class":332},"    \u002F\u002F 从头部切出前 12 字节作为 IV\n",[163,5879,5881,5883,5885,5887,5889,5891,5893,5895,5897,5899,5901,5904,5906,5908,5910,5913],{"class":165,"line":5880},91,[163,5882,1885],{"class":1856},[163,5884,2488],{"class":1888},[163,5886,297],{"class":190},[163,5888,2015],{"class":169},[163,5890,4980],{"class":190},[163,5892,2670],{"class":169},[163,5894,4985],{"class":190},[163,5896,1892],{"class":190},[163,5898,5858],{"class":1888},[163,5900,1918],{"class":190},[163,5902,5903],{"class":1863},"slice",[163,5905,1867],{"class":293},[163,5907,2082],{"class":183},[163,5909,1975],{"class":190},[163,5911,5912],{"class":183}," 12",[163,5914,4646],{"class":293},[163,5916,5918],{"class":165,"line":5917},92,[163,5919,5920],{"class":332},"    \u002F\u002F 剩下的部分作为真正的密文\n",[163,5922,5924,5926,5928,5930,5932,5934,5936,5938,5940,5942,5944,5946,5948,5950],{"class":165,"line":5923},93,[163,5925,1885],{"class":1856},[163,5927,2528],{"class":1888},[163,5929,297],{"class":190},[163,5931,2015],{"class":169},[163,5933,4980],{"class":190},[163,5935,2670],{"class":169},[163,5937,4985],{"class":190},[163,5939,1892],{"class":190},[163,5941,5858],{"class":1888},[163,5943,1918],{"class":190},[163,5945,5903],{"class":1863},[163,5947,1867],{"class":293},[163,5949,2508],{"class":183},[163,5951,4646],{"class":293},[163,5953,5955],{"class":165,"line":5954},94,[163,5956,2768],{"class":332},[163,5958,5960,5962,5964,5966,5968,5970,5972,5974,5976,5978,5980,5982,5984,5986,5988,5990,5992,5994,5996,5998,6000,6002,6004,6006,6008,6010,6012],{"class":165,"line":5959},95,[163,5961,1885],{"class":1856},[163,5963,2775],{"class":1888},[163,5965,297],{"class":190},[163,5967,4713],{"class":169},[163,5969,1892],{"class":190},[163,5971,1951],{"class":1805},[163,5973,4657],{"class":1888},[163,5975,1918],{"class":190},[163,5977,2305],{"class":1863},[163,5979,1867],{"class":293},[163,5981,5591],{"class":190},[163,5983,385],{"class":293},[163,5985,297],{"class":190},[163,5987,2079],{"class":190},[163,5989,2253],{"class":173},[163,5991,1968],{"class":190},[163,5993,1975],{"class":190},[163,5995,2488],{"class":293},[163,5997,297],{"class":190},[163,5999,2488],{"class":1888},[163,6001,4194],{"class":190},[163,6003,2382],{"class":1888},[163,6005,1975],{"class":190},[163,6007,2528],{"class":1888},[163,6009,1918],{"class":190},[163,6011,4708],{"class":1888},[163,6013,4646],{"class":293},[163,6015,6017,6019,6021,6023,6025,6027],{"class":165,"line":6016},96,[163,6018,1885],{"class":1856},[163,6020,2857],{"class":1888},[163,6022,1892],{"class":190},[163,6024,1895],{"class":190},[163,6026,2864],{"class":1863},[163,6028,5484],{"class":293},[163,6030,6032,6034,6036,6038,6040,6042,6044],{"class":165,"line":6031},97,[163,6033,2110],{"class":1805},[163,6035,2857],{"class":1888},[163,6037,1918],{"class":190},[163,6039,2879],{"class":1863},[163,6041,1867],{"class":293},[163,6043,2884],{"class":1888},[163,6045,4646],{"class":293},[163,6047,6049],{"class":165,"line":6048},98,[163,6050,2119],{"class":190},[13,6052,6053],{},"TS 测试脚本",[13,6055,6056],{},"如果你在 Vite\u002FNuxt\u002FNext.js 项目或者使用了 ts-node \u002F bun \u002F deno 的后端环境中，可以直接引入并运行这段强类型的测试代码：",[154,6058,6061],{"className":4559,"code":6059,"filename":6060,"language":4562,"meta":159,"style":159},"import { decryptText, encryptText, exportKeyToBase64, generateAESKey, importKeyFromBase64 } from 'crypto'\n;(async () => {\n    try {\n        const rawData: string = '前后端全平台无缝传输的敏感数据 1234-abcd-TS-VERSION'\n        console.log('🔒 原始待加解密明文:', rawData)\n\n        \u002F\u002F ==========================================\n        \u002F\u002F 阶段一：在 A 端（例如 Node.js 后端）生成密钥并加密\n        \u002F\u002F ==========================================\n        const originalKey: CryptoKey = await generateAESKey()\n\n        \u002F\u002F 导出密钥为 Base64 字符串\n        const transportableKeyStr: string = await exportKeyToBase64(originalKey)\n        console.log('🔑 [传输中] 密钥已安全序列化为 Base64:', transportableKeyStr)\n\n        \u002F\u002F 对数据进行加密\n        const secureCipherText: string = await encryptText(rawData, originalKey)\n        console.log('📦 [传输中] 密文已安全打包（含IV）为 Base64:', secureCipherText)\n\n        \u002F\u002F ==========================================\n        \u002F\u002F 阶段二：在 B 端（例如现代 Web 浏览器前端）收到数据解密\n        \u002F\u002F ==========================================\n        console.log('\\n--- 数据已通过网络无缝抵达另一端 ---')\n\n        \u002F\u002F 1. 恢复强类型的 CryptoKey 实体\n        const receivedKey: CryptoKey = await importKeyFromBase64(transportableKeyStr)\n\n        \u002F\u002F 2. 还原数据内容\n        const decryptedOutput: string = await decryptText(secureCipherText, receivedKey)\n        console.log('🔓 [还原成功] 解密后的最终明文结果:', decryptedOutput)\n    } catch (error) {\n        console.error('❌ 密码学同构解密链路失败，错误原因: ', error)\n    }\n})()\n\n","test-crypto.ts",[42,6062,6063,6102,6118,6125,6146,6171,6175,6180,6185,6189,6208,6212,6217,6241,6264,6268,6273,6301,6324,6328,6332,6337,6341,6363,6367,6372,6396,6400,6405,6433,6456,6474,6498,6503],{"__ignoreMap":159},[163,6064,6065,6068,6070,6072,6074,6076,6078,6081,6083,6085,6087,6090,6093,6096,6098,6100],{"class":165,"line":166},[163,6066,6067],{"class":1805},"import",[163,6069,1813],{"class":190},[163,6071,2746],{"class":1888},[163,6073,1975],{"class":190},[163,6075,2416],{"class":1888},[163,6077,1975],{"class":190},[163,6079,6080],{"class":1888}," exportKeyToBase64",[163,6082,1975],{"class":190},[163,6084,2212],{"class":1888},[163,6086,1975],{"class":190},[163,6088,6089],{"class":1888}," importKeyFromBase64",[163,6091,6092],{"class":190}," }",[163,6094,6095],{"class":1805}," from",[163,6097,2079],{"class":190},[163,6099,4381],{"class":173},[163,6101,594],{"class":190},[163,6103,6104,6107,6109,6111,6114,6116],{"class":165,"line":303},[163,6105,6106],{"class":190},";",[163,6108,1867],{"class":1888},[163,6110,1857],{"class":1856},[163,6112,6113],{"class":190}," ()",[163,6115,2049],{"class":1856},[163,6117,1875],{"class":190},[163,6119,6120,6123],{"class":165,"line":313},[163,6121,6122],{"class":1805},"    try",[163,6124,1875],{"class":190},[163,6126,6127,6130,6133,6135,6137,6139,6141,6144],{"class":165,"line":321},[163,6128,6129],{"class":1856},"        const",[163,6131,6132],{"class":1888}," rawData",[163,6134,297],{"class":190},[163,6136,4719],{"class":169},[163,6138,1892],{"class":190},[163,6140,2079],{"class":190},[163,6142,6143],{"class":173},"前后端全平台无缝传输的敏感数据 1234-abcd-TS-VERSION",[163,6145,594],{"class":190},[163,6147,6148,6151,6153,6156,6158,6160,6163,6165,6167,6169],{"class":165,"line":329},[163,6149,6150],{"class":1888},"        console",[163,6152,1918],{"class":190},[163,6154,6155],{"class":1863},"log",[163,6157,1867],{"class":293},[163,6159,1968],{"class":190},[163,6161,6162],{"class":173},"🔒 原始待加解密明文:",[163,6164,1968],{"class":190},[163,6166,1975],{"class":190},[163,6168,6132],{"class":1888},[163,6170,4646],{"class":293},[163,6172,6173],{"class":165,"line":336},[163,6174,1934],{"emptyLinePlaceholder":842},[163,6176,6177],{"class":165,"line":345},[163,6178,6179],{"class":332},"        \u002F\u002F ==========================================\n",[163,6181,6182],{"class":165,"line":353},[163,6183,6184],{"class":332},"        \u002F\u002F 阶段一：在 A 端（例如 Node.js 后端）生成密钥并加密\n",[163,6186,6187],{"class":165,"line":361},[163,6188,6179],{"class":332},[163,6190,6191,6193,6196,6198,6200,6202,6204,6206],{"class":165,"line":372},[163,6192,6129],{"class":1856},[163,6194,6195],{"class":1888}," originalKey",[163,6197,297],{"class":190},[163,6199,5133],{"class":169},[163,6201,1892],{"class":190},[163,6203,1951],{"class":1805},[163,6205,2212],{"class":1863},[163,6207,5484],{"class":293},[163,6209,6210],{"class":165,"line":380},[163,6211,1934],{"emptyLinePlaceholder":842},[163,6213,6214],{"class":165,"line":392},[163,6215,6216],{"class":332},"        \u002F\u002F 导出密钥为 Base64 字符串\n",[163,6218,6219,6221,6224,6226,6228,6230,6232,6234,6236,6239],{"class":165,"line":403},[163,6220,6129],{"class":1856},[163,6222,6223],{"class":1888}," transportableKeyStr",[163,6225,297],{"class":190},[163,6227,4719],{"class":169},[163,6229,1892],{"class":190},[163,6231,1951],{"class":1805},[163,6233,6080],{"class":1863},[163,6235,1867],{"class":293},[163,6237,6238],{"class":1888},"originalKey",[163,6240,4646],{"class":293},[163,6242,6243,6245,6247,6249,6251,6253,6256,6258,6260,6262],{"class":165,"line":411},[163,6244,6150],{"class":1888},[163,6246,1918],{"class":190},[163,6248,6155],{"class":1863},[163,6250,1867],{"class":293},[163,6252,1968],{"class":190},[163,6254,6255],{"class":173},"🔑 [传输中] 密钥已安全序列化为 Base64:",[163,6257,1968],{"class":190},[163,6259,1975],{"class":190},[163,6261,6223],{"class":1888},[163,6263,4646],{"class":293},[163,6265,6266],{"class":165,"line":417},[163,6267,1934],{"emptyLinePlaceholder":842},[163,6269,6270],{"class":165,"line":428},[163,6271,6272],{"class":332},"        \u002F\u002F 对数据进行加密\n",[163,6274,6275,6277,6280,6282,6284,6286,6288,6290,6292,6295,6297,6299],{"class":165,"line":436},[163,6276,6129],{"class":1856},[163,6278,6279],{"class":1888}," secureCipherText",[163,6281,297],{"class":190},[163,6283,4719],{"class":169},[163,6285,1892],{"class":190},[163,6287,1951],{"class":1805},[163,6289,2416],{"class":1863},[163,6291,1867],{"class":293},[163,6293,6294],{"class":1888},"rawData",[163,6296,1975],{"class":190},[163,6298,6195],{"class":1888},[163,6300,4646],{"class":293},[163,6302,6303,6305,6307,6309,6311,6313,6316,6318,6320,6322],{"class":165,"line":442},[163,6304,6150],{"class":1888},[163,6306,1918],{"class":190},[163,6308,6155],{"class":1863},[163,6310,1867],{"class":293},[163,6312,1968],{"class":190},[163,6314,6315],{"class":173},"📦 [传输中] 密文已安全打包（含IV）为 Base64:",[163,6317,1968],{"class":190},[163,6319,1975],{"class":190},[163,6321,6279],{"class":1888},[163,6323,4646],{"class":293},[163,6325,6326],{"class":165,"line":453},[163,6327,1934],{"emptyLinePlaceholder":842},[163,6329,6330],{"class":165,"line":459},[163,6331,6179],{"class":332},[163,6333,6334],{"class":165,"line":2126},[163,6335,6336],{"class":332},"        \u002F\u002F 阶段二：在 B 端（例如现代 Web 浏览器前端）收到数据解密\n",[163,6338,6339],{"class":165,"line":2132},[163,6340,6179],{"class":332},[163,6342,6343,6345,6347,6349,6351,6353,6356,6359,6361],{"class":165,"line":2599},[163,6344,6150],{"class":1888},[163,6346,1918],{"class":190},[163,6348,6155],{"class":1863},[163,6350,1867],{"class":293},[163,6352,1968],{"class":190},[163,6354,6355],{"class":1888},"\\n",[163,6357,6358],{"class":173},"--- 数据已通过网络无缝抵达另一端 ---",[163,6360,1968],{"class":190},[163,6362,4646],{"class":293},[163,6364,6365],{"class":165,"line":2606},[163,6366,1934],{"emptyLinePlaceholder":842},[163,6368,6369],{"class":165,"line":2611},[163,6370,6371],{"class":332},"        \u002F\u002F 1. 恢复强类型的 CryptoKey 实体\n",[163,6373,6374,6376,6379,6381,6383,6385,6387,6389,6391,6394],{"class":165,"line":2617},[163,6375,6129],{"class":1856},[163,6377,6378],{"class":1888}," receivedKey",[163,6380,297],{"class":190},[163,6382,5133],{"class":169},[163,6384,1892],{"class":190},[163,6386,1951],{"class":1805},[163,6388,6089],{"class":1863},[163,6390,1867],{"class":293},[163,6392,6393],{"class":1888},"transportableKeyStr",[163,6395,4646],{"class":293},[163,6397,6398],{"class":165,"line":2633},[163,6399,1934],{"emptyLinePlaceholder":842},[163,6401,6402],{"class":165,"line":4854},[163,6403,6404],{"class":332},"        \u002F\u002F 2. 还原数据内容\n",[163,6406,6407,6409,6412,6414,6416,6418,6420,6422,6424,6427,6429,6431],{"class":165,"line":4873},[163,6408,6129],{"class":1856},[163,6410,6411],{"class":1888}," decryptedOutput",[163,6413,297],{"class":190},[163,6415,4719],{"class":169},[163,6417,1892],{"class":190},[163,6419,1951],{"class":1805},[163,6421,2746],{"class":1863},[163,6423,1867],{"class":293},[163,6425,6426],{"class":1888},"secureCipherText",[163,6428,1975],{"class":190},[163,6430,6378],{"class":1888},[163,6432,4646],{"class":293},[163,6434,6435,6437,6439,6441,6443,6445,6448,6450,6452,6454],{"class":165,"line":4919},[163,6436,6150],{"class":1888},[163,6438,1918],{"class":190},[163,6440,6155],{"class":1863},[163,6442,1867],{"class":293},[163,6444,1968],{"class":190},[163,6446,6447],{"class":173},"🔓 [还原成功] 解密后的最终明文结果:",[163,6449,1968],{"class":190},[163,6451,1975],{"class":190},[163,6453,6411],{"class":1888},[163,6455,4646],{"class":293},[163,6457,6458,6461,6464,6466,6469,6472],{"class":165,"line":4931},[163,6459,6460],{"class":190},"    }",[163,6462,6463],{"class":1805}," catch",[163,6465,4615],{"class":293},[163,6467,6468],{"class":1888},"error",[163,6470,6471],{"class":293},") ",[163,6473,4624],{"class":190},[163,6475,6476,6478,6480,6482,6484,6486,6489,6491,6493,6496],{"class":165,"line":4936},[163,6477,6150],{"class":1888},[163,6479,1918],{"class":190},[163,6481,6468],{"class":1863},[163,6483,1867],{"class":293},[163,6485,1968],{"class":190},[163,6487,6488],{"class":173},"❌ 密码学同构解密链路失败，错误原因: ",[163,6490,1968],{"class":190},[163,6492,1975],{"class":190},[163,6494,6495],{"class":1888}," error",[163,6497,4646],{"class":293},[163,6499,6500],{"class":165,"line":4941},[163,6501,6502],{"class":190},"    }\n",[163,6504,6505,6507],{"class":165,"line":4946},[163,6506,1820],{"class":190},[163,6508,6509],{"class":1888},")()\n",[810,6511,6512],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s6hCs, html code.shiki .s6hCs{--shiki-light:#9C3EDA;--shiki-light-font-style:italic;--shiki-default:#C792EA;--shiki-default-font-style:italic;--shiki-dark:#C792EA;--shiki-dark-font-style:italic}html pre.shiki code .sFweD, html code.shiki .sFweD{--shiki-light:#E2931D;--shiki-light-font-style:italic;--shiki-default:#FFCB6B;--shiki-default-font-style:italic;--shiki-dark:#FFCB6B;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":159,"searchDepth":313,"depth":313,"links":6514},[6515,6516,6517,6518,6526,6527,6528,6534,6535,6536],{"id":875,"depth":303,"text":876},{"id":899,"depth":303,"text":900},{"id":1013,"depth":303,"text":1014},{"id":1034,"depth":303,"text":1035,"children":6519},[6520,6521,6522,6523,6524,6525],{"id":1041,"depth":313,"text":1042},{"id":1081,"depth":313,"text":1082},{"id":1144,"depth":313,"text":1145},{"id":1193,"depth":313,"text":1194},{"id":1226,"depth":313,"text":1227},{"id":1265,"depth":313,"text":1266},{"id":1354,"depth":303,"text":1355},{"id":1705,"depth":303,"text":1706},{"id":1758,"depth":303,"text":1759,"children":6529},[6530,6531,6532,6533],{"id":1765,"depth":313,"text":1766},{"id":2901,"depth":313,"text":2902},{"id":3413,"depth":313,"text":3414},{"id":3938,"depth":313,"text":3939},{"id":4242,"depth":303,"text":4243},{"id":4339,"depth":303,"text":4340},{"id":4549,"depth":303,"text":4550},{"name":6538,"symbol":6539,"icon":6540,"id":372},"Node.js","node","material-icon-theme:nodejs-alt","2026-08-25 15:59:25",{"type":10,"value":6543},[6544,6547],[13,6545,6546],{},"在过去很长一段时间里，只要一提到在前端进行加密、解密、生成哈希（MD5\u002FSHA）或生成随机数，绝大多数开发者的第一反应就是 npm install crypto-js。作为曾经的功勋库，crypto-js 陪伴我们度过了无数个项目。",[13,6548,6549],{},"然而，随着 Web 标准的演进，现代浏览器早已内置了一个更强大、更安全、性能也更恐怖的官方武器——Web Crypto API。时至今日，如果你还在新项目里打包第三方的加密库，是时候了解一下为什么要换成原生 API 了。",{},"\u002Fblog\u002Fweb-crypto-api","---\ntitle: Web Crypto API 浏览器原生加密的终极指南\ndescription: Web Crypto API 是浏览器原生提供的一套加密接口，允许 JavaScript 脚本使用密码学原语来构建基于加密的系统。它自 2015 年 7 月起已得到各大浏览器的广泛支持，是一个相当成熟且稳定的 Web 标准。\narticleId: web-crypto-api\ncreateDate: \"2026-08-25 15:59:25\"\nupdateDate: \"2026-08-26 10:46:16\"\narticleThumb: https:\u002F\u002Fcdn.anyfork.top\u002Fimages\u002F20260825111119031.webp\ntagIds: \n    - 12\ncategoryId: 10\nisTop: false\nisRecommend: false\nisHot: true\nisHidden: false\nnavigation: true\n---\n在过去很长一段时间里，只要一提到在前端进行加密、解密、生成哈希（MD5\u002FSHA）或生成随机数，绝大多数开发者的第一反应就是 npm install crypto-js。作为曾经的功勋库，crypto-js 陪伴我们度过了无数个项目。\n\n然而，随着 Web 标准的演进，现代浏览器早已内置了一个更强大、更安全、性能也更恐怖的官方武器——Web Crypto API。时至今日，如果你还在新项目里打包第三方的加密库，是时候了解一下为什么要换成原生 API 了。\n\u003C!--more-->\n\n## 1. 什么是 Web Crypto API？\nWeb Crypto API 是浏览器原生提供的一套加密接口，允许 JavaScript 脚本使用密码学原语来构建基于加密的系统。它自 2015 年 7 月起已得到各大浏览器的广泛支持，是一个相当成熟且稳定的 Web 标准。\n\n在 Web Crypto 规范诞生前，部分浏览器为了满足加密需求，在全局实现了一个名为 Crypto 的接口(即 window.crypto)。但正由于这样，当时各大浏览器的实现缺乏统一的 W3C 标准，定义模糊，且在密码学意义上是不够严谨和安全的（not cryptographically sound）。\n\n当正式的 Web Crypto API 标准（支持 encrypt、decrypt、sign、verify 等现代异步加密操作）推出时，为了彻底避免和老旧、不安全的旧接口方法发生命名混淆与冲突，W3C 规范决定将这些低底层的密码学核心方法群全部打包进一个全新的独立接口中，这就是 SubtleCrypto。你可以通过全局对象的 crypto.subtle 属性来访问它。\n\n由于旧的全局 crypto 对象并没有被废除（它依然保留了诸如密码学安全的伪随机数生成器 crypto.getRandomValues() 和 crypto.randomUUID() 等简单的同步方法），因此所有新版的 SubtleCrypto 复杂加密算法全部被挂载到了它的下级属性中。你必须通过 crypto.subtle 属性才能访问到这一整套现代加解密方法。\n\nNode.js 最早在 v15.0.0 引入了 Web Crypto API 接口。直到 Node.js 19.0.0，官方正式去掉了实验性警告，将其标记为 稳定版本（Stable），并且默认在全局作用域挂载了 globalThis.crypto。\n\n::warning\n重要提示：在现代浏览器中，为了保障密钥安全，crypto.subtle 属性被限制为只能在安全上下文（HTTPS 协议或 localhost）下才能被成功访问。如果你在普通的非加密 HTTP 网页里调用 crypto.subtle，它会直接返回 undefined。\n::\n## 2. 为什么选择 Web Crypto API？\n在决定是否在 Web 项目中使用 Web Crypto API 时，通常是因为它相比传统的第三方加密库（如 CryptoJS, jsencrypt, forge 等）在安全性、性能和原生支持度上带来了降维打击的优势。\n1. **防 XSS 攻击的“密钥黑盒”机制（核心安全优势）** : 这是选择Web Crypto API最关键的安全理由。\n- **传统加密库的痛点：** 如果你用 CryptoJS，密钥必须以明文字符串或 Uint8Array 的形式存在于 JavaScript 变量、localStorage 或 sessionStorage 中。一旦网站遭受 XSS（跨站脚本）攻击，黑客可以直接读取并盗走你的密钥。\n- **Web Crypto 的解决办法：** 它引入了 CryptoKey 对象。在生成或导入密钥时，如果设置 extractable: false，这个密钥在创建后将永远无法通过 JavaScript 读取、打印或导出。它被死死封锁在浏览器的底层安全内存中。黑客即使通过 XSS 控制了你的页面，也拿不到密钥本身，只能在本地调用它，大大降低了密钥泄露的风险。\n2. **硬件级的高性能与非阻塞（异步原生）**\n- **原生 C++ \u002F 汇编实现：** Web Crypto API 是由浏览器底层（基于 OpenSSL 或操作系统自带的密码学库）用原生代码实现的。相比纯 JavaScript 编写的加密库，它的运行速度快了数十倍到上百倍。\n- **异步非阻塞：** 传统的加密库（如 CryptoJS）大都是同步执行的。当计算大文件哈希，或者进行复杂的 RSA 大素数生成时，会直接卡死浏览器的 UI 主线程，导致页面瞬间卡顿或未响应。而 Web Crypto 的所有核心方法（如 encrypt、digest）全部是异步 Promise 机制，浏览器会将其交给底层的系统线程去算，完全不影响前端界面的流畅度。\n3. **密码学安全的伪随机数生成器 (CSPRNG)**: 在密码学中，随机数的质量决定了加密的生死。\n- **传统的隐患：** JavaScript 原生的 Math.random() 是非密码学安全的，它的随机轨迹可以被算法预测，绝不能用于生成加密密钥、IV（初始化向量）或盐值。\n- **Web Crypto 的保障：** 提供了 crypto.getRandomValues()，它直接调用操作系统底层的硬件熵源（如系统的 \u002Fdev\u002Furandom 或 CAPI），生成密码学安全的高强度真随机数，保障加密系统的根基不可被破解。\n4. **零依赖，极致减小打包体积 (Zero-Bundle)**\n- **传统加密库的痛点：** 引入 CryptoJS、JSEncrypt 或 Forge 会让前端的 Vendor 打包体积（Bundle Size）平白无故增加几十 KB 甚至几百 KB，影响首屏加载速度。\n- **Web Crypto 的优势：** 它是现代浏览器的标准内置 API，不需要 npm install 任何第三方包。直接开箱即用，让前端工程更加轻量、纯净。\n5. **行业标准规范，跨端兼容性完美**\n- **大厂和 W3C 背书：** 它遵循严格的 W3C 规范，由各大浏览器厂商共同维护和不断优化。\n- **环境通用性：** 目前 Web Crypto API 的覆盖率已接近 100%。不仅在 Chrome、Safari、Edge、Firefox 浏览器中完美支持，在现代的 Node.js (v15+)、Deno、Bun 以及 Cloudflare Workers 等服务端\u002F边缘计算环境中也实现了完全一致的 API。这使得你写的一套加解密代码，可以无缝在前端和服务端之间复用。\n## 3. 什么时候不应该选择它？\n使用 Web Crypto API 有唯一的限制条件：\n- **非安全上下文限制：** 它强制要求 HTTPS 环境（本地开发支持 localhost 或 127.0.0.1）。如果你的网站部署在纯 HTTP 生产环境下，crypto.subtle 会直接返回 undefined 无法工作。\n- **需要高层封装：** 它提供的是非常底层的“密码学原语”（Raw Primitives），不提供像“一键生成 JWT”或者“一键做密码信封”这样的高级功能，开发者需要自己处理字节对齐、IV 传递等微秒的细节。\n## 4. Web Crypto API核心接口\nWeb Crypto API（通常称为 crypto.subtle）的核心接口主要由以下 5 个标准接口（Interfaces） 和 1 个全局入口 组成。它们构成了浏览器原生密码学生态的基石。\n### 4.1 全局入口对象Crypto\n虽然这不是单独的加解密接口，但它是所有 Web 加密功能的总入口，挂载在全局 window.crypto（或 globalThis.crypto）上。Crypto提供基础的加密功能，主要包括：\n- **核心作用：** 提供基础的同步安全原语。\n- **常用方法：**\n   - **crypto.getRandomValues()：** 生成密码学安全的伪随机数（常用于生成 IV 或盐）。\n   - **crypto.randomUUID()：** 快速生成一个符合 RFC 4122 标准的随机 UUID 字符串。\n   - **crypto.subtle：** 指向核心异步加密接口 SubtleCrypto 的指针。\n### 4.2  核心操作员SubtleCrypto\n这是整个 Web Crypto API 中最重要、最核心的接口。所有的实际异步加解密、签名、密钥派生等操作都在这里执行。SubtleCrypto 提供的主要方法包括：\n- **核心作用：** 提供一系列底层、底层的异步密码学原语方法（全部返回 Promise）。\n- **核心方法（按功能分类）：**\n    - **加密与解密：** encrypt() \u002F decrypt()\n    - **签名与验证：** sign() \u002F verify()\n    - **哈希计算：** digest()\n    - **密钥生成：** generateKey()\n    - **密钥导入与导出：** importKey() \u002F exportKey()\n    - **密钥包装与解包：** wrapKey() \u002F unwrapKey()\n    - **密钥派生：** deriveKey() \u002F deriveBits()\n### 4.3 密钥实体CryptoKey\n该接口代表一个在浏览器中生成的、受保护的密钥对象。为了安全，浏览器的 SubtleCrypto 方法不直接操作纯文本的密钥字符串（如 \"my-secret-key\"），而是操作 CryptoKey 实例。\n- **核心作用：** 作为参数传递给 SubtleCrypto 的各项方法。\n- **核心属性（只读）：**\n   - **type：** 密钥类型（\"secret\" 对称加密、\"private\" 私钥、\"public\" 公钥）。\n   - **extractable：** 布尔值。如果为 false，该密钥在创建后绝对无法从内存中被导出或用 JS 打印出来，防止 XSS 攻击窃取密钥。\n   - **algorithm：** 绑定该密钥的算法对象（例如绑定的 AES-GCM 或 RSA-PSS 等参数）。\n   - **usages：** 该密钥被允许的用途数组（例如 ['encrypt', 'decrypt']）。\n### 4.4 非对称密钥对CryptoKeyPair\n当使用 RSA、ECDSA 等非对称加密算法时，crypto.subtle.generateKey() 返回的不是单个密钥，而是一个包含公钥和私钥的 CryptoKeyPair 对象。\n- **核心作用：** 容器对象，封装非对称密钥对。\n- **核心属性：**\n   - **publicKey：** 一个 CryptoKey 实例，代表公钥（用于加密或验证签名）。\n   - **privateKey：** 一个 CryptoKey 实例，代表私钥（用于解密或生成签名）。\n### 4.5 算法配置对象AlgorithmIdentifier\n虽然它在技术上是一个规范定义的字典对象（Dictionary），而不是传统的类或接口，但它是每个 API 都必传的灵魂参数。\n- **核心作用：** 用来指定具体的密码学算法名称以及其特有的初始化附加参数（如 IV、盐值、计数器等）。\n- **常见表现形式：** \n   - **哈希配置：** { name: \"SHA-256\" }\n   - **AES-GCM 配置：** { name: \"AES-GCM\", iv: Uint8Array, tagLength: 128 }\n   - **PBKDF2（密码派生）配置：** { name: \"PBKDF2\", salt: Uint8Array, iterations: 100000, hash: \"SHA-256\" }\n\n### 4.6 核心接口对比速查表\n| 接口名称 | 角色定位 | 非安全上下文（HTTP）使用规则 | 典型返回值 \u002F 用途 |\n| --- | --- | --- | --- |\n| Crypto | 全局大管家 | 是（部分方法受限） | 提供crypto.subtle入口、用于生成UUID |\n| SubtleCrypto | 核心工具库 | 否（必须在HTTPS\u002Flocalhost环境下使用） | 返回`Promise\u003CArrayBuffer>`，用于生成密文、哈希等加密核心操作 |\n| CryptoKey | 密钥黑盒 | 否 | 作为安全载体，直接传递给加解密函数使用 |\n| CryptoKeyPair | 密钥对容器 | 否 | 封装返回包含publicKey和privateKey的密钥对对象 |\n## 5. Web Crypto API支持的主流算法\n1. **对称加密（Symmetric）表**\n\n| 算法标识符 | 支持的操作 | 推荐搭配与技术参数 | 典型应用场景 |\n| --- | --- | --- | --- |\n| AES-GCM（强烈推荐） | encrypt、decrypt、generateKey、importKey、exportKey、wrapKey、unwrapKey | 密钥支持128\u002F192\u002F256位，IV推荐96位（12字节） | 传输数据加解密、本地缓存加密，自带防篡改认证标签 |\n| AES-CBC | encrypt、decrypt、generateKey、importKey、exportKey、wrapKey、unwrapKey | 密钥支持128\u002F192\u002F256位，IV固定128位（16字节） | 兼容老旧遗留系统的对称加解密业务 |\n| AES-CTR | encrypt、decrypt、generateKey、importKey、exportKey、wrapKey、unwrapKey | 计数器模式，支持流式处理 | 高频非对齐的流式数据区块加解密 |\n\n- AES-GCM 是当前Web场景下的首选方案，硬件加速支持度高，同时自带消息认证能力，无需额外实现哈希校验逻辑，安全性和开发效率都更优。\n- 不同算法的IV长度要求严格，比如AES-CBC必须使用固定16字节的IV，否则会直接触发解密失败。\n- 三款算法都完整覆盖了密钥生成、导入导出、包裹\u002F解包裹密钥的全流程操作，完全适配Web Crypto的全场景开发需求。\n\n2. **非对称加密算法（Asymmetric）表**\n\n| 算法标识符 | 支持的操作 | 推荐搭配与技术参数 | 典型应用场景 |\n| --- | --- | --- | --- |\n| RSA-OAEP（强烈推荐） | encrypt, decrypt, generateKey, importKey, exportKey, wrapKey, unwrapKey | 模数：2048, 4096 位哈希：推荐 SHA-256 | 混合加密体系中用于加密“对称密钥”，或加密超短敏感字段 |\n\nRSA-OAEP是Web场景下非对称加密的首选方案，它通过最优非对称加密填充机制，解决了传统“教科书RSA”的密文可延展性安全缺陷，在随机预言模型下可以抵御适应性选择密文攻击。由于非对称加密的特性，它不适合直接加密大体积数据，行业通用的最佳实践就是用它加密AES等对称密钥，再用对称算法加密实际业务数据，兼顾安全性和性能效率。\n\n3. **数字签名算法（Signature）表**\n\n| 算法名称 | 支持的操作 | 推荐搭配与技术参数| 典型应用场景 |\n| --- | --- | --- | --- |\n| ECDSA（强烈推荐） | sign、verify、generateKey、importKey、exportKey | 椭圆曲线：P-256、P-384、P-521 | 现代高并发身份验签、JWT签名、区块链节点验签 |\n| RSASSA-PKCS1-v1_5 | sign、verify、generateKey、importKey、exportKey | 模数≥2048位，哈希推荐SHA-256 | 传统企业级API签名、老旧版本JWT签名验签 |\n| RSA-PSS | sign、verify、generateKey、importKey、exportKey | RSA概率签名方案，密码学严谨度极高 | 金融级或司法级等高安全要求的数字签名场景 |\n| HMAC | sign、verify、generateKey、importKey、exportKey | 基于哈希的密钥消息认证码 | 前后端API请求防篡改哈希签名，如AWS签名机制 |\n\n- ECDSA 是Web场景下的首选签名方案，密钥体积小、验签速度快，非常适配高并发的现代Web服务。\n- RSA系列算法兼容性极强，适合需要对接老旧系统的业务场景，其中RSA-PSS的抗攻击能力远高于传统的RSASSA-PKCS1-v1_5。\n- HMAC实现成本低，是轻量API防篡改签名的最优选择。\n\n4. **单向哈希算法（Digest）表**\n\n| 算法名称 | 支持的操作 | 推荐搭配与技术参数 | 典型应用场景 |\n| --- | --- | --- | --- |\n| SHA-256 | digest | 无需密钥，单向不可逆 | 文件完整性校验（秒传场景）、数据防篡改指纹生成 |\n| SHA-384 | digest | 无需密钥，单向不可逆，安全强度高于SHA-256 | 高敏感数据校验、金融级业务的完整性验证 |\n| SHA-512 | digest | 无需密钥，单向不可逆，安全强度最高 | 司法级、军工级等高安全要求场景的哈希校验 |\n\n这三款同属SHA-2家族哈希算法，都具备单向不可逆的核心特性，无法从最终哈希值反推原始输入内容，仅用于生成数据唯一指纹，不能直接用于加解密还原原始数据。安全强度随输出位数提升而升高，同时计算性能也会小幅下降，普通业务场景优先选择SHA-256即可满足需求。\n\n5. **密钥派生算法（Key Derivation）表**\n\n| 算法名称 | 支持的操作 | 推荐搭配与技术参数 | 典型应用场景 |\n| --- | --- | --- | --- |\n| ECDH | deriveKey、deriveBits、generateKey、importKey、exportKey | 椭圆曲线：Diffie-Hellman | 两个终端在不安全信道中，安全地协商出一致的对称密钥 |\n| PBKDF2 | deriveKey、deriveBits、importKey | 基于密码的密钥派生，推荐迭代≥100000次 | 将用户输入的简短文明密码，转换为符合AES标准的高强度密钥 |\n| HKDF | deriveKey、deriveBits、importKey | 基于HMAC的密钥扩展算法 | 从一个基础主密钥，安全地扩展派生出多个子密钥 |\n\n- ECDH 是现代网络通信中密钥协商的核心方案，双方全程不需要直接传输最终的共享密钥，仅通过交换椭圆曲线公钥就能各自计算出完全一致的会话密钥，安全性极高。\n- PBKDF2 专门针对低熵的用户密码设计，通过高迭代次数大幅提升暴力破解的成本，是密码类密钥衍生的行业标准方案。\n- HKDF 面向已经具备高熵的主密钥材料，能高效地将单个主密钥拆分扩展为多个用途独立的子密钥，广泛应用于TLS等现代加密协议中。\n## 6. Web Crypto API 不支持的主流算法\n| 算法名称 | 不支持原因 | 替代实现方案 |\n| --- | --- | --- |\n| MD5 | 已被密码学界证实存在碰撞漏洞，安全性完全失效 | 引入crypto-js等第三方库实现或使用 SHA-256 |\n| SM2\u002FSM3\u002FSM4国密系列 | 浏览器国际标准未原生内置国密算法 | 引入sm-crypto等国密第三方库实现 |\n| ChaCha20-Poly1305\u002FEd25519 | 目前尚未完全普及到所有主流浏览器的 crypto.subtle 中 | 引入第三方加密库补充实现 |\n## 7. 常用操作示例\n以下是整理的 Web Crypto API (crypto.subtle) 最核心、最常用的四大标准代码示例（哈希生成、密钥生成、加密、解密）。需要特别提醒的是：所有 crypto.subtle 的操作都是异步的，都会返回一个 Promise，因此必须配合 async\u002Fawait 使用。\n### 7.1 对称加密AES-GCM示例\nAES-GCM (Galois\u002FCounter Mode) 是一种非常流行且强大的认证加密（AEAD, Authenticated Encryption with Associated Data） 模式。你可以把它理解为一种“二合一”的加密模式：它在高效加密数据的同时，还能确保数据没有被篡改。\n\nAES-GCM 是兼具高性能和高安全性的认证加密模式，特别适合在 Web Crypto API 这类原生环境中使用。它的安全性高度依赖于每次加密都使用唯一且不可预测的 IV。遵循这一核心原则，并妥善管理密钥、认真验证认证标签，就能为你的 Web 应用构建起一道坚实的数据安全防线。\n\n1. **计算文本哈希摘要 (Digest)**：这是最简单的操作，常用于生成文件的 SHA-256。它不需要密钥。\n```js\n\u002F**\n * 计算字符串的 SHA-256 哈希值\n * @param {string} text - 输入的文本\n * @returns {Promise\u003Cstring>} Hex 格式的哈希字符串\n *\u002F\nasync function generateHash(text) {\n    \u002F\u002F 1. 将文本编码为 Uint8Array 字节流\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    \u002F\u002F 2. 使用 crypto.subtle.digest 生成哈希（返回 ArrayBuffer）\n    const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n\n    \u002F\u002F 3. 将 ArrayBuffer 转换为常见的十六进制 (Hex) 字符串\n    const hashArray = Array.from(new Uint8Array(hashBuffer));\n    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n    \n    return hashHex;\n}\n\n\u002F\u002F 使用示例：\nconst sha256Result = await generateHash(\"Hello World\");\n```\n2. **生成对称加密密钥 (Generate Key)**：在加密数据之前，必须先通过 subtle 算法生成一个标准的加密密钥对象（CryptoKey）。这里以主流的 AES-GCM 算法为例：\n```js\n\u002F**\n * 生成一个用于 AES-GCM 加密的 256 位随机密钥\n * @returns {Promise\u003CCryptoKey>} 返回 CryptoKey 对象\n *\u002F\nasync function generateAESKey() {\n    return await crypto.subtle.generateKey(\n        {\n        name: 'AES-GCM',\n        length: 256 \u002F\u002F 密钥长度支持 128, 192, 或 256 位\n        },\n        false, \u002F\u002F 是否允许该密钥被导出（如果设为 false，密钥无法脱离当前浏览器，安全性极高）\n        ['encrypt', 'decrypt'] \u002F\u002F 该密钥被允许用来做什么操作\n    );\n}\n\n```\n3. **数据加密 Encrypt**：使用生成的密钥和 AES-GCM 算法对敏感文本进行加密。为了安全，AES-GCM 要求每次加密必须配合一个随机的初始化向量 (IV)。\n```js\n\u002F**\n * 对文本进行 AES-GCM 加密\n * @param {string} text - 待加密的明文\n * @param {CryptoKey} key - 上一步生成的 CryptoKey 对象\n * @returns {Promise\u003C{cipherText: ArrayBuffer, iv: Uint8Array}>} 返回密文和IV\n *\u002F\nasync function encryptText(text, key) {\n    const encoder = new TextEncoder();\n    const encodedText = encoder.encode(text);\n\n    \u002F\u002F AES-GCM 推荐使用 12位（96位）的高安全伪随机数作为 IV\n    \u002F\u002F 注意：这里使用的是外部同步方法 getRandomValues 生成随机数\n    const iv = crypto.getRandomValues(new Uint8Array(12));\n\n    \u002F\u002F 执行加密\n    const cipherText = await crypto.subtle.encrypt(\n        {\n        name: 'AES-GCM',\n        iv: iv \u002F\u002F 加密参数必须包含 IV\n        },\n        key, \u002F\u002F 密钥\n        encodedText \u002F\u002F 明文数据\n    );\n\n    \u002F\u002F 需要同时把密文和 IV 返回，因为解密时必须提供一模一样的 IV\n    return { cipherText, iv };\n}\n\n```\n4. **数据解密 Decrypt**：传入加密时使用的 CryptoKey、IV 和密文数组，将其还原为人类可读的明文字符串。\n```js\n\u002F**\n * 对 AES-GCM 密文进行解密\n * @param {ArrayBuffer} cipherText - 密文数据\n * @param {CryptoKey} key - 加密时使用的同一个密钥\n * @param {Uint8Array} iv - 加密时对应的随机 IV\n * @returns {Promise\u003Cstring>} 解密后的明文字符串\n *\u002F\nasync function decryptText(cipherText, key, iv) {\n    \u002F\u002F 执行解密\n    const decryptedBuffer = await crypto.subtle.decrypt(\n        {\n        name: 'AES-GCM',\n        iv: iv \u002F\u002F 必须传入与加密时完全相同的 IV\n        },\n        key,\n        cipherText\n    );\n\n    \u002F\u002F 将 ArrayBuffer 字节解码还原为字符串\n    const decoder = new TextDecoder();\n    return decoder.decode(decryptedBuffer);\n}\n```\n::tip\n如果要在项目中保存或传输生成的密钥和密文，通常需要通过 crypto.subtle.exportKey('raw', key) 将密钥导出成字节，再用 btoa() 或 Base64 方案 转换成可传输的字符串格式。\n::\n### 7.2 非对称加密RSA-OAEP示例\n非对称加密使用公钥加密、私钥解密，常用于非对称场景或加密对称密钥（如密钥交换）。这里使用最安全的 RSA-OAEP 算法。\n\n1. **生成 RSA 密钥对**\n```js\n\u002F**\n * 生成用于 RSA-OAEP 加解密的 2048 位密钥对\n * @returns {Promise\u003CCryptoKeyPair>} 返回包含公钥和私钥的对象\n *\u002F\nasync function generateRSAKeyPair() {\n    return await crypto.subtle.generateKey(\n        {\n            name: \"RSA-OAEP\",\n            modulusLength: 2048, \u002F\u002F 密钥长度，2048 位是目前的标准安全线\n            publicExponent: new Uint8Array([1, 0, 1]), \u002F\u002F 等于 65537，固定的密码学标准常数\n            hash: \"SHA-256\", \u002F\u002F 内部使用的哈希算法\n        },\n        true, \u002F\u002F 是否允许导出\n        [\"encrypt\", \"decrypt\"] \u002F\u002F 用途分配\n    );\n}\n```\n2. **使用公钥加密**\n```js\n\u002F**\n * 使用 RSA 公钥加密文本\n *\u002F\nasync function rsaEncrypt(text, publicKey) {\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    \u002F\u002F 返回 ArrayBuffer 格式的密文\n    return await crypto.subtle.encrypt(\n        { name: \"RSA-OAEP\" },\n        publicKey,\n        data\n    );\n}\n```\n3. **使用私钥解密**\n```js\n\u002F**\n * 使用 RSA 私钥解密文本\n *\u002F\nasync function rsaDecrypt(cipherText, privateKey) {\n    const decryptedBuffer = await crypto.subtle.decrypt(\n        { name: \"RSA-OAEP\" },\n        privateKey,\n        cipherText\n    );\n\n    const decoder = new TextDecoder();\n    return decoder.decode(decryptedBuffer);\n}\n```\n### 7.3  数字签名与验证 (RSASSA-PKCS1-v1_5)\n数字签名常用于防止数据被篡改并确认发送者身份。其逻辑是：私钥签名、公钥验证。这里使用最通用的 RSASSA-PKCS1-v1_5 算法。\n\n1. **生成数字签名的 RSA 密钥对**\n```js\n\u002F**\n * 生成用于数字签名的 RSA 密钥对\n *\u002F\nasync function generateSignKeyPair() {\n    return await crypto.subtle.generateKey(\n        {\n        name: \"RSASSA-PKCS1-v1_5\",\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: \"SHA-256\",\n        },\n        true,\n        [\"sign\", \"verify\"] \u002F\u002F 注意：这里的用途是签名和验证\n    );\n}\n```\n2. **使用私钥对数据生成签名**\n```js\n\n\u002F**\n * 使用私钥对数据生成签名\n * @returns {Promise\u003CArrayBuffer>} 返回签名摘要\n *\u002F\nasync function signData(text, privateKey) {\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    return await crypto.subtle.sign(\n        { name: \"RSASSA-PKCS1-v1_5\" },\n        privateKey,\n        data\n    );\n}\n```\n3. **公钥验证签名是否有效**\n```js\n\n\u002F**\n * 使用公钥验证签名是否有效\n * @returns {Promise\u003Cboolean>} 验证成功返回 true，被篡改则返回 false\n *\u002F\nasync function verifyData(text, signature, publicKey) {\n    const encoder = new TextEncoder();\n    const data = encoder.encode(text);\n\n    return await crypto.subtle.verify(\n        { name: \"RSASSA-PKCS1-v1_5\" },\n        publicKey,\n        signature, \u002F\u002F 之前生成的签名 ArrayBuffer\n        data \u002F\u002F 原始数据\n    );\n}\n```\n### 7.4 密钥的导入与导出 (Import \u002F Export)\n在实际业务中，密钥不能只存在于浏览器内存中，我们需要将其导出为字符串传给后端，或者将后端的字符串密钥导入为浏览器可识别的 CryptoKey 对象。\n\nWeb Crypto 支持多种格式，最常用的是 JWK (JSON Web Key) 格式（易读、跨平台）和 raw\u002Fspki\u002Fpkcs8（二进制字节流）。\n\n1. **导出密钥为 JSON (JWK 格式)**\n```js\n\u002F**\n * 将 CryptoKey 密钥对象导出为标准的 JSON 对象 (JWK)\n * @param {CryptoKey} key \n * @returns {Promise\u003CObject>} 可直接用于传输或 JSON.stringify 的对象\n *\u002F\nasync function exportKeyToJWK(key) {\n    return await crypto.subtle.exportKey(\"jwk\", key);\n}\n```\n2. **将 JSON 导入回密钥对象**\n```js\n\u002F**\n * 将 JWK 格式的 JSON 对象导入还原为可用的 AES-GCM 密钥\n * @param {Object} jwkObject \n * @returns {Promise\u003CCryptoKey>}\n *\u002F\nasync function importKeyFromJWK(jwkObject) {\n    return await crypto.subtle.importKey(\n        \"jwk\",\n        jwkObject,\n        { name: \"AES-GCM\" }, \u002F\u002F 必须指定对应的算法\n        true, \u002F\u002F 是否允许再次导出\n        [\"encrypt\", \"decrypt\"] \u002F\u002F 该密钥允许的用途\n    );\n}\n```\n## 8 避坑指南\n1. **陷阱一：IV 重复使用**\n- **问题：** 在 AES-GCM 等对称加密中，重复使用相同的初始化向量（IV）会导致密钥流重复，严重破坏加密安全性。\n- **正确做法：**\n    - 每次加密都使用 crypto.getRandomValues() 生成新的随机 IV\n    - AES-GCM 模式推荐使用 12 字节（非 16 字节！）的 IV\n    - 同一密钥下 IV 重复概率应低于 2⁻³²\n    - IV 不需要保密，但必须唯一——记得将 IV 与密文一起存储\n\n2. **陷阱二：密钥管理不当**\n- **常见错误：**\n    - 将密钥存储在 localStorage 或 Cookie 中\n    - 使用不安全的密钥传输方式\n    - 未设置适当的密钥使用限制\n- **安全建议：**\n    - 敏感会话密钥存储在 Web Worker 内存中\n    - 需要持久化的密钥使用 IndexedDB\n    - 使用 exportKey 和 importKey 安全地传输公钥\n\n3. **陷阱三：算法选择失误**\n- 不要使用 SHA-1 或 MD5 进行哈希\n- 不要使用 ECB 模式的 AES 加密\n- RSA 密钥长度至少 2048 位\n\n## 9 最佳实践\n1. **使用方式选型指南推荐**\n\n自 Node.js 19+ 起，Web Crypto API 已成为内置标准，可通过以下方式访问：\n```js\n\u002F\u002F 方式一：使用 globalThis\nconst { subtle } = globalThis.crypto;\n\n\u002F\u002F 方式二：使用 Node.js 模块\nconst { webcrypto } = require('node:crypto');\nconst { subtle } = webcrypto;\n```\n这两种方式虽然最终都能让你获取到 subtle 接口，但它们在设计哲学、模块机制、环境通用性以及底层依赖上有着本质的区别。\n\n| 对比维度 | 方式一：globalThis.crypto | 方式二：require('node:crypto') |\n| --- | --- | --- |\n| 本质定位 | Web标准API（与浏览器对齐） | Node.js原生模块API（特定服务端） |\n| 引入方式 | 零依赖，直接全局访问 | 需要显式通过require引入模块 |\n| 跨端通用性 | 极高（同构JavaScript） | 极低（只能在Node.js运行） |\n| 底层实现 | 浏览器内核（前端）\u002F Node V8底层封装（后端） | 强依赖Node.js内置的OpenSSL库 |\n| 执行上下文 | 必须在安全上下文（前端限制） | 服务端环境，无任何安全上下文限制 |\n\n**推荐使用\"globalThis.crypto\"的场景：** 正在编写纯前端项目、前后端全栈通用工具库（Isomorphic Lib），或者代码需要部署在 Cloudflare Workers、Vercel Edge 等现代边缘计算架构中。\n\n**推荐使用\"require('node:crypto')\"的场景：** 正在编写纯 Node.js 服务端项目（如大型后端服务、CLI 工具），且除了 Web Crypto 之外，还需要用到 Node.js 特有的高级安全特性（如流式加密大文件、硬件安全模块 HSM 交互等）。\n\n2. **使用细节建议**\n\nWeb Crypto API 是浏览器原生加密的黄金标准。它以零依赖、高性能、高安全性的优势，正在逐步取代各种第三方加密库。然而，正如其名 \"subtle\" 所警示的，加密是一个极其专业的领域，细微的错误就可能导致整个系统安全性完全失效。在使用 Web Crypto API 时：\n- 始终在 HTTPS 环境下使用\n- 选择正确的算法和参数（如 AES-GCM + 12 字节 IV）\n- 妥善管理密钥的生命周期\n\n## 10 加解密模板工具\n以下为您编写的这一套 AES-GCM 高级对称加密与解密工具函数。它做到了100% 零依赖、纯原生，通过 globalThis.crypto 实现完美同构。你不需要进行任何环境判断，同一段代码可以无缝运行在浏览器（安全上下文）、Node.js（19+）、Deno、Bun 以及边缘计算（如 Cloudflare Workers）中。\n\n为了解决数据传输与存储的问题，工具函数内部自动处理了 ArrayBuffer 与 Base64 字符串之间的相互转换。可以直接复制使用，代码如下：\n```ts [crypto.ts]\n\u002F**\n * 完美的同构 Web Crypto 安全加解密工具库 (TypeScript 箭头函数版本)\n * 适用环境：Browser (HTTPS), Node.js 19+, Deno, Bun, Cloudflare Workers\n *\u002F\n\n\u002F\u002F 1. 获取全局同构的 subtle 对象\nconst cryptoProvider = globalThis.crypto\nif (!cryptoProvider) {\n    throw new Error('当前运行环境不支持 Web Crypto API。请确保在 HTTPS、localhost 或现代 Node.js 19+ 环境下运行。')\n}\nconst subtle: SubtleCrypto = cryptoProvider.subtle\n\n\u002F**\n * 辅助函数：将 ArrayBuffer 转换为可传输的 Base64 字符串\n * 使用现代批处理写法apply，100% 消除严格模式下的 undefined 类型报错\n *\u002F\nconst bufferToBase64 = (buffer: ArrayBuffer): string => {\n    const bytes = new Uint8Array(buffer)\n    const binary = String.fromCharCode.apply(null, bytes as unknown as number[])\n    return btoa(binary)\n}\n\n\u002F**\n * 辅助函数：将 Base64 字符串还原为 ArrayBuffer\n * 使用 Array.from 映射，确保严格类型安全\n *\u002F\nconst base64ToBuffer = (base64: string): ArrayBuffer => {\n    const binaryString = atob(base64)\n    const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0))\n    return bytes.buffer\n}\n\n\u002F**\n * [同构] 生成 AES-GCM 256位加密密钥\n *\u002F\nexport const generateAESKey = async (): Promise\u003CCryptoKey> => {\n    return await subtle.generateKey(\n        { name: 'AES-GCM', length: 256 },\n        true, \u002F\u002F 允许导出\n        ['encrypt', 'decrypt']\n    )\n}\n\n\u002F**\n * [同构] 导出 CryptoKey 密钥为可传输的 Base64 字符串\n * @param cryptoKey - 浏览器或服务端生成的 CryptoKey 实体\n *\u002F\nexport const exportKeyToBase64 = async (cryptoKey: CryptoKey): Promise\u003Cstring> => {\n    const exported = await subtle.exportKey('raw', cryptoKey)\n    return bufferToBase64(exported)\n}\n\n\u002F**\n * [同构] 从 Base64 字符串导入还原为 CryptoKey 密钥对象\n * @param base64Key - Base64 格式的密钥字符串\n *\u002F\nexport const importKeyFromBase64 = async (base64Key: string): Promise\u003CCryptoKey> => {\n    const rawKeyBuffer = base64ToBuffer(base64Key)\n    return await subtle.importKey('raw', rawKeyBuffer, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'])\n}\n\n\u002F**\n * [同构] 使用 AES-GCM 算法加密字符串文本\n * @param text - 待加密的明文文本\n * @param key - CryptoKey 密钥对象\n * @returns 返回打包了 \"IV\" 和 \"密文\" 的统一 Base64 字符串\n *\u002F\nexport const encryptText = async (text: string, key: CryptoKey): Promise\u003Cstring> => {\n    const encoder = new TextEncoder()\n    const encodedText: Uint8Array\u003CArrayBuffer> = encoder.encode(text)\n    \u002F\u002F 生成 12 字节的密码学安全伪随机数作为 IV (AES-GCM 标准推荐)\n    const iv: Uint8Array\u003CArrayBuffer> = cryptoProvider.getRandomValues(new Uint8Array(12))\n    \u002F\u002F 执行加密，返回密文 ArrayBuffer\n    const cipherBuffer: ArrayBuffer = await subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, encodedText)\n    \u002F\u002F 将 IV（12字节）和密文拼接，方便单字符串传输\n    const combined = new Uint8Array(iv.length + cipherBuffer.byteLength)\n    combined.set(iv, 0)\n    combined.set(new Uint8Array(cipherBuffer), iv.length)\n    return bufferToBase64(combined.buffer)\n}\n\n\u002F**\n * [同构] 使用 AES-GCM 算法解密文本\n * @param combinedBase64 - 由 encryptText 生成的统一加密 Base64 字符串（内部含 IV）\n * @param key - 对应的 CryptoKey 密钥对象\n *\u002F\nexport const decryptText = async (combinedBase64: string, key: CryptoKey): Promise\u003Cstring> => {\n    const combinedBuffer = base64ToBuffer(combinedBase64)\n    const combinedBytes = new Uint8Array(combinedBuffer)\n    \u002F\u002F 从头部切出前 12 字节作为 IV\n    const iv: Uint8Array\u003CArrayBuffer> = combinedBytes.slice(0, 12)\n    \u002F\u002F 剩下的部分作为真正的密文\n    const cipherText: Uint8Array\u003CArrayBuffer> = combinedBytes.slice(12)\n    \u002F\u002F 执行解密\n    const decryptedBuffer: ArrayBuffer = await subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, cipherText.buffer)\n    const decoder = new TextDecoder()\n    return decoder.decode(decryptedBuffer)\n}\n```\nTS 测试脚本\n\n如果你在 Vite\u002FNuxt\u002FNext.js 项目或者使用了 ts-node \u002F bun \u002F deno 的后端环境中，可以直接引入并运行这段强类型的测试代码：\n\n```ts[test-crypto.ts]\nimport { decryptText, encryptText, exportKeyToBase64, generateAESKey, importKeyFromBase64 } from 'crypto'\n;(async () => {\n    try {\n        const rawData: string = '前后端全平台无缝传输的敏感数据 1234-abcd-TS-VERSION'\n        console.log('🔒 原始待加解密明文:', rawData)\n\n        \u002F\u002F ==========================================\n        \u002F\u002F 阶段一：在 A 端（例如 Node.js 后端）生成密钥并加密\n        \u002F\u002F ==========================================\n        const originalKey: CryptoKey = await generateAESKey()\n\n        \u002F\u002F 导出密钥为 Base64 字符串\n        const transportableKeyStr: string = await exportKeyToBase64(originalKey)\n        console.log('🔑 [传输中] 密钥已安全序列化为 Base64:', transportableKeyStr)\n\n        \u002F\u002F 对数据进行加密\n        const secureCipherText: string = await encryptText(rawData, originalKey)\n        console.log('📦 [传输中] 密文已安全打包（含IV）为 Base64:', secureCipherText)\n\n        \u002F\u002F ==========================================\n        \u002F\u002F 阶段二：在 B 端（例如现代 Web 浏览器前端）收到数据解密\n        \u002F\u002F ==========================================\n        console.log('\\n--- 数据已通过网络无缝抵达另一端 ---')\n\n        \u002F\u002F 1. 恢复强类型的 CryptoKey 实体\n        const receivedKey: CryptoKey = await importKeyFromBase64(transportableKeyStr)\n\n        \u002F\u002F 2. 还原数据内容\n        const decryptedOutput: string = await decryptText(secureCipherText, receivedKey)\n        console.log('🔓 [还原成功] 解密后的最终明文结果:', decryptedOutput)\n    } catch (error) {\n        console.error('❌ 密码学同构解密链路失败，错误原因: ', error)\n    }\n})()\n\n```",33.5,{"title":868,"description":879},{"loc":6551},"blog\u002F9.Web Crypto API浏览器原生加密的终极指南",[392],[6559],{"name":6560,"symbol":6561,"icon":6562,"id":392},"浏览器","web","mdi:web","2026-08-26 10:46:16",6031,"dFEAXKZPH39je0En-u1mE32EQFhpez4BM-ckLEv9l8w",1787994231063]