我正在编写一个系统,用户可以在其中编写内容(通过移动浏览器),并且“String”将使用用户选择的密码进行加密。由于经常使用unicode表情符号,因此也必须支持它们。
作为加密的lib,我选择CryptoJs - 这样加密就可以在设备上进行本地化。
目前,当我加密字符串并解密相同的字符串时,所有表情符号都会消失/被随机字符替换。
var key = "123";
var content = "secret text with an emoji, ";
var encrypted = aes_encrypt(key, content); //U2FsdGVkX19IOHIt+eRkaOcmNuZrc1rkU7JepL4iNdUknzhDaLOnSjYBCklTktSe
var decrypted = aes_decrypt(key, encrypted);//secret text with an emoji, Ø<ß®
我正在使用这样一对辅助函数:
function aes_encrypt(key, content){
var key_string = key + "";
var content_string = ascii_to_hex(content) + "";
var key_sha3 = sha3(key_string);
var encrypted = CryptoJS.AES.encrypt(content_string, key_sha3, {
mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.Iso10126});
return encrypted + "";
};
有人可以告诉我我做错了吗?
答案 0 :(得分:6)
警告:正确获取加密代码非常困难。在JavaScript中可能更难,在那里你经常缺乏对执行环境的控制,并且(如下所述)缺乏语言支持导致了不一致的约定。我没有对CryptoJS库进行足够的研究来了解它的设计或安全性,或者是否在这种情况下安全地使用它。
如果没有专业审核,请不要依赖此代码中的任何一项。
在JavaScript中使用加密代码时的一个常见问题是,没有内置的方法来表示二进制数据。这已在现代引擎中解决(浏览器中的类型为Blobs
和TypedArrays
,Node.js中为Buffers
),但仍有许多代码无法利用这是出于历史或兼容性原因。
如果没有这些内置类型,一个常见的约定(由内置的atob
和btoa
函数使用)是使用内置字符串类型来保存二进制数据。 JavaScript字符串实际上是一个双字节值列表(通常包含UCS-2 / UTF-16编码的Unicode字符)。想要存储二进制数据的用户通常只使用较低的字节,完全忽略较高的字节。
如果您只处理与ASCII兼容的数据,那么在使用这样的代码时可能会忽略这些细节(即事情会起作用 - 但可能会产生微妙的安全后果)。这是因为编码为ASCII的文本看起来与编码为UTF-16的文本相同,并且高字节被剥离。但是当你冒险超越它时,你需要做一些编码。
除了使用真正的二进制类型之外,最正确的做法是获取输入的字符串,将其编码为UTF-8,并将该数据放在输出字符串的低字节中。但是,JavaScript不提供内置函数来执行此操作。作为粗略但简单的替代方法,the encodeURIComponent
function会将任何有效的unicode字符串编码为基于UTF-8的完全URL安全字符表示,这些字符都是ASCII兼容的。对于您的代码,这意味着这样的事情:
var key = "123";
var content = "secret text with an emoji, ";
var encrypted = aes_encrypt(key, encodeURIComponent(content));
var decrypted = decodeURIComponent(aes_decrypt(key, encrypted));
如果您有许多非URL安全字符,这可能会导致编码数据远大于必要的数据,但它应该是安全的。此外,encodeURIComponent
显然会为包含“未配对的代理字符”的字符串抛出错误。我不认为这些应该出现在普通的输入中,但有人可以制作它们。
我希望在CryptoJS中有一种更正确的方法来处理这样的事情,但我不知道它。如果您计划将此代码部署以供公众使用,请考虑进一步研究。
答案 1 :(得分:3)
CryptoJS能够将UTF-8编码的字符串转换为自己的二进制数据格式(WordArray
)。这可以通过var binData = CryptoJS.enc.Utf8.parse(string);
:
var password = "123";
var content = "secret text with an emoji, ";
inContent.innerHTML = content;
var encrypted = aes_encrypt(password, content);
var decrypted = aes_decrypt(password, encrypted);
out.innerHTML = decrypted;
function aes_encrypt(password, content) {
return CryptoJS.AES.encrypt(content, password).toString();
}
function aes_decrypt(password, encrypted) {
return CryptoJS.AES.decrypt(encrypted, password).toString(CryptoJS.enc.Utf8);
}
#inContent { color: blue; }
#out { color: red; }
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
<div>in: <span id="inContent"></span></div>
<div>out: <span id="out"></span></div>
这是有效的,因为如果将字符串作为内容传递给CryptoJS.AES.encrypt
,那么它将自动解析为UTF-8,但您需要在解密后将其转换回UTF-8。这是通过.toString(CryptoJS.enc.Utf8)
完成的。
此代码仅演示了CryptoJS已经很好地处理UTF-8。这不安全,因为
单次迭代的MD5用于密码的密钥派生。您需要使用CryptoJS提供的类似PBKDF2的东西。 (不要忘记每次都使用随机IV。它不必是秘密的,所以你可以将它与密文一起发送。)
密文未经过身份验证,因此无法检测(恶意)加密数据的操作。最好对您的密文进行身份验证,以便像padding oracle attack这样的攻击是不可能的。这可以通过GCM或EAX等经过身份验证的模式来完成,也可以使用具有强MAC的encrypt-then-MAC方案来完成,如CryptoJS提供的HMAC-SHA256。