本文将简单介绍一种静态界面需要加密的情景,并给出使用 AES 加密算法的一种实现。
静态内容的加密
有些时候,我们可能需要在页面上添加一些需要密码才能查看到内容。但对于一个静态页面而言,页面源代码初始时便已经由服务器发送到了设备上,所以仅仅只通过增加一个输入密码框来“欺骗”用户并不能很好地起到保密的作用。鉴于此,我们得考虑静态页面源代码中相关内容就已经被加密,而用户通过自己的设备输入密钥,运算,来获取原有的明文。
已知明文攻击
如果仅仅发送密文,需要用户自己输入密钥解密,这种方式理论上已经比较安全了,但我们还希望在前端增加一个密码判定,能够判断用户是否使用了正确的密钥,只有密钥正确才会将解密的明文放到页面上,否则解密出的“明文”没有意义,应该不予反应。
在这种情况下,我们可以考虑额外增加一组对应的试探明文和密文,用户使用密钥时,先利用这个密钥解密试探密文,如果解密出了试探明文,那么再继续解密密文,得到正确的明文。
这样做虽然使得界面更加的人性化,但是却额外增加了一定的风险:攻击者可以额外得到一组明文和密文。就密码学而言,这称为 已知明文攻击 ,因为攻击者可以根据这一组明文和密文来猜测正确的密钥,所以在加密时,我们选择的算法应当是能防范已知明文攻击的。
不过对于现代的加密算法而言,都不需要担心这个问题。
本篇文章中,我们使用 AES 加密算法来解决我们的需求。这是一种对称的加密算法,也就是加密和解密共用同一个密钥。
AES 加密的使用
引入 AES 加密
想要在静态页面使用 AES 加密,可以直接使用谷歌开发的一个纯 JavaScript 的加密算法类库: crypto-js 。
引入:
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.min.js"
></script>
再查阅该 JS 的 AES 相关 API:
var CryptoJS = require("crypto-js");
// Encrypt
var ciphertext = CryptoJS.AES.encrypt(
"my message",
"secret key 123"
).toString();
// Decrypt
var bytes = CryptoJS.AES.decrypt(ciphertext, "secret key 123");
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log(originalText); // 'my message'
还是非常简明的。
静态页面中的使用
首先看一下最后成品:
(见 原博客,现在搬迁到 Vue3 + TS 了重写有点麻烦)
随后我们再逐部分介绍。
这个成品的正确密钥可以在文末获取(括弧笑
由于懒得写 CSS 所以很丑
预加密处理
首先需要在提前对试探明文和明文进行加密。
可以现在任意网页中引入 crypto-js (比如本页面),然后根据 API 说明,进行加密。控制台中输入:
temp = CryptoJS.AES.encrypt("明文", "密钥").toString();
然后输入 temp ,键入回车后即可查看对应的密文
举例:
temp = CryptoJS.AES.encrypt("stDkxwE", "password").toString();
//获取本文的试探密文
对于明文部分,我们可以选择使用 HTML 文本,这样只要后面 JavaScript 采用 innerHTML 方法替换文本就可以保留需要的格式。
界面部分
<div style="border: 2px solid black;padding: 10px;">
<div data-origin="stDkxwE" data-now="U2FsdGVkX19y5/AiAAtZy2R6PQ57zsSZgv3aghSMI4A=" style="display: none"
class="cryptoText">
U2FsdGVkX1+tZBv8Pm1WWqR23FPMqHVtrqgXVMcOnLGHw4vbaZlfj01cw8UIzH4Oo2U38Q7a2ka+alOGezBznPeyIfTGOaKFdilDO6vbTs1g+3GUgP3aX81Hnb2js5Y3wv75yRVIWQgk9mrOoICBsD2GAWpPHzTYADMyautvG90=
</div>
请输入密码查看隐藏内容:<input type="password">
<input type="submit" onclick="crypto(this)"></button>
</div>
整体加密部分采用一个黑色边框包裹起来。
初始时,密文部分 display: none ,仅显示输入框。密文部分还包含了一组试探的明文和密文,分别在 data-origin 和 data-now 。
提交按钮会传入 this 参数,指示当前按钮。
JS 逻辑
function crypto(sub) {
const e = sub.parentNode.firstElementChild; //密文部分
const key = sub.previousElementSibling.value; //用户输入的密钥
const origin = e.getAttribute("data-origin"); //试探明文
const now = e.getAttribute("data-now"); //试探密文
const res = CryptoJS.AES.decrypt(now, key).toString(CryptoJS.enc.Utf8);
if (res == origin) {
//如果密钥正确
const t = e.innerHTML.replace(/\s/g, "");
sub.parentNode.innerHTML = CryptoJS.AES.decrypt(t, key).toString(
CryptoJS.enc.Utf8
);
} else {
alert("密码错误!");
}
}
比较简单,基本看注释就行()
e.innerHTML.replace(/\s/g, "") 需要注意一下,是通过正则表达式去除了可能的空格和换行的干扰。
到这里,这个简易的加密系统就已经完成了。
本文中的成品正确的密钥为: password 。