2016-02-19 148 views
4

我正在编写一个系统,用户可以在其中写入某些内容(通过。手机浏览器),并且将使用用户选择的密码对“字符串”进行加密。由于经常使用unicode emojis,因此它们也必须得到支持。使用CryptoJS破坏unicode表情符号的AES加密

作为加密的lib,我选择CryptoJs - 这样加密可以在设备本地完成。

目前,当我加密一个字符串,并解密相同的刺痛,所有emojis消失/被替换为随机字符。

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 + ""; 
}; 

任何人能告诉我什么,我做错了什么?

+2

你能提供一个链接到你正在使用的加密库吗?这里的根本问题是加密算法对二进制数据进行操作,而JavaScript字符串则不是。 JavaScript字符串中的每个字符都是两个字节。将JavaScript字符串视为二进制数据的加密代码通常会忽略较高字节,并假定较低字节用于存储数据。表情符号要求数据丢失的高位字节。您需要以某种形式将字符串字符数据显式编码为UTF-8。 hacky的解决方案是在解码之前/之后使用encode/decodeURIComponent。 –

+0

@JeremyBanks我从谷歌代码(https://code.google.com/archive/p/crypto-js/)使用原始库的副本。 –

+0

是你写的aes_encrypt吗? – alexandergs

回答

6

警告:要正确得到加密代码是非常困难的。在JavaScript中可能会更难,因为你经常缺乏对执行环境的控制,并且(如下所述)缺少语言支持导致了不一致的约定。我没有对CryptoJS库进行足够的研究,以了解其设计或安全性,或者它是否在这种情况下安全使用。

请不要依赖任何此代码在没有专业审计的情况下确保安全。

在JavaScript中使用加密代码时的一个常见问题是,没有内置的方法来表示二进制数据。这在现代引擎中已得到解决(浏览器中的类型为BlobsTypedArrays,Node.js中的类型为Buffers),但仍有很多代码因历史或兼容性原因而没有利用此功能。

如果没有这些内置类型,一个常见的约定(内置的atob和​​函数使用)是使用内置字符串类型来保存二进制数据。 JavaScript字符串实际上是一个双字节值列表(通常包含UCS-2/UTF-16编码的Unicode字符)。希望存储二进制数据的用户通常只使用较低的字节,而完全忽略较高的字节。

如果您只处理与ASCII兼容的数据,那么在使用这样的代码时(即事情可行 - 但可能存在微妙的安全后果),您可能会忽略这些细节。这是因为编码为ASCII的文本看起来与编码为UTF-16的文本相同,并且高位字节被剥离。但是当你冒险超越这个,你需要做一些编码。

最正确的事(除了使用实际二进制类型)做。将采取的字符的输入字符串,它编码为UTF-8,并把该数据在输出串的下字节。但是,JavaScript不提供内置功能。作为一个简单而粗略的选择,the encodeURIComponent function将把任何有效的unicode字符串编码为一个完全符合URL安全的字符的UTF-8表示形式,它们都是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中有一种更正确的方式来处理像这样的事情,但我没有意识到这一点。如果您打算部署此代码供公众使用,请考虑进一步研究。

+0

这是一个快速,粗略的答案,但我觉得最好是有一些东西,而不是留下评论中散布的信息。我不是密码专家,这不是一个可靠的密码学建议,<插入十多个免责声明,请咨询专家>等。 –

+0

a)实际上不需要'encodeURIComponent',因为CryptoJS能够处理UTF -8本身。 b)由于OP已经使用'ascii_to_hex()'加倍了大小,因此大小的增加可以忽略不计。 c)你说得对,加密权很难。我主要给出需要完成的内容的文本描述(和链接),而不是显示适当的代码,因为它会使帖子长度爆炸。 –

2

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(如CryptoJS提供的HMAC-SHA256)的encrypt-then-MAC方案完成。

+0

感谢您提供更明智的解释! –