1. 程式人生 > 實用技巧 ># $Paillier$ 同態加密方案

# $Paillier$ 同態加密方案

背景

生成公鑰金鑰

隨機選取大素數 \(p,\ q\),保證 \((pq,\ (p - 1)\cdot (q - 1)) = 1\),計算 \(n = pq,\ \lambda = [p - 1,\ q - 1]\)

隨機選取 \(g\in \mathbb{Z_{n^2}^{*}}\),計算 \(\mu = [L(g^{\lambda}\ mod\ n^2)]^{-1}\ mod\ n\),其中 \(L(x) = \frac{x - 1}{n}\)

其中 \((n,\ g)\) 為公鑰,\((\lambda,\ \mu)\) 為私鑰

加密

將明文 \(m\) 對映成 \(0\leq m < n\)

選取 \(r\in \mathbb{Z_{n}^{*}},\ 0 < r < n\),即 \((r,\ n) = 1\)

計算密文 \(c = g^{m}\cdot r^{n}\ (mod\ n^2)\)

解密

計算 \(m = L(c^{\lambda}\ mod\ n^2)\cdot \mu\ (mod\ n)\)

正確性

\(\lambda = [p - 1,\ q - 1]\),所以對於 \(x\in \mathbb{Z_{n}}\),滿足 \(x^{\lambda}\equiv 1\ (mod\ p),\ x^{\lambda}\equiv 1\ (mod\ q)\)

,所以 \(x^{\lambda}\equiv 1\ (mod\ n)\),這是因為 \(n = [p,\ q]\)

所以有 \(g^{\lambda}\equiv 1\ (mod\ n),\ r^{\lambda}\equiv 1\ (mod\ n)\)

不妨設 \(g = 1 + qn,\ r = 1 + q'n\),滿足 \(q,\ q'\in \mathbb{Z}\)

計算

\[\begin{aligned} c^{\lambda} & \equiv g^{m\lambda}\cdot r^{n\lambda}\ (mod\ n^2)\\ & \equiv (1 + qn)^{m}\cdot (1 + q'n)^{n}\ (mod\ n^2)\\ & \equiv (1 + qnm)\cdot (1 + q'n^2)\ (mod\ n^2)\\ & \equiv 1 + qnm\ (mod\ n^2) \end{aligned} \]

所以

\[\frac{c^{\lambda} - 1}{n}\equiv qm\ ( mod\ n^2) \]

同理計算出

\[\frac{g^{\lambda} - 1}{n}\equiv q\ (mod\ n^2) \]

所以

\[m\equiv \frac{\frac{c^{\lambda} - 1}{n}\ mod\ n^2}{\frac{g^{\lambda} - 1}{n}\ mod\ n^2}\equiv qm\cdot q^{-1}\equiv m\ (mod\ n) \]

隨機選取 \(g\)

\(g = 1 + n\)

\((1 + n)^n\equiv 1 + n^2\equiv 1\ (mod\ n^2)\),這意味著 \(ord(1 + n)\mid n\)

而對於 \(1 < k < n\),有 \((1 + n)^k\equiv 1 + kn\not\equiv 1\ (mod\ n^2)\),所以 \(ord(1 + n) = n\)

進一步,令 \(g = 1 + kn,\ 0 < k < n\)

\((1 + kn)^n\equiv 1\ (mod\ n^2)\)

對於 \(1 < k' < n\),有 \((1 + kn)^{k'}\equiv 1 + k'kn\ (mod\ n^2)\),只需要 \(kk'\not\equiv 0\ (mod\ n)\) 即可,即

\[k\not\equiv p\ (mod\ n),\ k\not\equiv q\ (mod\ n) \]

所以對 \(g\) 的隨機選取,轉化為對 \(k\in \mathbb{Z_{n}}\backslash\left\{p,\ q\right\}\) 的隨機選取

程式碼

#include <bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;

const int L = 10;
const int m1 = 15, m2 = 20;
int n, p, q, lambda, mu;
LL g, nn;
vector<int> ZnStar;

LL qmul(LL a, LL b, LL mod)
{
    LL res = 0;
    while(b > 0) {
        if(b & 1) res += a, res %= mod;
        a += a, a %= mod;
        b >>= 1;
    }
    return res;
}

LL qpow(LL a, LL b, LL MOD)
{
    if(!a) return 0;
    LL ans = 1;
    while(b) {
        if(b & 1) ans = qmul(ans, a, MOD), ans %= MOD;
        a  = qmul(a, a, MOD), a %= MOD, b >>= 1;
    }
    return ans;
}

LL phi(LL m)
{
    LL ans = m;
    for(LL i = 2; i * i <= m; ++i) {
        if(m % i == 0) {
            ans -= ans / i;
            while(m % i == 0) m /= i;
        }
    }
    if(m > 1) ans -= ans / m;
    return ans;
}

LL getInv(LL a, LL MOD)
{
    return qpow(a, phi(MOD) - 1, n);
}

bool isPrime(int x)
{
    if(x == 1) return 0;
    for(int i = 2; i * i <= x; ++i)
        if(x % i == 0) return 0;
    return 1;
}

int getPrime(int L)
{
    int ans = 0;
    do {
        ans = 1;
        for(int i = 0; i < L - 2; ++i)
            ans *= 2, ans += rand() % 2;
        ans *= 2, ans += 1;
    }while(!isPrime(ans));
    return ans;
}

void getZnStar(int n)
{
    for(int i = 1; i < n; ++i)
        if(__gcd(i, n) == 1 && i != p && i != q)
            ZnStar.pb(i);
}

int getNumOfZnStar() { return ZnStar[rand() % ZnStar.size()]; }

int getLambda(int p, int q) { return (p - 1) * (q - 1) / __gcd(p - 1, q - 1); }

void getPublicKey()
{
    while(1) {
        p = getPrime(L);
        q = getPrime(L);
        lambda = getLambda(p, q);
        n = p * q;
        if(__gcd(n, lambda) == 1 && p != q) break;
    }
    nn = 1LL * n * n;
    getZnStar(n);
    g = (1 + 1LL * n * getNumOfZnStar()) % nn;
    cout << "n: " << n << endl;
    cout << "p: " << p << endl;
    cout << "q: " << q << endl;
    cout << "g: " << g << endl;
    cout << "n^2: " << nn << endl;
}

LL getLx(LL x) { return (x - 1) / n; }

void getPrivateKey()
{
    LL x = qpow(g, lambda, nn);
    LL Lx = getLx(x);
    mu = getInv(Lx, n);
    cout << "lambda: " << lambda << endl;
    cout << "mu: " << mu << endl;
}

LL encryption(int m)
{
    LL r = getNumOfZnStar();
    LL c = qmul(qpow(g, m, nn), qpow(r, n, nn), nn);
    cout << "r: " << r << endl;
    cout << "c: " << c << endl;
    return c;
}

LL decryption(LL c)
{
    cout << c << endl;
    LL temp = qpow(c, lambda, nn);
    LL Lx = getLx(temp) % n;
    LL plaintext = Lx * mu % n;
    cout << "plaintext: " << plaintext << endl;
    return plaintext;
}
int main()
{
    srand((unsigned)time(NULL));
    getPublicKey();
    getPrivateKey();
    LL ciphertext1 = encryption(m1);
    LL ciphertext2 = encryption(m2);
    LL plaintext = decryption(qmul(ciphertext1, ciphertext2, nn));
    return 0;
}