學習文章:TPRE:分散式門限代理重加密
成方金科新技術實驗室與隱語團隊合作,構建了「基於國密的分散式門限代理重加密演演算法TPRE」,為使用者提供了一種安全、高效、自主可控的資料共用和授權管理方案。在資料隱私保護和資料安全共用方面具有廣泛的應用前景。
⚠️:該演演算法由成方金科密碼學研究員張曙光(知乎:六三)基於隱語的密碼庫yacl實現,其提供了易於開發的密碼工具介面,TPRE演演算法目前已經貢獻至yacl中。https://github.com/secretflow/yacl/tree/main/yacl/crypto/primitives/tpre
TPRE演演算法具有以下優勢:
代理重加密是一種公鑰密碼方案,它允許代理將一個公鑰加密成的密文轉換到另一個公鑰加密成的密文,而代理者不能瞭解關於原始訊息的任何資訊,要做到這一點,代理必須擁有一個重加密金鑰。
一個代理重加密演演算法通常包含三種角色,即資料擁有者Alice、資料接收者Bob和代理計算Proxy。
假設,資料\(m\)已經被Alice使用自己公鑰加密為密文\(c\),並儲存在Proxy,具體演演算法具體步驟如下:
Alice作為資料擁有者,想要將資料授權給Bob使用,則為Proxy生成重加密金鑰\(rk\)。
Proxy在接收到\(rk\)後,對密文\(c\)重加密,得到新密文\(c^‘\),並將其傳送至Bob。
Bob使用自己的私鑰\(c^‘\)對解密,即可得到明文資料\(m\)。
這裡想起來了一個基於NTRU的代理重加密方案:NTRUReEncrypt: An Efficient Proxy Re-Encryption Scheme Based on NTRU,下面具體介紹一下方案:
代理重加密方案:NTRUReEncrypt
委託人為:A,被委託人為:B,代理者:proxy
代理重加密適合在雲端計算場景中使用,即代理節點為計算效能較強的單節點,這與現有隱私計算體系架構不符,因為現在隱私計算架構通常是分散式架構。因此,需要對傳統代理重加密方案進行改造,使之能夠適應分散式計算環境。
分散式代理重加密是指將傳統代理重加密中的單Proxy節點,拆分為多個Proxy節點。因而在對資料重加密時,需要多個Proxy節點參與合作計算。
考慮到選取參與計算的Proxy節點的靈活性,需要將分散式代理重加密重新設計為基於門限的分散式代理重加密。
Shamir Secret Sharing是一種加密技術,可以將祕密資訊分散成多個部分,並分配給不同的人,只有所有部分被彙集在一起才能重構出原來的祕密資訊。它是由以色列密碼學家Adi Shamir在1979年發明的,被廣泛應用於保護機密資訊,例如在密碼學、數位簽章、多方計算等領域。其基本思想是通過多項式插值來實現資訊的分散和重構,具有高度的安全性和可靠性。
Shamir密祕分享:假設有一個祕密資訊,例如一個密碼或者一個私鑰,需要將這個祕密資訊分配給兩個人,可以使用Shamir Secret Sharing演演算法來實現。
例如:假設祕密資訊是密碼「5」,需要將它分配給兩個人。
具體參考:https://www.cnblogs.com/pam-sh/p/17179097.html#shamir祕密分享
下圖是我國商用密碼SM2選定的素數域橢圓曲線"sm2p256v1"的引數選擇:
更多可參考SM2國標:
使用的雜湊演演算法構造如下:
\(h_x= (1 + Bignum(SM3(x)||SM3(SM3(x))) )mod n-1\)
其中\(n\)是橢圓曲線的階,\(x\)是函數的輸入
SM3套wa?
由於公鑰密碼是執行在代數計算上的密碼演演算法,其計算效率遠低於對稱密碼,因此,在待加密資料量較大時,使用公鑰密碼直接對資料加密不是一個良好選擇。在該場景中,可使用混合加密體制KEM/DEM:
這兩個機制聯合使用,可以提供資料的加解密效率,在密文需要傳輸時,也可降低通訊開銷。具體而言,DEM機制是用作保護原始資料,即使用對稱加密演演算法,對原始資料進行加密保護;KEM是用作保護加密原始資料時所使用的對稱金鑰,使用公鑰加密演演算法對對稱金鑰進行加密保護。
關於更多金鑰封裝可參考:金鑰封裝和公鑰加密的聯絡和區別?
UMBRAL是一個代理重加密方案(A THRESHOLD PROXY RE-ENCRYPTION SCHEME),根據一個金鑰封裝演演算法改進而來。Alice作為資料產生方,通過\(N\)個代理階段的重加密機制可以將密文的解密許可權委託給Bob,具體來說,Bob得到\((t,N)\)個節點重加密資訊才能解密(恢復)出該明文。
代理重加密的使用場景:公共網路中任意數量的參與者實現私人資料共用,但不向中間實體(代理者)洩漏金鑰資訊。
UMBRAL是閾值代理重加密演演算法,是非互動式的、單向的,且在重加密後可以驗證的,在閾值方面採用Shamir 祕密分享技術。
一個代理重加密【Proxy Re-Encryption,PRE】演演算法的簡易框架如下:
下面對KEM的演演算法功能模組分別介紹:
1、金鑰生成演演算法
Alice得到一對公私鑰對\((pk_A,sk_A)\)
輸入:\(sk_A=a\)、\(pk_B=g^b\)、片段\(N\)、閾值\(t\)
輸出:\(N\)個重加密金鑰片段\(kFrag\)
2、封裝和解封裝演演算法
輸入:\(pk_A\)
輸出:對稱加密金鑰\(K\)和封裝結果\(capsule\)
輸入:\(sk_A\)和\(capsule\)
輸出:對稱加密金鑰\(K\)
3、重封裝和解封裝片段演演算法
輸入:重加密金鑰片段\(kFrag\)和封裝結果\(capsule\)
輸出:重新封裝的片段(部分)\(cFrag\)
輸入:Bob的私鑰\(sk_B=b\)、\(t\)個重封裝片段\(cFrag\)
輸出:對稱加密金鑰\(K\)
下面詳細介紹KDM演演算法流程:
1、引數設定
2、金鑰生成演演算法
\(KeyGen()\)
\(ReKeyGen(sk_A,pk_B,N,t)\)
3、封裝和解封裝演演算法
\(Encapsulate(pk_A)\)
\(CheckCapsule(capsule)\)
\(Decapsulate(sk_A,capsule)\)
4、重封裝和解封裝片段演演算法
\(ReEncapsulation(kFrag,capsule)\)
\(DecapsulateFrags(sk_B,\left \{cFrag_i \right \}_{i=1}^{t},capsule)\)
結合上述KEM演演算法,加入\(DEM\)後,封裝和解封裝演演算法變為了加密和解密演演算法,另外需後續驗證,所以DEM具體為認證加密演演算法(AEAD),下面給出完整的KEM/DEM演演算法:
\(Encrypt(pk_A,M)\):
\(Decrypt(sk_A,C)\):
\(ReEncrypt(kFrag,C)\):
\(DecryptFrags(sk_B,\{C_i'\}_{i=1}^t)\):
正確性和安全性參考原文。
pyUmbral是對門限代理重加密方案Umbral的實現,基於python,依賴openssl和cryptography庫。
在該程式中,Alice(資料擁有者)通過一個重加密的代理節點,將解密權利授權給Bob。具體說,當有門限個代理節點參與重加密時,Bob能聚合這些重加密的密文,然後使用自己的私鑰解密恢復出原始訊息。
pyUmbral是nucypher背後的加密引擎,nucypher是一個代理重新加密網路,用於在去中心化系統中增強隱私。
pip3 install umbral
下面以一個例子證明方案功能的正確性(docs/examples/umbral_simple_api.py):
1、金鑰生成
Alice生成:
Bob生成:
import random
from umbral import (
SecretKey, Signer, CapsuleFrag,
encrypt, generate_kfrags, reencrypt, decrypt_original, decrypt_reencrypted)
# Generate an Umbral key pair
# ---------------------------
# First, Let's generate two asymmetric key pairs for Alice:
# A delegating key pair and a Signing key pair.
alices_secret_key = SecretKey.random() #sk_A
alices_public_key = alices_secret_key.public_key() #pk_A
alices_signing_key = SecretKey.random() #簽名私鑰
alices_verifying_key = alices_signing_key.public_key() #簽名公鑰
alices_signer = Signer(alices_signing_key)
bobs_secret_key = SecretKey.random() #sk_B
bobs_public_key = bobs_secret_key.public_key() #pk_B
2、加解密
# Encrypt some data for Alice
# ---------------------------
# Now let's encrypt data with Alice's public key.
# Invocation of `pre.encrypt` returns both the `ciphertext`,
# and a `capsule`. Anyone with Alice's public key can perform
# this operation.
plaintext = b'Proxy Re-encryption is cool!'
capsule, ciphertext = encrypt(alices_public_key, plaintext)
print("ciphertext:",ciphertext)
# Decrypt data for Alice
# ----------------------
# Since data was encrypted with Alice's public key,
# Alice can open the capsule and decrypt the ciphertext with her private key.
cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
print("cleartext:",cleartext)
# Bob receives a capsule through a side channel (s3, ipfs, Google cloud, etc)
bob_capsule = capsule
# Attempt Bob's decryption (fail)
try:
fail_decrypted_data = decrypt_original(bobs_secret_key, bob_capsule, ciphertext)
except ValueError:
print("Decryption failed! Bob doesn't has access granted yet.")
3、重加密
# Alice grants access to Bob by generating kfrags
# -----------------------------------------------
# When Alice wants to grant Bob access to open her encrypted messages,
# she creates *threshold split re-encryption keys*, or *"kfrags"*,
# which are next sent to N proxies or *Ursulas*.
# She uses her private key, and Bob's public key, and she sets a minimum
# threshold of 10, for 20 total shares
kfrags = generate_kfrags(delegating_sk=alices_secret_key,
receiving_pk=bobs_public_key,
signer=alices_signer,
threshold=10,
shares=20) #Alice產生20個重加密金鑰片段,給代理節點
# Ursulas perform re-encryption
# ------------------------------
# Bob asks several Ursulas to re-encrypt the capsule so he can open it.
# Each Ursula performs re-encryption on the capsule using the `kfrag`
# provided by Alice, obtaining this way a "capsule fragment", or `cfrag`.
# Let's mock a network or transport layer by sampling `threshold` random `kfrags`,
# one for each required Ursula.
kfrags = random.sample(kfrags, # All kfrags from above
10) # M - Threshold #從20個重加密金鑰片段中,隨機選出10個用作重加密
# Bob collects the resulting `cfrags` from several Ursulas.
# Bob must gather at least `threshold` `cfrags` in order to open the capsule.
cfrags = list() # Bob's cfrag collection
for kfrag in kfrags:
cfrag = reencrypt(kfrags=capsule, kfrag=kfrag)
cfrags.append(cfrag) # Bob collects a cfrag
assert len(cfrags) == 10
4、解密
# Bob checks the capsule fragments
# --------------------------------
# If Bob received the capsule fragments in serialized form,
# he can verify that they are valid and really originate from Alice,
# using Alice's public keys.
suspicious_cfrags = [CapsuleFrag.from_bytes(bytes(cfrag)) for cfrag in cfrags]
cfrags = [cfrag.verify(capsule,
verifying_pk=alices_verifying_key,
delegating_pk=alices_public_key,
receiving_pk=bobs_public_key,
)
for cfrag in suspicious_cfrags] #驗證重加密後的密文
# Bob opens the capsule
# ------------------------------------
# Finally, Bob decrypts the re-encrypted ciphertext using his key.
bob_cleartext = decrypt_reencrypted(receiving_sk=bobs_secret_key,
delegating_pk=alices_public_key,
capsule=bob_capsule,
verified_cfrags=cfrags,
ciphertext=ciphertext) #Bob使用sk_B解密得到明文
print("bob_cleartext:",bob_cleartext)
assert bob_cleartext == plaintext
TPRELib共包含6個演演算法,分別為金鑰對生成演演算法【GenerateTpreKeyPair】、重加密金鑰生成【GenerateReKey】、加密【Encrypt】、解密演演算法【Decrypt】、重加密演演算法【ReEncrypt】、解密重加密密文演演算法【DecryptFrage】:
假設資料持有方為A,接收方為B,代理者(節點)為proxy
下面是門限代理重加密演演算法執行流程:
\(capsule\):包含對稱金鑰資訊
\(ct\):使用對稱加密的密文
假設Alice為資料所有方,Bob為資料需求方,代理者(代理節點)為proxy
設定引數,為了簡單起見,我們將省略其餘函數中的公共引數。
\(ReKeyGen(sk_A,pk_B,N,t)\):輸入\((pk_A,sk_A)\) ,\(pk_B\),代理節點數\(N\)和閾值\(t\),重加密金鑰生成演演算法計算出Alice和Bob之間的\(N\)個代理節點的重加密金鑰:
隨機抽樣\(x_A\in Z_q\)並計算\(X_A=g^{x_A}\)
計算\(d=H_3(X_A,pk_B,(pk_B)^{x_A})=H_3(g^{x_a},g^b,g^{bx_a})\)
在\(f_i\in Z_q\)中隨機抽取\(t-1\)個元素,其中\(i\in [1,t-1]\),並計算\(f_0=(a*d^{-1}) mod q\)
【密祕分享】在\(r-1\)階的\(Z_q[x]\)中構造一個多項式\(f(x)\) ,使得\(f(x)=f_0+f_1x+f_2x^2+...+f_{t-1}x^{t-1}\)
計算\(D=H_6(pk_A,pk_B,pk_B^a)=H_6(g^a,g^b,g^{ba})\)
初始化集合\(KF=0\),重複\(N\)次
封裝演演算法\(Encapsulate(pk_A)\):輸入公鑰\(pk_A=g^a\)
首先選擇亂數\(r,u\in Z_q\) ,並計算\(E=g^r\)和\(V=g^u\)
計算\(s=u+r*H_2(E,V)\),計算\(K=KDF((pk_A)^{r+u})=KDF(g^{a(r+u)})\),該元組\((E,V,s)\)被稱為膠囊(capsule)
最後輸出\((K,capsule)\),其中\(K\)為對稱金鑰
檢查函數\(CheckCapsule(capsule)\):在輸入一個\(capsule=(E,V,s)\)時,通過檢查以下方程是否成立來檢查該膠囊的有效性:\(g^{s}\overset{?}{=}V\cdot E^{H_{2}(E,V)}\)
解封演演算法\(Decapsulate(sk_A,capsule)\):輸入金鑰\(sk_A=a\)和原始膠囊\(capsule=(E,V,s)\)
封裝演演算法是根據公鑰\(pk_A\)生成一個對稱金鑰\(K\)和一個capsule
解封裝演演算法可以根據capsule還原出對稱金鑰\(K\)
本演演算法可以選擇任意素數域橢圓曲線,例如Secp256k1和sm2p256v1,其中sm2p256v1是我國國產密碼學演演算法SM2使用的橢圓曲線【2.4】
源程式:https://github.com/secretflow/yacl/tree/main/yacl/crypto/primitives/tpre
TPRE是umbral的替代品,它是用C++實現的,用SM2、SM3和SM4取代了ECC和AES等演演算法。
可用於一下場景:
資料安全共用
資料安全授權
分散式金鑰管理
模組功能:
由於tpre是放在隱語的yacl庫中,安裝編譯參考「yacl使用」
1、金鑰生成
std::unique_ptr<EcGroup> ecc_group = EcGroupFactory::Create("sm2"); //初始化引數,生成SM2對應的曲線引數
Keys keys;
std::pair<Keys::PublicKey, Keys::PrivateKey> key_pair_alice =
keys.GenerateKeyPair(ecc_group);
// According to the official SM2 document
// The hexadecimal of generator is:
// Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589
// 334C74C7
// Gy = BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5
// 2139F0A0
// When converting it to decimal, we have :
// "(2296314654723705055947953136255007457880
// 2567295341616970375194840604139615431,
// "85132369209828568825618990617112496413088
// 388631904505083283536607588877201568)";
std::string generator_str =
"(2296314654723705055947953136255007457880256729534161697037519"
"4840604139"
"615431, "
"85132369209828568825618990617112496413088388631904505083283536"
"6075888772"
"01568)";
EXPECT_EQ(ecc_group->GetAffinePoint(key_pair_alice.first.g).ToString(),
generator_str); //檢測generator_str點是否在曲線上
std::pair<Keys::PublicKey, Keys::PrivateKey> key_pair_bob =
keys.GenerateKeyPair(ecc_group); //Bob生成一對公私鑰
std::vector<Keys::KFrag> kfrags =
keys.GenerateReKey(ecc_group, key_pair_alice.second, key_pair_alice.first,
key_pair_bob.first, 5, 4); //生成5個重加密金鑰
for (int i = 0; i < 5; i++) {
EXPECT_TRUE(kfrags[i].id > zero);
}
2、加解密
/************************* Phase 1 *************************/
// Start testing encryption and decryption functions
std::unique_ptr<EcGroup> ecc_group = EcGroupFactory::Create("sm2"); // 引數生成
std::pair<Keys::PublicKey, Keys::PrivateKey> key_pair_A =
keys.GenerateKeyPair(ecc_group); //Alice生成金鑰對
// test tpre.Encrypt
std::string message = "hellooooooooooooo, I am 63, who are you?"; //明文訊息
std::pair<Capsule::CapsuleStruct, std::vector<uint8_t>> ct_1 =
tpre.Encrypt(ecc_group, key_pair_A.first, iv, message); //使用Alice的公鑰加密得到密文(Capsule,encdata)
// test tpre.Decrypt
std::string message_1 =
tpre.Decrypt(ecc_group, ct_1.first, iv, ct_1.second, key_pair_A.second); //使用Alice的私鑰解密得到明文
// Determine if decryption was successful
EXPECT_EQ(message, message_1); //測試解密是否成功
// End testing encryption and decryption functions
3、重加密和解密
/************************* Phase 2 *************************/
// Start testing encryption, re-encryption, and decryption functions
// Second test tpre.Encrypt
std::string message_2 =
"If you were a teardrop;In my eye, For fear of losing you, I would never "
"cry. And if the golden sun, Should cease to shine its light, Just one "
"smile from you, Would make my whole world bright.";
std::pair<Capsule::CapsuleStruct, std::vector<uint8_t>> ct_2 =
tpre.Encrypt(ecc_group, key_pair_A.first, iv, message_2); //使用Alice的公鑰加密明文訊息
// test keys->GenerateReKey
std::pair<Keys::PublicKey, Keys::PrivateKey> key_pair_B =
keys.GenerateKeyPair(ecc_group); //Bob生成公私鑰
int N = 5; // Number of all participants //代理節點數
int t = 4; // Threshold //閾值大小
std::vector<Keys::KFrag> kfrags = keys.GenerateReKey(
ecc_group, key_pair_A.second, key_pair_A.first, key_pair_B.first, N, t); //生成N個重加密金鑰片段
// test tpre.ReEncrypt
std::pair<std::vector<Capsule::CFrag>, std::vector<uint8_t>> re_ct_set;
// You need to meet the number of participants to successfully decrypt,
// otherwise decryption will not be successful
for (int i = 0; i < t; i++) {
std::pair<Capsule::CapsuleStruct, std::vector<uint8_t>> ct_2_i = {
ct_2.first, ct_2.second};
std::pair<Capsule::CFrag, std::vector<uint8_t>> re_ct_i =
tpre.ReEncrypt(ecc_group, kfrags[i], ct_2_i); //使用重加密金鑰片段 重新加密密文 得到re_ct_set
std::unique_ptr<Capsule::CFrag> cfrag_i_up(
new Capsule::CFrag(re_ct_i.first));
re_ct_set.first.push_back(re_ct_i.first);
re_ct_set.second = re_ct_i.second;
}
// test tpre.DecryptFrags
std::string message_3 =
tpre.DecryptFrags(ecc_group, key_pair_B.second, key_pair_A.first,
key_pair_B.first, iv, re_ct_set); //Bob使用私鑰解密
// Determine whether decryption was successful after performing re-encryption
EXPECT_EQ(message_2, message_3);
為了適應資料要素化背景下資料流通需求場景,提出了基於國密的分散式門限代理重加密演演算法TPRE,該演演算法是由成方金科新技術實驗室密碼學研究員張曙光基於隱語社群的基礎密碼學庫yacl實現。與傳統的存取控制和許可權管理方法相比,TPRE演演算法具有以下優點:
總之,基於國密的分散式門限代理重加密演演算法TPRE在資料隱私保護和資料安全共用方面具有廣泛的應用前景和重要意義,為使用者提供了一種安全、高效、自主可控的資料共用和授權管理方案。