一個以ajax請求為主的應用,資料傳輸加密的解決方案
首先是金鑰交換的過程,Diffie-Hellman金鑰交換演算法參考維基百科的文件:
http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
client端js語言,服務端php語言 用DH金鑰交換演算法交換金鑰。
var g = "2"; var p = "106025087133488299239129351247929016326438167258746469805890028339770628303789813787064911279666129"; function doStaff() { var big_a = randBigInt(100); var big_p = str2bigInt(p, 10, 0); var big_g = str2bigInt(g, 10, 0); var A = powMod(big_g, big_a, big_p); var str_A = bigInt2str(A, 10); var B; var secret; $.ajax({ url: 'server.php', type: 'GET', async: false, data: {"A":str_A}, cache: false, timeout: 5 * 1000, dataType: 'json', success: function(data, status, xhr) { B = str2bigInt(data.B, 10, 0); }, error: function() { alert(2); } }); secret = powMod(B, big_a, big_p); secret = bigInt2str(secret, 10);
上邊的程式碼中,最後的到的secret,就是最終和服務端協商一致的金鑰(這是一個很多位數字的字串,我們的金鑰使用16位元組,那麼我們可以考慮對它md5,作為對稱加密的金鑰)。
上述程式碼中big int相關的js,直接使用的一個開源的bigint.js(js程式碼有不開源的嗎?^_^) 。 參見這裡:http://leemon.com/crypto/BigInt.js
服務端php程式碼:
<?php $g = "2"; $p = "106025087133488299239129351247929016326438167258746469805890028339770628303789813787064911279666129"; $b = "86410430023"; // TODO: change this to a random generated number. $A = $_REQUEST['A']; $B = bcpowmod($g, $b, $p); $secret = bcpowmod($A, $b, $p); echo '{"B":"' . $B .'"}'; ?>
可見我們採用固定的g和p,這2個變數是公開的,不怕洩漏。
js端首先生成一個100bit長的整數a,並依據公式計算出A, 用ajax的形式傳送到服務端php。 服務端收到A,自己生成變數b,依據公式計算出B,響應給客戶端js。
此時,服務端和客戶端分別可以依據公式計算出一個相同的secret。 這個secret沒有在網路中傳輸過,雙方可說是“心照不宣”,且雙方自己選定的a和b是保密的,第三方無法根據公開傳輸的資料推算出a,b,當然也無法得到secret。 這就是DH演算法的原理。
======================================================華麗的分割線========================================================
金鑰交換完成後,我們假定雙方擁有了對稱加密的金鑰。 接下來是一個AES演算法的demo。 分別有js加密php解密,和反過來php加密js解密。
這裡採用了一個js庫,叫做crypt-js.它的官方地址是 http://code.google.com/p/crypto-js/#CryptoJS_3.1
js加密:
var pwd = "123456";
var key = CryptoJS.enc.Utf8.parse("96e79218965eb72c92a549dd5a330112"); //CryptoJS.MD5("111111");
var iv = CryptoJS.enc.Utf8.parse('1234567812345678');
var encrypted = CryptoJS.AES.encrypt(pwd, key, {iv:iv});
document.write(encrypted.toString());
這裡write出來的值,就是加密過後的密文,toString方法得到,按照crypto-js文件中說,是相容openssl格式的可見文字形式。(加密通常得到的結果是byte陣列,要以某種形式轉換為可見字元,如base64,hex等方式。 這個形式轉換也可以自定義)。
此值在php端解密:
$iv='1234567812345678';
$key = "96e79218965eb72c92a549dd5a330112";
$data = "FBPJjTRA4MEkMcMDg7eOng==";
$data = base64_decode($data);
$ttt = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
echo "decrypted : $ttt";
接下來的程式碼demo是php端加密, js解密。
php加密:
$svrMessage = "server message . .. adfasdfsdaf adfasdfsdaf";
$iv='1234567812345678';
$key = "96e79218965eb72c92a549dd5a330112";
$enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $svrMessage, MCRYPT_MODE_CBC, $iv);
$enc = base64_encode($enc);
echo "<br/>$enc";
輸出的結果是經過base64轉換為可見字元的。
js解密:
var received = "UQhDUzgusxZiejMuuVjh78BcpoQt82swQvSqyCvnuTdb3drBJTPghFBmTORflU6h";
var eee = CryptoJS.enc.Base64.parse(received);
var ddd = CryptoJS.AES.decrypt({
ciphertext: CryptoJS.enc.Base64.parse(received),
salt: ""
}, key, {iv:iv});
document.write(ddd.toString(CryptoJS.enc.Utf8) + "<br />");