1. 背景
雙線性映射是現代密碼學中的常用數學工具,經常被用來構造包括基於身份加密在內的各種密碼算法。筆者近日開發的一款B/S架構支持基於身份加密和關鍵字可搜索加密的郵件系統就使用到了雙線性映射,而通過搜索,發現瀏覽器中的常用前端語言JavaScript並沒有提供文檔與功能齊全的支持雙線性映射的代碼庫可供使用。為了使開發工作可以正常進行,筆者另尋他路解決了這個問題,最終可以在Chrome瀏覽器實現需要的加密算法,本文就記錄了問題的解決過程。但是這個解決方案並不通用,目前僅能在FireFox和Chrome瀏覽器中使用,在IE和Edge瀏覽器中則需要使用另外的技術。
1.1. 雙線性映射
雙線性映射的定義十分抽象,這里僅對其做簡單介紹。
1.1.1. 定義
設G1、G2、GT都是階p為素數的循環群,如果映射e:G1xG2→GT滿足如下性質:
1、雙線性:對於任意的g1∈G1,g1∈G1,a,b∈Zp,均有e(g1a,g2b)=e(g1,g2)ab成立;
2、非退化性:∃g1∈G1和g2∈G2,滿足e(g1,g2)≠1GT;
3、可計算性:存在有效的算法,對於∀g1∈G1,g2∈G2,均可計算e(g1,g2)。
如果G1和G2群相同,就稱雙線性映射e是對稱的,否則稱其為非對稱的。
另外,上述雙線性映射中各群是素數階的,其實在2005年boneh也實現了合數階群的雙線性映射,具體可以參考文獻[1]。
1.1.2. 使用場景
雙線性映射在現代密碼學中應用廣泛,比如基於身份加密[2]、基於屬性加密[3]、組密鑰分配協議[4]、基於屬性簽名[5]和公鑰可搜索加密[6]。可以看到雙線性映射對於現代密碼學是多么重要。
1.1.3. 常用的函數庫
目前有非常多的函數庫提供了雙線性映射的計算功能,如筆者最常用的PBC Library[7],這是一個開源的C語言項目。此外還有C語言的Miracl[8]、Relic[9],Java的jPBC[10]等。這些函數庫都可以從互聯網上進行下載並使用。
1.2. 瀏覽器
瀏覽器的編程環境比較單一,不像客戶端或服務器開發中那樣可以使用多種開源軟件以及系統調用,因此其中的編程方式選擇也很匱乏。
1.2.1. 編程環境及不足
瀏覽器中的編程環境,主要是前端語言HTML和JavaScript,對於FireFox和Chrome來說,還可以使用C++開發Native Client,對於IE來說可以使用C#或者C++開發ActiveX空間。其中Native Client中的代碼主要是平台無關代碼,其作用更多的是優化網頁中特定組件的運行速度,如實時3D動畫等。
對於HTML來說,其主要作用是繪制網頁內容,並不具備執行復雜內容的能力;對JavaScript來說,存在面向JavaScript的雙線性映射運算庫jspairing[11],但是其已多年無人維護,並且缺乏文檔和教程,缺少實用性;對Native Client來說,其更多的是面向代碼的可移植性,對於已有代碼的兼容性以及系統中動態庫的調用流程沒有過多考慮;而ActiveX技術則非常古老,其只對Windows平台中的IE瀏覽器兼容,並且缺乏足夠的文檔和教程。因此這些技術都被一一否定。
1.2.2. 其他選項
直接編寫瀏覽器可用的功能代碼是行不通的,那么就只能考慮其他選項了。這里我的選擇是使用由FireFox和Chrome兼容的Native Messaging[12]技術。這項技術允許瀏覽器中的JavaScript代碼與操作系統中的應用程序進行通信,從而實現調用操作系統中程序功能的目的。如果使用這項技術,就可以在應用程序中提供基於雙線性映射的密碼算法的底層功能,並由JavaScript實現上層接口,從而在瀏覽器中實現雙線性映射的功能。
2. 方案實現
雖然Native Messaging可以幫助我們實現想要的功能,但是其本身的編程規范較為復雜,並且編程過程中需要注意的地方也很多,因此整個方案實現起來比較麻煩。這里使用Chrome瀏覽器的Native Messaging技術來進行實現,理論上Firefox也是兼容這套技術的,但是有些細節部分需要修改,這其中的細節筆者沒有去求證。
2.1. 架構
Chrome的Native Messaging技術是依托於瀏覽器擴展的,也就是說與本地應用程序直接進行通信的是瀏覽器中的擴展程序,而網頁中的JavaScript代碼通過與擴展程序通訊,來對本地應用程序進行間接調用。其整個通信框架如圖 1所示。
圖 1 Native Messaging的通信框架[13]
從圖中,可以看到,網頁首先向瀏覽器擴展發送請求,接着擴展向本地應用程序發送請求,當本地應用程序將請求數據返回給擴展后,擴展再將請求結果返回給網頁。因此在整個方案實現中,需要進行三個部分的開發:本地應用程序、瀏覽器擴展和網頁代碼。
2.2. 通信方式
在開始開發工作之前,首先要確定瀏覽器擴展與本地應用之間的通信方式,以確定通信的具體數據結構以及封裝形式,並及早確定所選編程語言,避免后面遇到問題。
根據Native Messaging的官方文檔,瀏覽器擴展和本地應用程序之間使用標准輸入輸出進行通信,通信的數據必須是JSON格式,並且本地應用程序在讀取數據的時候,會先從標准輸入中讀取一個4字節的二進制數據,其代表后續JSON數據的長度;同樣地,本地應用程序在向瀏覽器擴展返回結果時,也會先向標准輸出中寫入返回的數據的總長度,再將整個數據寫入到標准輸出中。同時,因為JSON數據格式不支持二進制字符,因此需要對通信過程中的二進制數據進行Base64編碼。
2.3. 底層庫編譯
因為非常熟悉PBC的用法和文檔,以及我手頭上還有比較多的PBC代碼,因此這里我選擇PBC作為底層的函數庫來使用。PBC的官方下載地址為https://crypto.stanford.edu/pbc/download.html,可以看到官方提供了源代碼以及編譯好的win32平台二進制dll文件。但是由於官方沒有提供該二進制文件對應的符號文件,因此沒有辦法直接使用它來進行編程,因此需要自己動手進行編譯。
在編譯時使用MinGW32環境,這里說明一下,即使平台是64位Windows,也要使用32位的MinGW來進行編譯,否則編譯好的PBC庫的數據輸入輸出會存在問題。
首先從中科大的開源軟件站https://mirrors.ustc.edu.cn/msys2/distrib/ 下載最新的msys2安裝包,接着一路點擊下一步進行安裝,安裝完畢后,啟動MinGW32.exe文件,並在彈出的shell窗口中輸入以下命令,安裝解壓和編譯gmp大數運算庫所需的軟件:
pacman –S mingw-w64-i686-gcc make mingw-w64-i686-gdb xzip
接着從https://gmplib.org/ 下載最新的gmp大數運算庫。解壓后從MinGW32 Shell進入其解壓的目錄。依次執行命令
./configure --enable-shared --disable-static make make install
來將gmp庫編譯為動態鏈接庫dll,並將其安裝到MinGW32的相關目錄中。
編譯完gmp后,執行命令
pacman –S flex bison
來安裝編譯pbc所需的軟件,pbc的源碼及依賴文件准備就緒后,就可以像gmp庫一樣依次執行命令
./configure --enable-shared --disable-static make make install
來將其編譯為動態鏈接庫並安裝到MinGw32的相關目錄中了。
2.4. 本地應用程序底層代碼
本地應用程序分為兩個部分:底層函數庫和Python接口層。其中底層函數庫使用C語言編寫,Python接口層使用Python語言編寫。這里就有兩種實現方式:
- 將底層函數庫編譯為DLL動態連接庫,通過Python的ctypes進行調用;
- 將底層函數庫編譯為Python模塊,直接由Python來進行調用。
這兩種方法中,第二種方法更加便捷,但是實現起來問題比較多,這里我將重點介紹第一種方法,第二種方法我會介紹一下要注意的地方。
2.4.1. 使用ctypes
ctypes是python內置的非常有用的模塊,它能夠賦予Python直接調用外部動態鏈接庫的能力,並且提供了多種C語言數據類型,是利用Python兼容調用已有代碼的非常好的工具。
這里挑選一個典型的函數來進行舉例講解。
設有一個C語言結構體和一個函數
typedef struct IBECipher_ { unsigned char *cip1, *cip2; int cip1_len, cip2_len; } IBECipher; int generate_ibe_cipher(char *receiver, IBECipher *cip, unsigned char *aes_key, int *aes_key_len);
其中結構體IBECipher中的cip1和cip2以及aes_key都是用於輸出的,其所指空間在函數調用前就已經分配完畢,並且全部都是二進制數據。而receiver是傳入的以NULL結尾的ASCII字符串,aes_key_len是指向一個整數的指針,用來表明aes_key的字節長度。
首先,要將包含函數generate_ibe_cipher的C語言實現文件編譯為動態鏈接庫。假設其文件名為foo.c,那么使用gcc來將其編譯為動態鏈接庫的命令為
gcc -shared foo.c -o foo.dll
這個命令會產生名為foo.dll的動態鏈接庫,供其他程序調用了。
那么為了在Python中利用ctypes調用這個函數,還需要需要模擬4種數據結構:整數指針、以NULL結尾的C語言字符串、二進制緩沖區和C語言結構體。這里要說明的是,在C語言中,以NULL結尾的字符串與二進制緩沖區的表達都是字節數組,但是在python中兩者是不一樣的,因為二進制緩沖區中的數據是可能存在NULL字符的,此時如果將其看做以NULL結尾的字符串,就會導致數據的截斷,從而造成錯誤。這四種C語言數據結構在Python中的表示為:
1、整數指針
整數指針非常簡單,就是指向整數的指針,使用ctypes來表達的話,就是這樣的
from ctypes import POINTER, c_int32
p_int = POINTER(c_int32)
其中p_int是一個整數指針的類型,如果要使用這個類型,需要用一個c_int32型的變量來對它進行初始化,也就是這樣的
p_some_int = p_int(c_int32())
這樣,p_some_int就是一個指向某個整數的指針了。
2、以NULL結尾的C語言字符串
以NULL結尾的C語言字符串非常簡單,它在ctypes中的類型是c_char_p,如果將某個已有的Python字符串轉換為以NULL結尾的C語言字符串,代碼也非常簡單
from ctypes import c_char_p
some_str = 'This is som string' p_some_ptr = c_char_p(some_str)
3、二進制緩沖區
前面說過,在C語言中二進制緩沖區和字符串沒有本質上的區別,但是在Python中,這兩者是有區別的,因為C語言字符串會被從NULL處截斷。因此,為了與動態鏈接庫交換任意二進制數據,就不能使用c_char_p來直接從Python中的字節數據來構造,而要使用另一個數據類型——字節數組。在ctypes中,定義一個數組類型非常簡單,比如要定義一個有64個字節的整形數組,代碼如下
from ctypes import c_int32
arr_64 = c_int32*64
這里的arr_64是一個數組類型,其中有64個整形元素。如果要定義一個此類型的變量,就直接使用代碼
arr = arr_64()
這會將arr中所有的元素初始化為0,當然也可以提供部分初始化值,比如
arr = arr_64(1,2,3,4,5)
這會將arr中的前5個元素分別初始化為1,2,3,4,5.並且字節數組中的元素在Python中支持按下標讀寫,特別方便。那么定義字節數組的方式與定義整形數組的方式也是一樣的
from ctypes import c_ubyte
arr_byte = c_ubyte*128
這個代碼定義了長度為128字節的字節數組類型,如果要使用它就直接像使用整形數組那樣就好
4、C語言結構體
ctypes也提供了定義C語言結構體的功能,以上面的IBECipher為例,在Python中,它的定義為
from ctypes import Structure, POINTER, c_ubyte, c_int32
class IBECipher(Structure): _fields_ = [ ("cip1", POINTER(c_ubyte)), ("cip2", POINTER(c_ubyte)), ("cip1_len", c_int32), ("cip2_len", c_int32) ]
p_IBECipher = POINTER(IBECipher)
就是說,結構體在ctypes中的定義是ctypes中Structure類的子類,並且自己按需填充其_feilds_字段就可以了。
接下來,就是要用ctype調用編譯好的foo.dll中的函數了,代碼如下
from ctypes import CDLL, PONTER, c_ubyte, c_int32, c_char_p, Structure dll_lib = CDLL('foo.dll') receiver = c_char_p('receiver') ibe_cipher = IBECipher() ibe_cipher.cip1 = arr_byte() ibe_cipher.cip2 = arr_byte() aes_key = arr_byte() aes_key_len = PONTER(c_int32)(c_int32()) dll_lib.generate_ibe_cipher(receiver, PONTER(IBECihper)(ibe_cipher), aes_key, aes_key_len)
執行完畢后就可以從ibe_cipher和aes_key中獲取輸出結果了。
這個方法的缺點是要對dll_lib中的函數進行封裝,優點是可以直接使用現有的dll文件,而不需要專門為Python編寫相應的版本。
如果要將編譯好的dll模塊拷貝出來,在MinGW32環境外使用,就需要將該dll以及它依賴的dll全部拷貝出來,這其中,出來gmp和pbc等編程庫之外,還要拷貝libgcc_s_dw2-1.dll和libwinpthread-1.dll這兩個MinGW32本身的依賴文件。
2.4.2. 編譯為Python模塊
將C語言代碼編譯為Python模塊的方法,在網上有非常多的參考資料,基本上都是使用Python的distutils.core模塊,編寫Setup文件來編譯安裝模塊的,但是在Windows平台上,就會出現幾個問題:
1、所用編譯器版本與當前系統中編譯器的版本不匹配,找不到所需的lib導入庫文件;
2、無法自動調用Visual Studio編譯器;
3、沒有安裝Visual Studio編譯器,無法編譯。
也就是說,如果使用默認的方法,就需要安裝對應版本的Visual Studio編譯器或者對應版本的SDK,並且還要保證不出各種奇奇怪怪的錯誤。因此,這里介紹使用MinGW32中的gcc來編譯模塊的方法。
首先進入要使用的Python的安裝目錄,進入Lib/distutils子文件夾,找到cygwinccompiler.py文件,找到
if msc_ver == ‘1300’: # MSVC 7.0 returen [‘msvcr70’]
這一行,然后在這個判斷語句中添加缺少的版本的判斷,比如1900,並且將列表中的字符串msvcr*替換為任意可在MinGW32中找到的庫,比如gmp。我這里的文件替換后的代碼為
msc_pos = sys.version.find('MSC v.') if msc_pos != -1: msc_ver = sys.version[msc_pos+6:msc_pos+10] if msc_ver == '1300': # MSVC 7.0 return ['msvcr70'] elif msc_ver == '1310': # MSVC 7.1 return ['msvcr71'] elif msc_ver == '1400': # VS2005 / MSVC 8.0 return ['msvcr80'] elif msc_ver == '1500': # VS2008 / MSVC 9.0 return ['msvcr90'] elif msc_ver == '1600': # VS2010 / MSVC 10.0 return ['msvcr100'] elif msc_ver == '1900': return ['gmp'] else: raise ValueError("Unknown MS Compiler version %s " % msc_ver)
此時,再在MinGW32的shell中利用此Python來執行編譯安裝所用的setup文件,並傳入參數--compiler=mingw32,即可完成編譯,具體的命令行為
'/c/Program Files (x86)/Python36-32/python.exe' setup.py build --compiler=mingw32
此時可在生成的build子目錄中的lib.w32-3.6目錄中找到編譯好的pyd文件,將此pyd文件與其依賴的動態鏈接庫一起拷貝到對應的Python根目錄,就可以直接在Python中導入其中內容了。
2.5. 本地應用程序接口代碼、瀏覽器擴展和網頁JavaScript
關於本地應用程序接口代碼、瀏覽器擴展和網頁JavaScript的編寫,可以參考谷歌官方的教程,地址是https://developer.chrome.com/apps/nativeMessaging,這個教程中的本地應用程序也是Python編寫的,因此與上面介紹的內容是很吻合的。這里只說幾個很容易踩坑的地方:
1、啟動本地應用程序的bat腳本,一定要在開頭加上@echo off命令,因為腳本與瀏覽器擴展之間是通過標准輸入與標准輸出進行數據交換的,因此如果不關閉腳本的回顯功能,那么腳本產生的輸出就會被傳遞給本地應用程序,造成錯誤;
2、瀏覽器與本地應用程序之間交換的JSON數據必須是JSON的數組或者字典結構,不能單獨傳遞數字或者字符串,並且具體的JSON內容與意義是由自己設計的;
3、在進行數據的讀取和寫入時,一定要先讀寫4個字節長度的數據長度整數值,這樣好確定后續數據的長度,並能避免出錯;
4、網頁向瀏覽器擴展傳遞請求信息時,提供的JavaScript回調函數是一次性的,即最多只能使用一次,並且默認情況下,其生命周期是在瀏覽器擴展內的chrome.runtime.onMessageExternal.addListener中注冊的監聽器函數的內部,如果要想保留該回調函數,以等待處理結果產生后再返回數據,就要讓監聽器函數返回true值,並且即使如此,該回調函數也只能調用一次;
5、為了讓網頁調用瀏覽器擴展,需要提供瀏覽器擴展的ID,這個ID是在chrome在Debug下安裝該擴展后就自動分配的,在擴展頁面可以看到。
這樣一來就可以通過由瀏覽器擴展中轉請求的方式來在瀏覽器環境下實現雙線性映射運算了。
參考文獻
[1] Boneh, Dan, Eu-Jin Goh, and Kobbi Nissim. "Evaluating 2-DNF formulas on ciphertexts." Theory of Cryptography Conference. Springer, Berlin, Heidelberg, 2005.
[2] Boneh, Dan, and Matt Franklin. "Identity-based encryption from the Weil pairing." Annual international cryptology conference. Springer, Berlin, Heidelberg, 2001.
[3] Goyal, Vipul, et al. "Attribute-based encryption for fine-grained access control of encrypted data." Proceedings of the 13th ACM conference on Computer and communications security. Acm, 2006.
[4] Lee, Sangwon, et al. "An efficient tree-based group key agreement using bilinear map." International Conference on Applied Cryptography and Network Security. Springer, Berlin, Heidelberg, 2003.
[5] Shanqing, Guo, and Zeng Yingpei. "Attribute-based signature scheme." Information Security and Assurance, 2008. ISA 2008. International Conference on. IEEE, 2008.
[6] Boneh, Dan, et al. "Public key encryption with keyword search." International conference on the theory and applications of cryptographic techniques. Springer, Berlin, Heidelberg, 2004.
[7] Lynn, Ben. "PBC library." Online: http://crypto. stanford. edu/pbc (2006).
[8] MRACL Trust. “Miracl Library.” Online: https://github.com/miracl/MIRACL (2018).
[9] Aranha, Diego F. "RELIC is an Efficient LIbrary for Cryptography." http://code. google. com/p/relic-toolkit/ (2013).
[10] De Caro, Angelo. "jPBC Library." Online at http://libeccio. dia. unisa. it/projects/jpbc/download. html 67.
[11] jorgenhoc@gnamil.com. “jspairings.” Online: https://github.com/jorgenhoc/jspairings (2014).
[12] Chrom. “Native Messaging.” Online: https://developer.chrome.com/apps/nativeMessaging (2018).
[13] kagula. “Chrome Native Messaging技術示例.” Online: https://blog.csdn.net/lee353086/article/details/49362811 (2015).