雙線性群簡介
質數階雙線性群(Prime-Order Bilinear Groups)
質數雙線性群可以由五元組(p,G1,G2,GT,e)來描述。五元組中p是一個與給定安全常數λ相關的大質數,G1,G2,GT均是階為p的乘法循環群,e為雙線性映射e:G1×G2→GT,它滿足以下3個條件:
- 雙線性(Bilinearity):對於任意的g∈G1,h∈G2,a,b∈Zp,有e(ga,hb)=e(g,h)ab;
- 非退化性(Non-degeneracy):至少存在元素g1∈G1,g2∈G2,滿足e(g1,g2)≠1;
- 可計算性(Efficiency):對於任意的u∈G1,v∈G2,存在一個與給定安全常數λ相關的多項式時間算法,可以高效地計算e(u,v);
現在的密碼學相關論文中,習慣將G1,G2設置為乘法循環群。但是,基於橢圓曲線的雙線性群構造中,G1,G2是加法群。所以在大約2005年以前的論文中,雙線性群一般寫成加法群形式。jPBC中將G1,G2表示稱為了乘法循環群,因此在實現寫成加法群形式的方案時,要注意將加法群改成乘法群的寫法再進行實現。如何修改呢?很簡單,把加法群中的加法運算寫成乘法運算、把加法群中的乘法運算寫成冪指數運算即可。
合數階雙線性群(Composite-Order Bilinear Groups)
合數階雙線性群和質數階雙線性群很類似,區別是G1,G2,GT的階數是一個合數N,其中N是一些大質數的乘積,如N=p1p2⋯pn。同樣,e為雙線性映射e:G1×G2→GT,它滿足雙線性性、非退化性以及可計算性。
與質數階雙線性群不同,合數階雙線性群中,GN有階數分別為p1,p2,⋯,pn的子群Gp1,⋯,Gpn。這些子群進一步滿足正交特性。
簡單地說就是,子群之間進行雙線性運算的結果必為1。
一些說明
首先,由於雙線性群現在的構造是基於橢圓曲線的,而橢圓曲線上的元素是由坐標(x,y)表示的,所以如果我們將G1,G2的結果輸出到Java的控制台,我們得到的是一個坐標。不過,GT是一個普通的Z群,所以其元素的表示是一個數。
其次,在密碼學中,如果G1=G2,我們稱這個雙線性群是對稱雙線性群(Symmetric Bilinear Group),否則稱之為非對稱雙線性群(Asymmetric Bilinear Group)。
是否為對稱雙線性群由選取的橢圓曲線種類決定。一般認為,非對稱雙線性群要比對稱雙線性群更安全。特別地,現在已經證明一些特定的對稱雙線性群是不安全的了。
現在jPBC可以使用的曲線為如下幾類:
- Type A
- Type A1
- Type D
- Type E
- Type F
- Type G
現在密碼學實現基本只使用Type A和Type A1的。前者為對稱質數階雙線性群,后者為合數階對稱雙線性群。本博客也只在這兩類曲線上實驗。其他類曲線的實現類似。由於是對稱雙線性群,本博客中G1,G2統一寫為G。
雙線性群初始化
在jPBC中,雙線性群的使用都是通過叫做Pairing的對象來實現的。雙線性群的初始化在jPBC中就是對Pairing對象的初始化。雙線性群有兩種初始化的方法。第一種是通過代碼動態產生一個雙線性群,第二種是從文件中讀取參數而產生群。
通過代碼動態產生
動態產生的方法非常簡單,大概有如下步驟:指定橢圓曲線的種類、產生橢圓曲線參數、初始化Pairing。Type A曲線需要兩個參數:rBit是Zp中階數p的比特長度;qBit是G中階數的比特長度。代碼為:
TypeACurveGenerator pg = new TypeACurveGenerator(rBit, qBit); PairingParameters typeAParams = pg.generate(); Pairing pairing = PairingFactory.getPairing(typeAParams);
Type A1曲線需要二個參數:numPrime是階數N中有幾個質數因子;qBit是每個質數因子的比特長度。注意,Type A1涉及到的階數很大,其參數產生的時間也比較長。代碼為:
TypeA1CurveGenerator pg = new TypeA1CurveGenerator(numPrime, qBit); PairingParameters typeA1Params = pg.generate(); Pairing pairing = PairingFactory.getPairing(typeA1Params);
通過文件讀取產生
我們也可以選擇事先產生好參數,存放在文件中。以后再初始化的時候,直接從文件中讀取參數,就可以非常快速的初始化雙線性群。
PairingParameters支持toString()函數。實際上,我們可以直接將PairingParametersd的toString()存放在文件中。讀取的時候,通過讀取文件就可以直接初始化雙線性群了。
Type A曲線從文件中讀取參數初始化的代碼為:
TypeACurveGenerator pg = new TypeACurveGenerator(rBit, qBit); PairingParameters typeAParams = pg.generate(); //將參數寫入文件a.properties中,我用了Princeton大學封裝的文件輸出庫 Out out = new Out("a.properties"); out.println(typeAParams); //從文件a.properties中讀取參數初始化雙線性群 Pairing pairing = PairingFactory.getPairing("a.properties");
Type A1曲線從文件中讀取參數初始化的代碼為:
TypeA1CurveGenerator pg = new TypeA1CurveGenerator(numPrimes, qBit); PairingParameters typeA1Params = pg.generate(); //將參數寫入文件a1.properties中,同樣使用了Princeton大學封裝的文件輸出庫 Out out = new Out("a1.properties"); out.println(typeA1Params); //從文件a1.properties中讀取參數初始化雙線性群 Pairing pairing = PairingFactory.getPairing("a1.properties");
產生雙線性群中的隨機數
Type A中產生隨機數的方法很簡單,代碼為:
//隨機產生一個Z_p群的元素 Element Z_p = pairing.getZr().newRandomElement().getImmutable(); //隨機產生一個G_1群的元素 Element G_1 = pairing.getG1().newRandomElement().getImmutable(); //隨機產生一個G_2群的元素 Element G_2 = pairing.getG2().newRandomElement().getImmutable(); //隨機產生一個G_T群的元素 Element G_T = pairing.getGT().newRandomElement().getImmutable();
Type A1中產生隨機數的方法稍微有點麻煩。對於ZN和GT方法和Type A一樣。代碼為:
//隨機產生一個Z_N群的元素 Element Z_N = pairing.getZr().newRandomElement().getImmutable(); //隨機產生一個G_T群的元素 Element G_T = pairing.getGT().newRandomElement().getImmutable();
但是對於G就不同了。因為G有子群Gpi,Type A1產生隨機數時需要指定生成元,橢圓曲線的參數,產生哪個子群的元素,以及Type A1一共有多少個子群。
假定我們產生的Type A1共有n個子群,這n個子群的階分別為p1,⋯,pn,產生隨機數的代碼如下:
TypeA1CurveGenerator pg = new TypeA1CurveGenerator(numPrimes, qBit); PairingParameters typeA1Params = pg.generate(); Pairing pairing = PairingFactory.getPairing(typeA1Params); //設定並存儲一個生成元。由於橢圓曲線是加法群,所以G群中任意一個元素都可以作為生成元 Element generator = pairing.getG1().newRandomElement().getImmutable(); //隨機產生一個G_p_1中的元素 Element G_p_1 = ElementUtils.getGenerator(pairing, generator, typeA1Params, 0, numPrimes).getImmutable(); //隨機產生一個G_p_2中的元素 Element G_p_2 = ElementUtils.getGenerator(pairing, generator, typeA1Params, 1, numPrimes).getImmutable(); // ...... //隨機產生一個G_p_n中的元素 Element G_p_n = ElementUtils.getGenerator(pairing, generator, typeA1Params, 1, numPrimes).getImmutable();
將指定的元素哈希到雙線性群中
由於雙線性群最初是用在基於身份的加密(Identity-Based Encryption)系統中,我們經常會需要將一個特定的String或者byte[]哈希到雙線性群中。
jPBC支持將byte[]哈希到雙線性群的Z,G,GT中。但是,jPBC說明文檔中沒有提到的是,byte[]數組長度不能太長,如果過長會拋出異常。因此,我建議首先將byte[]用一個SHA256或者其他通用哈希函數哈希到固定長度,再用jPBC提供的函數哈希到雙線性群上。在這里我略去SHA256步驟,直接給出哈希到Z,G,GT群的代碼:
//將byte[] byteArray_Z_p哈希到Z_p群 Element hash_Z_p = pairing.getZr().newElement().setFromHash(byteArray_Z_p, 0, byteArray_Z_p.length); //將byte[] byteArray_G_1哈希到G_1群 Element hash_G_1 = pairing.getG1().newElement().setFromHash(byteArray_G_1, 0, byteArray_G_1.length); //將byte[] byteArray_G_2哈希到G_2群 Element hash_G_2 = pairing.getG2().newElement().setFromHash(byteArray_G_2, 0, byteArray_G_2.length); //將byte[] byteArray_G_T哈希到G_T群 Element hash_G_T = pairing.getGT().newElement().setFromHash(byteArray_G_T, 0, byteArray_G_T.length);
注意,對於Type A1來說,這個代碼無法指定哈希到指定子群Gpi中。解決方法是將byte[]先哈希到Z群,然后利用G,GT的生成元計算冪指數,從而達到哈希到G,GT上的效果。
雙線性群的運算
雙線性群之間有如下運算:
- G相關運算:GZ, G×G;
- GT相關運算:GZT, GT×GT;
- Z相關運算:Z+Z, Z×Z;
- Pairing運算
做運算的時候要注意一下幾點:
- Java的運算結果都是產生一個新的Element來存儲,所以我們需要把運算結果賦值給一個新的Element;
- Java在進行相關運算時,參與運算的Element值可能會改變。所以,如果需要在運算過程中保留參與運算的Element值,在存儲的時候一定要調用getImmutable(),具體方法見代碼中的初始化相關參數部分。
- 其實為了保險起見,防止Element在運算的過程中修改了Element原本的數值,可以使用Element.duplicate()方法。這個方法將返回一個與Element數值完全一樣的Element,但是是個新的Element對象。舉例來說,如果做G1×G1的運算,可以寫成:
Element G_1_m_G_1 = G_1.duplicate().mul(G_1_p.duplicate());
- G和G其實也是可以進行冪指數運算的,即GG,調用的函數為Element e1.pow(Element e2)。特別注意,我們再寫G群的Z次方運算時,用的函數為powZn(),而不是pow(),這個調用錯誤很容易使得程序的運算結果不正確。
代碼如下:
//初始化相關參數 Element G_1 = pairing.getG1().newRandomElement().getImmutable(); Element G_2 = pairing.getG2().newRandomElement().getImmutable(); Element Z = pairing.getZr().newRandomElement().getImmutable(); Element G_T = pairing.getGT().newRandomElement().getImmutable(); Element G_1_p = pairing.getG1().newRandomElement().getImmutable(); Element G_2_p = pairing.getG2().newRandomElement().getImmutable(); Element Z_p = pairing.getZr().newRandomElement().getImmutable(); Element G_T_p = pairing.getGT().newRandomElement().getImmutable(); //G_1的相關運算 //G_1 multiply G_1 Element G_1_m_G_1 = G_1.mul(G_1_p); //G_1 power Z Element G_1_e_Z = G_1.powZn(Z); //G_2的相關運算 //G_2 multiply G_2 Element G_2_m_G_2 = G_2.mul(G_2_p); //G_2 power Z Element G_2_e_Z = G_2.powZn(Z); //G_T的相關運算 //G_T multiply G_T Element G_T_m_G_T = G_T.mul(G_T_p); //G_T power Z Element G_T_e_Z = G_T.powZn(Z); //Z的相關運算 //Z add Z Element Z_a_Z = Z.add(Z_p); //Z multiply Z Element Z_m_Z = Z.mul(Z_p); //Pairing運算 Element G_p_G = pairing.pairing(G_1, G_2);