2016-02-19 142 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

感謝您提供更明智的解釋! –