前言
想象一下,你有一本珍贵的日记,里面写满了秘密和故事,但你又想把它放在互联网上,让世界都看到——当然是在你设定的“密码锁”保护之下。StatiCrypt 就是那个能帮你把日记变成只有你知道如何打开的秘密宝箱的神奇工具。它不仅能让你的 HTML 文件穿上一件防弹背心,还能确保只有输入正确密码的人才能一窥究竟。而且,最棒的是,这一切的魔法都发生在没有任何后端支持的纯客户端世界里。
网页保险箱:如何用 StatiCrypt 打造坚不可摧的数字堡垒
安全地加密并使用密码保护您的公共静态 HTML 文件的内容,以便在不依赖后端的情况下在浏览器中解密,从而可以通过像 Netlify、GitHub Pages 等静态托管服务来提供内容(参见实时示例[1])。
StatiCrypt 使用 AES-256 和 WebCrypto 来加密您的 HTML 文件,并使用您提供的长密码,然后返回一个静态 HTML 页面,显示密码提示,您现在可以安全地将该页面上传到任何地方,页面包含您的加密内容,解密过程在客户端的 JavaScript 中进行(查看它的工作原理[2]的详细信息)。
👉️ 您可以在robinmoisson.github.io/staticrypt[3]上在线在浏览器中(客户端)加密文件,或者使用 CLI 在终端或构建过程中进行加密。
StatiCrypt 的工作原理
那么,您如何在没有后端的情况下对 HTML 进行密码保护呢?
StatiCrypt 使用 WebCrypto 生成一个静态的、受密码保护的页面,该页面可以在浏览器中解密。然后,您只需将生成的页面发送或上传到提供静态内容的地方(例如 github 页面),就完成了:页面将提示用户输入密码,JavaScript 将解密并加载您的 HTML,所有操作都在浏览器中完成。
所以它基本上加密了您的页面,并以用户友好的方式将所有内容放入新文件中以输入密码。
CLI
迁移: v3 带来了许多改进,更清晰的 CLI 和比 v2 更简单的password_template
。参见v2 到 v3 的迁移指南[4]。v3 使用 WebCrypto,它只在 HTTPS 或 localhost 上下文中可用,因此如果您需要在 HTTP 中使用它,您需要使用 v2。
安装
StatiCrypt 可以通过 npm 作为 CLI 使用,安装命令如下:
npm install staticrypt
然后您可以使用npx staticrypt ...
来运行它。您也可以使用npm install -g staticrypt
全局安装,然后从任何地方调用staticrypt ...
。
示例
这些示例将在当前目录中创建一个
.staticrypt.json
文件(这是为什么[5])。这个文件不是秘密,您不需要保护它。您可以通过设置--config
标志为false
来阻止创建这个文件(一个字符串)。
加密文件
加密test.html
并创建一个encrypted/test.html
文件(使用-d my_directory
更改输出目录):
# 这将提示您输入密码,密码不会保留在您的终端命令历史记录中
staticrypt test.html
# 您也可以将密码作为参数传递
staticrypt test.html -p <长密码>
使用环境变量中的密码加密文件
将您的长密码设置在STATICRYPT_PASSWORD
环境变量中(支持`.env`文件[6]):
# 密码在STATICRYPT_PASSWORD环境变量中,您不会被提示
staticrypt test.html
一次性加密多个 HTML 文件
这将把 HTML 文件放在您运行staticrypt
命令时创建的encrypted
目录中。非 HTML 文件将从输入目录原样复制,因此您可以轻松地用加密目录覆盖它,如果您愿意的话。
# 这将加密test_A.html和test_B.html
staticrypt test_A.html test_B.html
# => 加密文件位于encrypted/test_A.html和encrypted/test_B.html
# 您也可以使用`-r`标志递归加密目录中的所有文件
staticrypt dir_to_encrypt -r
# => 加密文件位于encrypted/dir_to_encrypt/...
# 如果您不希望在输出路径中包含目录名称,可以使用`dir_to_encrypt/*`代替。`-r`也将包括潜在的子目录
staticrypt dir_to_encrypt/* -r
# => 加密文件位于encrypted/...
用加密文件替换文件夹中的所有文件
# 'dir_to_encrypt/*'作为参数将选择目录中的所有文件('-r'递归),
# 而'-d dir_to_encrypt'将把它们放在同一个目录中,覆盖文件
staticrypt dir_to_encrypt/* -r -d dir_to_encrypt
获取可分享的自动解密链接
链接包含哈希密码,将自动解密文件 - 您可以包含您的文件 URL 或留空。(⚠️ 您应该保留您的.staticrypt.json
,以便盐值在每次加密时都相同,或者重新加密将使链接失效[7]):
# 您也可以在不指定URL的情况下传递'--share'来获取`#staticrypt_pwd=...`
staticrypt test.html --share https://example.com/encrypted.html
# => https://example.com/encrypted.html#staticrypt_pwd=5bfbf1343c7257cd7be23ecd74bb37fa2c76d041042654f358b6255baeab898f
# 添加`--share-remember`以自动启用"记住我" - 如果您想在发送一个链接时自动解密多个页面,这很有用(您也可以只附加'&remember_me')
staticrypt test.html --share --share-remember
# => #staticrypt_pwd=5bfbf1343c7257cd7be23ecd74bb37fa2c76d041042654f358b6255baeab898f&remember_me
在 CI 或构建步骤中固定盐值
如果您希望"记住我"或共享功能在多个页面或多次连续部署中工作,盐值需要保持不变(见为什么[8])。如果您在 CI 步骤中运行 StatiCrypt,您可以通过两种方式固定盐值:
要么提交
.staticrypt.json
配置文件 - 您可以在本地机器上生成一个随机盐和配置文件:staticrypt --salt
或在 CI 脚本中的加密命令中硬编码盐值:
staticrypt test.html --salt 12345678901234567890123456789012
参见这个社区项目,了解如何在 CI 构建步骤中使用 StatiCrypt:a-nau/password-protected-website-template[9]
自定义密码提示
自定义 HTML,使加密页面与您的风格相匹配(参见常见问题解答[10]以获取完整的自定义模板):
# 使用您自己的自定义模板
staticrypt test.html -t my/own/password_template.html
# 或者自定义默认模板
staticrypt test.html \
--template-color-primary "#fd45a4" \
--template-title "我的自定义标题" \
--template-instructions "要解锁此文件,您应该..." \
# ...
从 CLI 解密文件
通过包含--decrypt
标志,直接从 CLI 解密您之前用 StatiCrypt 加密的文件。(所以如果您愿意,您可以只保留加密文件)。-r|--recursive
标志和输出-d|--directory
选项的工作方式与加密时相同(输出目录的默认名称为decrypted
):
staticrypt encrypted/test.html --decrypt
# => 解密文件位于decrypted/test.html
CLI 参考
如果设置了环境变量STATICRYPT_PASSWORD
或.env
文件中的密码,密码参数是可选的。
用法:staticrypt <filename> [<filename> ...] [选项]
选项:
--help 显示帮助 [布尔值]
--version 显示版本号 [布尔值]
-c, --config 配置文件的路径。设置为"false"以禁用。[字符串] [默认: ".staticrypt.json"]
-d, --directory 生成文件将被保存的目录的名称。如果设置了'--decrypt'标志,默认将是'decrypted'。
[字符串] [默认: "encrypted"]
--decrypt 包含此标志以解密文件,而不是加密。 [布尔值] [默认: false]
-p, --password 加密文件的密码。留空以提示输入。如果环境变量中设置了STATICRYPT_PASSWORD,我们将使用该值。 [字符串] [默认: null]
-r, --recursive 是否递归加密输入目录。 [布尔值] [默认: false]
--remember 整数:"记住我"复选框的有效期(天数),该复选框将在用户输入密码时将(加盐 + 哈希)密码保存在localStorage中。设置为"false"以隐藏该框。 默认:"0",无过期。 [默认: 0]
-s, --salt 生成配置文件或手动设置盐值。
传递一个32字符长的十六进制字符串作为盐值,或留空以生成、显示并保存到配置文件中的随机盐。这不会覆盖现有的配置文件。 [字符串]
--share 获取包含您哈希密码的链接,该链接将自动解密页面。将您的URL作为值传递以附加
"#staticrypt_pwd=<hashed_pwd>",或留空以显示要附加的哈希。 [字符串]
--share-remember 共享链接是否应该自动启用'记住我'。 [布尔值] [默认: false]
--short 隐藏"短密码"警告。
[布尔值] [默认: false]
-t, --template 带有密码提示的自定义HTML模板的路径。
[字符串] [默认: "/code/staticrypt/lib/password_template.html"]
--template-button 解密按钮使用的标签。默认:
"DECRYPT". [字符串] [默认: "DECRYPT"]
--template-color-primary 主色(按钮...)
[字符串] [默认: "#4CAF50"]
--template-color-secondary 次色(页面背景...)
[字符串] [默认: "#76B852"]
--template-instructions 向用户显示的特殊说明。
[字符串] [默认: ""]
--template-error 输入错误密码时显示的错误消息。
[字符串] [默认: "Bad password!"]
--template-placeholder 密码输入使用的占位符。
[字符串] [默认: "Password"]
--template-remember "记住我"复选框使用的标签。
[字符串] [默认: "Remember me"]
--template-title 输出HTML页面的标题。
[字符串] [默认: "Protected Page"]
--template-toggle-hide 切换密码可见性的备选文本 - "隐藏"操作。
[字符串] [默认: "Hide password"]
--template-toggle-show 切换密码可见性的备选文本 - "显示"操作。
[字符串] [默认: "Show password"]
常见问题解答
它安全吗?
简单回答:您的文件内容已使用 AES-256 加密,这是一种流行且强大的加密算法。您现在可以将其上传到任何公共地方,没有人能够在没有密码的情况下阅读它。因此,如果您使用了一个长而强大的密码,那么应该是安全的。
更长的回答:实际安全性取决于许多因素,以及您想要保护的威胁模型。因为您的完整加密文件可以在客户端访问,暴力破解/字典攻击可以快速进行:使用一个长而不寻常的密码。我们推荐使用 16 个或更多的字母数字字符,Bitwarden[11] 是一个很棒的开源密码管理器,如果您还没有的话。
在技术方面:我们使用 AES CBC 模式(参见为什么这种模式适用于 StatiCrypt 的讨论#19[12])和 600k PBKDF2-SHA256 迭代来减缓暴力破解攻击(这是 OWASP 推荐的数字[13] - 阅读有关这个数字和 StatiCrypt 安全模型的详细报告#159[14])。
透明度声明: 我不是密码学家。我尽力确保实现正确,听取反馈,并在管理 StatiCrypt 时保持透明。但请根据您的威胁模型进行相应调整:如果您是处于风险中的活动家或有非常敏感的加密资产需要保护,您可能想要使用其他方法。
我可以自定义密码提示吗?
可以!只需复制lib/password_template.html
,根据您风格进行修改,并使用-t path/to/my/file.html
标志指向您的模板文件。
小心不要破坏加密 JavaScript 部分,StatiCrypt 替换的变量格式如下:/*[|variable|]*/0
。不要遗漏末尾的0
,这种奇怪的语法是为了避免与其他模板引擎冲突,同时仍然被解析器作为有效的 JS 读取,以便我们可以使用模板文件上的自动格式化。
我可以支持多个用户使用不同密码吗?
目前,您只能为每个页面使用一个密码(尽管在#158[15]中反思了支持使用多个不同密码进行解密)。如果您想支持多个用户,以便您可以单独使密码失效,当前推荐的方法如下:
制作一个脚本,使用不同密码和不同输出文件夹加密您的文件
staticrypt test.html -p <john-password> -d john
...向每个用户发送带有他们密码的他们文件夹的链接:
https://example.com/john/test.html
在某种程度上,用户名输入变成了https://example.com/<username>
URL 中的文件夹,密码输入是 HTML 表单。然后您可以在脚本中更改密码并再次运行它来使单个密码失效。
为什么 StatiCrypt 在 HTTP 中不起作用?
从 3.x 版本开始,StatiCrypt 只使用浏览器 WebCrypto API,这使它更安全,但只能在 HTTPS 或 localhost 上使用。如果您需要在 HTTP 中使用它,您可以使用 2.x 版本,它提供 CryptoJS 引擎作为选项,将无处不在。
StatiCrypt 为什么要创建配置文件?
"记住我"功能将用户密码以哈希和加盐的形式存储在浏览器的 LocalStorage 中,因此每次加密时盐值需要保持相同,否则用户在您再次加密页面时会被注销。配置文件是存储盐值的方法,以便您不必记住它并手动传递。
在决定使用什么盐时,StatiCrypt 首先会查找--salt
标志,然后尝试从配置文件中获取盐值,如果仍然找不到盐值,它将生成一个随机的。然后它将盐值保存在配置文件中。
如果您不希望 StatiCrypt 创建或使用配置文件,可以设置--config false
以禁用它。
盐不是秘密(它在加密文件上公开可见),所以您不必担心隐藏配置文件。如果您作为 CI 步骤的一部分进行加密,您可以提交.staticrypt.json
文件,以便它对您的构建服务器可访问。
"记住我"复选框如何工作?
CLI 默认会在密码提示上添加一个"记住我"复选框(使用--remember false
来禁用)。如果用户选中它,(加盐 + 哈希)密码将存储在他们浏览器的 LocalStorage 中,当他们再次访问页面时,页面将尝试自动解密。
如果没有提供值,存储的密码不会过期,您也可以使用--remember NUMBER_OF_DAYS
给出一个以天为单位的值,以确定存储值应保留多长时间。如果用户在过期日期后重新连接到页面,存储的值将被清除。
"注销"
您可以随时通过在 URL 片段中附加staticrypt_logout
来清除 LocalStorage 中的 StatiCrypt 值(有效地"注销")。
加密多个页面
这允许在单个域上加密多个页面:如果您选中"记住我",您将只需要输入一次密码,然后该域上的所有页面将自动解密它们的内容。因为哈希值存储在浏览器的 LocalStorage 中,这只有在所有页面都在同一域名下时才有效。
"记住我"复选框安全吗?
如果存储在浏览器中的值被泄露,攻击者可以解密页面,但由于它是加盐和哈希存储的,这应该仍然可以防止密码重用攻击,如果您在其他网站上使用了该密码(当然,请无论如何使用一个长而独特的密码)。
我可以移除"记住我"复选框吗?
如果您不希望包含复选框,可以设置--remember false
标志来禁用它。
iCrypt 安全模型有一般的想法或反馈,它们非常受欢迎,如果不是太敏感,请随时开启一个问题。之前讨论安全的地方有#19[16] 和 #159[17]。
贡献指南
源代码映射
cli/
- 发布到 NPM 的命令行界面。example/
- 示例加密文件,用作公共网站的示例和手动测试。lib/
- 跨 www 和 cli 共享的文件。scripts/
- 构建项目的便捷脚本。index.html
- 托管在https://robinmoisson.github.io/staticrypt的浏览器内加密网站的根目录。 保留在仓库的根目录,便于部署到 GitHub Pages。
构建
当编辑 StatiCrypt 逻辑时,我们希望将更改同步到浏览器版本、CLI 和示例文件,以便它们都使用新的逻辑。为此,请运行:
npm install
npm run build
社区和替代品
基于 StatiCrypt 的教程和项目
使用 Github Pages 托管加密单页面网站的模板: a-nau/password-protected-website-template[18] 是如何在 Github Pages 上构建受保护页面的演示,与 Github Actions 集成。
StatiCrypt 的替代品
MaxLaumeister/PageCrypt[19] 是具有类似功能但风格不同的项目(我认为它在 StatiCrypt 之前就创建了)。
github 地址
https://github.com/robinmoisson/staticrypt
实时示例: https://robinmoisson.github.io/staticrypt/example/encrypted/example.html
[2]它的工作原理: #how-staticrypt-works
[3]robinmoisson.github.io/staticrypt: https://robinmoisson.github.io/staticrypt
[4]v2 到 v3 的迁移指南: MIGRATING.md
[5]这是为什么: #why-does-staticrypt-create-a-config-file
[6].env
文件: https://www.npmjs.com/package/dotenv#usage
使链接失效: #why-does-staticrypt-create-a-config-file
[8]见为什么: https://github.com/robinmoisson/staticrypt#why-does-staticrypt-create-a-config-file
[9]a-nau/password-protected-website-template: https://github.com/a-nau/password-protected-website-template
[10]常见问题解答: #can-i-customize-the-password-prompt
[11]Bitwarden: https://bitwarden.com/
[12]#19: https://github.com/robinmoisson/staticrypt/issues/19
[13]数字: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
[14]#159: https://github.com/robinmoisson/staticrypt/issues/159
[15]#158: https://github.com/robinmoisson/staticrypt/issues/158
[16]#19: https://github.com/robinmoisson/staticrypt/issues/19
[17]#159: https://github.com/robinmoisson/staticrypt/issues/159
[18]a-nau/password-protected-website-template: https://github.com/a-nau/password-protected-website-template
[19]MaxLaumeister/PageCrypt: https://github.com/MaxLaumeister/PageCrypt