本文詳細學習Lifted ElGamal 門限加密演算法
門限加密體制
(1)門限加密是可以抗合謀的
(2)表現在私鑰分為\(n\)份,至少需要\(t\)份才能解密成功,叫做(t-n)門限。類似於“祕密分享”。
ElGamal演算法
(1)源自【A public key cryptosystem and a signature scheme based on discrete logarithms】給出了加法和乘法同態性的定義,其中加法同態只能用於小的明文域。
(2)\(G\)是階為\(p\)的群,\(g\)是群\(G\)的生成元,基於\(DDH\)問題,公鑰是\(PK=(G,p,g,h)\),私鑰是\(SK=s\),其中\(g^s=h\)。
(3)加密:選擇一個隨機數\(r\in Z_p\),計算\(Enc_{PK}(m,r)=<g^r,h^r*g^m>\);解密:密文\(c=<\alpha,\beta>\),計算\(g^m=\beta*\alpha^{-s}\),最後得到\(m\)【\(m\)只能是小資料,如果太大則根據離散對數問題\(m\)是難解的】
原論文中給出的公鑰加密方案是:
參考:ElGamal演算法
Lifted ElGamal 門限加密演算法
(1)源自【A public key cryptosystem and a signature scheme based on discrete logarithms】
(2)這裡將明文放在了指數上,恢復明文,就需要計算離散對數,所以\(\rho\)選取要很小,不然很難恢復明文。
(3)通過查表(離散對數表)來獲取結果,這裡對應上面的exhaustive search
(4)金鑰生成,加密,解密和原ElGamal類似。
開源庫
該庫實現了具有加法同態性的Lifted-ElGamal演算法【A Public Key Cryptosystem and a Signature Scheme Based on Discrete Logarithms】和實現了對受限明文空間的零知識證明【Methods for Restricting Message Space in Public-Key Encryption】。
基於Lifted-ElGamal演算法給出了一個投票系統。
安裝
環境:MacOS
(1)依賴庫
OpenSSL:安裝參考
GMP(libgmp-dev)
(2)下載
mkdir work
cd work
git clone git://github.com/aistcrypt/Lifted-ElGamal.git
git clone git://github.com/herumi/xbyak.git
git clone git://github.com/herumi/mie.git
git clone git://github.com/herumi/cybozulib.git
git clone git://github.com/herumi/cybozulib_ext.git
#如果卡的話,換成 https://github.com/***.git
其中:
- cybozulib_ext是VC(Visual C++)中使用OpenSSL和GMP庫所需的
- Xbyak在Intel系CPU中提升運算速度
- Linux通過apt-get等獲取OpenSSL和libgmp-dev
測試
(1)有限域\(F_p\)上進行測試
CYBOZU_TEST_AUTO(testFp)
{
typedef mie::FpT<mie::Gmp, TagFp> Zn;
typedef mie::ElgamalT<Fp, Zn> ElgamalFp;
/*
Zn = (Z/mZ) - {0}
*/
const int m = 65537;
{
std::ostringstream os;
os << m;
Fp::setModulo(os.str());
}
{
std::ostringstream os;
os << m - 1;
Zn::setModulo(os.str());
}
ElgamalFp::PrivateKey prv;
/*
3^(m-1) = 1
*/
const int f = 3;
{
Fp x(f);
Fp::power(x, x, m - 1);
CYBOZU_TEST_EQUAL(x, 1);
}
prv.init(f, 17, rg);
const ElgamalFp::PublicKey& pub = prv.getPublicKey();
const int m1 = 12345;
const int m2 = 17655;
ElgamalFp::CipherText c1, c2;
pub.enc(c1, m1, rg);
pub.enc(c2, m2, rg);
// BitVector
{
cybozu::BitVector bv;
c1.appendToBitVec(bv);
ElgamalFp::CipherText c3;
c3.fromBitVec(bv);//c3複製c1
CYBOZU_TEST_EQUAL(c1.c1, c3.c1);
CYBOZU_TEST_EQUAL(c1.c2, c3.c2);
}
Zn dec1, dec2;
prv.dec(dec1, c1);
prv.dec(dec2, c2);
// dec(enc) = id,判斷是否解密成功
CYBOZU_TEST_EQUAL(dec1, m1);
CYBOZU_TEST_EQUAL(dec2, m2);
// iostream
{
ElgamalFp::PublicKey pub2;
ElgamalFp::PrivateKey prv2;
ElgamalFp::CipherText cc1, cc2;
{
std::stringstream ss;
ss << prv;
ss >> prv2;
}
Zn d;
prv2.dec(d, c1);
CYBOZU_TEST_EQUAL(d, m1);
{
std::stringstream ss;
ss << c1;
ss >> cc1;
}
d = 0;
prv2.dec(d, cc1);
CYBOZU_TEST_EQUAL(d, m1);
{
std::stringstream ss;
ss << pub;
ss >> pub2;
}
pub2.enc(cc2, m2, rg);
prv.dec(d, cc2);
CYBOZU_TEST_EQUAL(d, m2);
}
// enc(m1) enc(m2) = enc(m1 + m2)
c1.add(c2);
prv.dec(dec1, c1);
CYBOZU_TEST_EQUAL(dec1, m1 + m2);
// enc(m1) x = enc(m1 + x)
const int x = 555;
pub.add(c1, x);
prv.dec(dec1, c1);
CYBOZU_TEST_EQUAL(dec1, m1 + m2 + x);
// rerandomize
c1 = c2;
pub.rerandomize(c1, rg);
// verify c1 != c2
CYBOZU_TEST_ASSERT(c1.c1 != c2.c1);
CYBOZU_TEST_ASSERT(c1.c2 != c2.c2);
prv.dec(dec1, c1);
// dec(c1) = dec(c2)
CYBOZU_TEST_EQUAL(dec1, m2);
// check neg
{
ElgamalFp::CipherText c;
Zn m = 1234;
pub.enc(c, m, rg);
c.neg();
Zn dec;
prv.dec(dec, c);
CYBOZU_TEST_EQUAL(dec, -m);
}
// check mul
{
ElgamalFp::CipherText c;
Zn m = 1234;
int x = 111;
pub.enc(c, m, rg);
c.mul(x);
Zn dec;
prv.dec(dec, c);
m *= x;
CYBOZU_TEST_EQUAL(dec, m);
}
// check negative value
for (int i = -10; i < 10; i++) {
ElgamalFp::CipherText c;
const Zn mm = i;
pub.enc(c, mm, rg);
Zn dec;
prv.dec(dec, c, 1000);
CYBOZU_TEST_EQUAL(dec, mm);
}
// isZeroMessage
for (int m = 0; m < 10; m++) {
ElgamalFp::CipherText c0;
pub.enc(c0, m, rg);
if (m == 0) {
CYBOZU_TEST_ASSERT(prv.isZeroMessage(c0));
} else {
CYBOZU_TEST_ASSERT(!prv.isZeroMessage(c0));
}
}
// zkp
{
ElgamalFp::Zkp zkp;
ElgamalFp::CipherText c;
cybozu::crypto::Hash hash(cybozu::crypto::Hash::N_SHA256);
pub.encWithZkp(c, zkp, 0, hash, rg);
CYBOZU_TEST_ASSERT(pub.verify(c, zkp, hash));
zkp.s0 += 1;
CYBOZU_TEST_ASSERT(!pub.verify(c, zkp, hash));
pub.encWithZkp(c, zkp, 1, hash, rg);
CYBOZU_TEST_ASSERT(pub.verify(c, zkp, hash));
zkp.s0 += 1;
CYBOZU_TEST_ASSERT(!pub.verify(c, zkp, hash));
CYBOZU_TEST_EXCEPTION_MESSAGE(pub.encWithZkp(c, zkp, 2, hash, rg), cybozu::Exception, "encWithZkp");
}
}
(2)測試加解密和同態計算
CYBOZU_TEST_AUTO(testEc)
{
typedef mie::FpT<mie::Gmp, TagEc> Zn;
typedef mie::ElgamalT<Ec, Zn> ElgamalEc;
Fp::setModulo(para.p);
Zn::setModulo(para.n);
Ec::setParam(para.a, para.b);
const Fp x0(para.gx);
const Fp y0(para.gy);
const size_t bitLen = Zn(-1).getBitLen();
const Ec P(x0, y0);
/*
Zn = <P>
*/
ElgamalEc::PrivateKey prv;
prv.init(P, bitLen, rg);
prv.setCache(0, 60000);
const ElgamalEc::PublicKey& pub = prv.getPublicKey();
const int m1 = 12345;
const int m2 = 17655;
ElgamalEc::CipherText c1, c2;
pub.enc(c1, m1, rg);
pub.enc(c2, m2, rg);
// BitVector
{
cybozu::BitVector bv;
c1.appendToBitVec(bv);
ElgamalEc::CipherText c3;
c3.fromBitVec(bv);
CYBOZU_TEST_EQUAL(c1.c1, c3.c1);
CYBOZU_TEST_EQUAL(c1.c2, c3.c2);
}
Zn dec1, dec2;
prv.dec(dec1, c1);
prv.dec(dec2, c2);
// dec(enc) = id
CYBOZU_TEST_EQUAL(dec1, m1);
CYBOZU_TEST_EQUAL(dec2, m2);
// iostream
{
ElgamalEc::PublicKey pub2;
ElgamalEc::PrivateKey prv2;
ElgamalEc::CipherText cc1, cc2;
{
std::stringstream ss;
ss << prv;
ss >> prv2;
}
prv.setCache(-200, 60000);
Zn d;
prv2.dec(d, c1);
CYBOZU_TEST_EQUAL(d, m1);
{
std::stringstream ss;
ss << c1;
ss >> cc1;
}
d = 0;
prv2.dec(d, cc1);
CYBOZU_TEST_EQUAL(d, m1);
{
std::stringstream ss;
ss << pub;
ss >> pub2;
}
pub2.enc(cc2, m2, rg);
prv.dec(d, cc2);
CYBOZU_TEST_EQUAL(d, m2);
}
// enc(m1) enc(m2) = enc(m1 + m2)
c1.add(c2);
prv.dec(dec1, c1);
CYBOZU_TEST_EQUAL(dec1, m1 + m2);
// enc(m1) x = enc(m1 + x)
const int x = 555;
pub.add(c1, x);
prv.dec(dec1, c1);
CYBOZU_TEST_EQUAL(dec1, m1 + m2 + x);
// rerandomize
c1 = c2;
pub.rerandomize(c1, rg);
// verify c1 != c2
CYBOZU_TEST_ASSERT(c1.c1 != c2.c1);
CYBOZU_TEST_ASSERT(c1.c2 != c2.c2);
prv.dec(dec1, c1);
// dec(c1) = dec(c2)
CYBOZU_TEST_EQUAL(dec1, m2);
// check neg
{
ElgamalEc::CipherText c;
Zn m = 1234;
pub.enc(c, m, rg);
c.neg();
Zn dec;
prv.dec(dec, c);
CYBOZU_TEST_EQUAL(dec, -m);
}
// check mul
{
ElgamalEc::CipherText c;
Zn m = 123;
int x = 111;
pub.enc(c, m, rg);
Zn dec;
prv.dec(dec, c);
c.mul(x);
prv.dec(dec, c);
m *= x;
CYBOZU_TEST_EQUAL(dec, m);
}
// check negative value
for (int i = -10; i < 10; i++) {
ElgamalEc::CipherText c;
const Zn mm = i;
pub.enc(c, mm, rg);
Zn dec;
prv.dec(dec, c, 1000);
CYBOZU_TEST_EQUAL(dec, mm);
}
// isZeroMessage
for (int m = 0; m < 10; m++) {
ElgamalEc::CipherText c0;
pub.enc(c0, m, rg);
if (m == 0) {
CYBOZU_TEST_ASSERT(prv.isZeroMessage(c0));
} else {
CYBOZU_TEST_ASSERT(!prv.isZeroMessage(c0));
}
}
// zkp
{
ElgamalEc::Zkp zkp;
ElgamalEc::CipherText c;
// cybozu::Sha1 hash;
cybozu::crypto::Hash hash(cybozu::crypto::Hash::N_SHA256);
pub.encWithZkp(c, zkp, 0, hash, rg);
CYBOZU_TEST_ASSERT(pub.verify(c, zkp, hash));
zkp.s0 += 1;
CYBOZU_TEST_ASSERT(!pub.verify(c, zkp, hash));
pub.encWithZkp(c, zkp, 1, hash, rg);
CYBOZU_TEST_ASSERT(pub.verify(c, zkp, hash));
zkp.s0 += 1;
CYBOZU_TEST_ASSERT(!pub.verify(c, zkp, hash));
CYBOZU_TEST_EXCEPTION_MESSAGE(pub.encWithZkp(c, zkp, 2, hash, rg), cybozu::Exception, "encWithZkp");
}
// cache
{
const int m1 = 9876;
const int m2 = -3142;
ElgamalEc::CipherText c1, c2;
pub.enc(c1, m1, rg);
pub.enc(c2, m2, rg);
prv.setCache(-10000, 10000);
int dec1 = prv.dec(c1);
int dec2 = prv.dec(c2);
CYBOZU_TEST_EQUAL(m1, dec1);
CYBOZU_TEST_EQUAL(m2, dec2);
c1.add(c2);
bool b;
int dec = prv.dec(c1, &b);
CYBOZU_TEST_EQUAL(m1 + m2, dec);
CYBOZU_TEST_ASSERT(b);
prv.clearCache();
prv.dec(c1, &b);
CYBOZU_TEST_ASSERT(!b);
}
// benchmark
{
int m = 12345;
ElgamalEc::CipherText c;
CYBOZU_BENCH("enc", pub.enc, c, m, rg);
prv.setCache(0, 20000);
CYBOZU_BENCH("dec", prv.dec, c);
CYBOZU_BENCH("rand", pub.rerandomize, c, rg);
}
}
同態計算:
(1)enc(m1) enc(m2) = enc(m1 + m2)
(2)enc(m1) x = enc(m1 + x)
投票例子
介紹
每個投票者對“0”或“1”進行加密,並單獨將其密文傳送到伺服器,伺服器計算結果,而不知道每次投票或結果本身,示例程式碼模擬了該方案。
編譯後得到檔案:vote_tool.exe
執行
執行命令:
vote_tool.exe [opt] mode mode: select any one of init/vote/count/open -l: input a bit vector
(1)初始化
vote_tool.exe init
初始化系統並生成公鑰(vote\u pub.txt)和金鑰(vote\u prv.txt)。secp192k1用作EC ElGamal加密的引數。
(2)投票
vote_tool.exe vote [-l a bit vector]
輸入一個長度為n 位向量v[i](1bit),表示第i個投票人的投票
使用公鑰對v[i]加密,並打亂密文序列的順序。然後將每個密文儲存在vote_0.txt,...,vote_n.txt中的任何一個檔案中。由於順序被打亂了,伺服器無法檢測哪個檔案包含誰的投票。此過程模擬每個投票者單獨傳送使用自己的公鑰加密的密文。
(3)統計
vote_tool.exe count
該程式從檔案中讀取所有密文,並在不解密的情況下檢查是否是“0”或“1”加密的。這是通過使用第三方庫提供的非互動式零知識證明來實現的。該程式在不解密的情況下聚合密文並驗證所有密文。
(4)開啟
vote_tool.exe open
該程式解密密文寫入的result.txt,並在控制檯上顯示結果。
結果:
PamdeMacBook-Air:bin pam$ ./vote_toold.exe init
mode=init
make privateKey=vote_prv.txt, publicKey=vote_pub.txt
PamdeMacBook-Air:bin pam$ ./vote_toold.exe vote -l 101010011mode=vote
voters=101010011
shuffle
each voter votes
make vote_5.txt
make vote_1.txt
make vote_6.txt
make vote_4.txt
make vote_7.txt
make vote_8.txt
make vote_0.txt
make vote_2.txt
make vote_3.txt
PamdeMacBook-Air:bin pam$ ./vote_toold.exe count
mode=count
aggregate votes
add vote_0.txt
add vote_1.txt
add vote_2.txt
add vote_3.txt
add vote_4.txt
add vote_5.txt
add vote_6.txt
add vote_7.txt
add vote_8.txt
create result file : vote_ret.txt
PamdeMacBook-Air:bin pam$ ./vote_toold.exe open
mode=open
result of vote count 5
原始碼
參考
1、集合交集問題的安全計算
2、Simple, Fast Malicious Multiparty Private Set Intersection