基於shamir門限的秘密分存
一、秘密分存
將一個秘密拆分成幾塊,分給幾個人保管,每個人保管一塊,只有當n塊組合在一起才能恢復出秘密,單獨的一塊對自己是沒有用的,n即為門限。
二、shamir門限方案
- SHAMIR門限方案是基於多項式的拉格朗日插值公式的,即已知Φ(x)在k個互相不同的點的函數值Φ(xi),可構造k-1次插值多項式為f(x)=∑Φ(xi)∏(x-xj)/(xi-xj)。
- 如:已知Φ(2)=10,Φ(5)=3,Φ(4)=7,求Φ(1),則可以構造f(x)=10[(x-5)(x-4)]/[(2-5)(2-4)]+3[(x-2)(x-4)]/[(5-2)(5-4)]+7*[(x-2)(x-5)]/[(4-2)(4-5)],帶入x=1即得
- 因此,可以在一個GF(q)上構造一個多項式f(x)(q為一個隨機大素數),秘密s為之上的一個隨機數,n個參與者分到子密鑰f(i),有k個能還原,即可采用拉格朗日插值公式構造多項式,f(0)即為秘密。
三、說明
- 分解秘密過程:首先得到要分解的秘密,為一個整數(要小於2,147,483,647),然后利用Java中的函數BigInteger q = BigInteger.probablePrime(bit_num, rand)生成一個大於秘密的隨機大整數,作為模值,rand為隨機數因子,bit_num為生成的隨機大素數的二進制的位數。之后生成隨機的多項式的系數和要分配的子密鑰(子密鑰在程序中設置小於100),之后根據構造好的多項式算出子密鑰的值,即子密鑰包括子密鑰及子密鑰的值。
- 還原秘密過程:先輸入各個子密鑰,再輸入各子密鑰對應的模值。因為秘密為f(0),實際上只要求出多項式的常數即可,得到每個拉格郎日插值多項式的各個部分的常數,相加模p即為秘密。
- 用廣義歐幾里得除法求逆時,這兩個數都為正數,開始忽視了這一點,導致寫求逆函數時出現錯誤
- java的BigInteger類,可以用該類的方法計算大數的加減乘除等運算,相關方法如下:
BigInteger.add(b); //大整數加法,b也為一個大數
BigInteger.subtract(b); //大整數減法
BigInteger.multiply(b); //大整數乘法
BigInteger.divide(b); //大整數除法(取整)
BigInteger.remainder(b); //大整數取模
BigInteger.pow(exponent); //大整數a的exponent次冪
結果

四、源代碼
package shamir;
import java.math.BigInteger;
import java.util.Random;
import java.util.Scanner;
public class Shamir {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (true) {
System.out.println("分解秘密請選擇1,還原秘密選擇2, 退出選擇3");
int flag = in.nextInt();
if (flag == 1) {
long a[] = new long[100];// 放系數
long f[] = new long[100];// 放密鑰的值
long dian[] = new long[100];// 放密鑰的點
System.out.println("輸入你的秘密");
int secret = in.nextInt();// 輸入為一個數小於2,147,483,647
Random rand = new Random();
int bit_num = rand.nextInt(29) + 2;// 隨機2到31位
BigInteger q = BigInteger.probablePrime(bit_num, rand);// 生成一個隨機大素數,必須大於輸入的隨機整數
long p = q.longValue();
while (p < secret) {// 如果不大於輸入的數
bit_num = rand.nextInt(29) + 2;
q = BigInteger.probablePrime(bit_num, rand);
p = q.longValue();
}
System.out.println("模為 " + p);
System.out.println("輸入門限值");// 門限值小於等於100
int k = in.nextInt();
System.out.println("輸入得到幾個子密鑰");// 子密鑰值小於等於100
int zi_key = in.nextInt();
for (int i = 0; i < k - 1; i++) {
a[i] = (long) (Math.random() * p);// 生成偽隨機系數
if (a[i] == 0) {// 生成的隨機系數不能為0
i--;
}
}
for (int i = 0; i < zi_key; i++) {
dian[i] = (long) (Math.random() * 100);// 生成100以內隨機點,不能重復,如果大了,可能在以后的運算中超出數據類型范圍,導致錯誤
if (dian[i] == 0) {// 生成的隨機點不能為0
i--;
}
}
System.out.println("子密鑰為(確保子密鑰沒有重復,如果有請重新生成) ");
for (int i = 0; i < zi_key; i++) {// 計算zi_key個子密鑰
f[i] = secret;
for (int j = 0; j < k - 1; j++) {// 一個子密鑰
long zhishu = j + 1;
f[i] = Math.floorMod((long) (a[j] * Math.pow(dian[i], zhishu)) + f[i], p);
}
System.out.print(dian[i] + " ");
System.out.println(f[i]);
}
} else if (flag == 2) {
System.out.println("輸入模值");
int m = in.nextInt();
System.out.println("輸入門限");
int k = in.nextInt();
long key_d[] = new long[k];
long key_z[] = new long[k];
long num_x[] = new long[k];
long num_s[] = new long[k];
long s = 0;
System.out.println("請輸入密鑰(先輸入所有的點,再輸入各點對應的值) ");
for (int i = 0; i < k; i++) {
key_d[i] = in.nextLong();
}
for (int i = 0; i < k; i++) {
key_z[i] = in.nextLong();
}
for (int i = 0; i < k; i++) {
num_x[i] = 1;
num_s[i] = 1;
for (int j = 0; j < k; j++) {
if (j == i) {
j++;
}
if (j == k) {
break;
}
num_x[i] = num_x[i] * (key_d[i] - key_d[j]);
num_s[i] = (-key_d[j]) * num_s[i];
}
num_x[i] = qiu_ni(num_x[i], m);
s = num_x[i] * num_s[i] * key_z[i] + s;
}
s = Math.floorMod(s, m);
System.out.println("秘密為 " + s);
} else {
break;
}
}
}
// 求逆函數
static long qiu_ni(long a, long b) {// 最后s[1]為s即逆元,t[1]為t
long s[] = { 1, 0, 0 };
long t[] = { 0, 1, 0 };
long r1 = a;
long r2 = b;
long tmp;
int i = (int) (r1 / r2);
tmp = r2;
r2 = Math.floorMod(r1, r2);
r1 = tmp;
while (r2 != 0) {
s[2] = s[0] - i * s[1];
t[2] = t[0] - i * t[1];
s[0] = s[1];
s[1] = s[2];
t[0] = t[1];
t[1] = t[2];
if (r2 != 0)
i = (int) (r1 / r2);
tmp = r2;
r2 = Math.floorMod(r1, r2);
r1 = tmp;
}
return s[1];
}
}
