Schnorr簽名(模指數)的實現java
和ElGama數字簽名一樣,Schnorr數字簽名方案也是基於離散對數。
Schnorr數字簽名主要工作不依賴於消息,生成簽名過程與消息相關的部分需要進行2n位長度的整數與n位長度的整數相乘。
1、算法描述
算法參數分析
該方案基於素數模p,且p-1包含大素數因子q,即 p-1≡0(mod q), p一般大約取 p=2^1024, q一般大約取 q=2^160,
p是1024位整數,q是160位整數,正好等於SHA-1中Hash值的長度。
具體算法流程
第一步生成公鑰和私鑰對,如下:
選擇素數p和q,使得q是p-1的素因子
選擇整數a,使 α^q=1 mod p, 使a,p,q 構成全局公鑰參數,在用戶組內的每個用戶都可以取此值
選擇隨機整數s,0<s<q ,作爲用戶的私鑰
計算 v=a^-s mod p, 作爲用戶的公鑰
簽名
選擇隨機整數r,0<r<q, 並計算 x=a^r mod p, 該過程和待簽名消息M無關
將x附在消息後面一起計算Hash值e, e=H(M||x)
計算 y=(r+ s*e) mod q , 簽名包括(e,y)對
其他用戶驗證簽名:
計算 x'= a^y * v^e mod p
驗證是否 e=H(M||x')
對於該驗證過程有:
x’≡a^y * v^e ≡a^y *a^-se ≡a^ y-se ≡ a^r ≡x mod p
所以 H(M||x’)=H(M||x)
2、算法的實現(java)
/**
* @author: mulming
* @ClassName: SchnorrSignature
* @date: 2020年6月16日 下午9:25:09
* @Description:schnorr簽名
*/
public class SchnorrSignature {
/**
* @Author: mulming
* @Description: 系統初始化階段,把初始化的密碼公共參數存放到文件中去
* @param blq:選擇的q的bit長度
* @Date:下午9:28:20
*/
public static void initPara(int blq) {
File file = new File(PARAM_PATH);
if(file.exists()) {
System.out.println("已經存在初始化參數,爲不影響已經頒發的證書,如果你強制要重新產生參數,請備份所有文件到其他路徑下,並重新生產密鑰對並重新簽名");
}else {
System.out.println("系統初始化中,生產公共參數... ...");
BigInteger one = new BigInteger("1");
BigInteger two = new BigInteger("2");
BigInteger q, qp, p, a, g;
int certainty = 100;
SecureRandom sr = new SecureRandom();
// blq長度的q, q是p-1的素因子
//生成BigInteger僞隨機數,它可能是(概率不小於1 - 1/2certainty)一個具有指定 bitLength 的素數
q = new BigInteger(blq, certainty, sr);
qp = BigInteger.ONE;
do { // 選擇一個素數 p
p = q.multiply(qp).multiply(two).add(one);
if(p.isProbablePrime(certainty))
break;
qp = qp.add(BigInteger.ONE);
} while(true);
while(true) {
a = (two.add(new BigInteger(blq, 100, sr))).mod(p);// (2+x) mod p
BigInteger ga = (p.subtract(BigInteger.ONE)).divide(q);// (p-1)/q
g = a.modPow(ga, p); // a^ga mod p = 1
if(g.compareTo(BigInteger.ONE) != 0) // g!=1
break;
}
// 存放公共參數
List<String> transArryToLi = KeyPairOperate.transArryToLi(new String[] {"blq=" + blq,"q=" + q, "p=" + p, "g=" + g});
KeyPairOperate.writePublicKeyToFile(PARAM_PATH, transArryToLi, false);
System.out.println("...");
System.out.println("初始化完成!");
}
}
/**
* @Author: mulming
* @Description: 爲用戶生成公私鑰對
* @param user:傳入用戶的身份
* @Return:void
* @Date:上午9:32:18
*/
public static void generateKeyForUser(String user) {
File file = new File(PERFIX_PATH + user + ".properties");
if(file.exists()) {
System.out.println(user + "已經頒發了密鑰,請備份所有文件到其他路徑下,並重新簽名");
}else {
System.out.println("密鑰頒發中,請稍後");
System.out.println("... ...");
BigInteger sk,pk;// 私鑰和公鑰
int blq = Integer.parseInt(KeyPairOperate.getDataFromFile(PARAM_PATH, "blq"));
SecureRandom sr = new SecureRandom();
// 隨機數作爲私鑰
sk = new BigInteger(blq, sr);
// 私鑰的話名字命名
List<String> toLiSK = KeyPairOperate.transArryToLi(new String[] {"sk=" + sk});
KeyPairOperate.writePublicKeyToFile(PERFIX_PATH + user + ".properties", toLiSK, false);
BigInteger g = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "g"));
BigInteger p = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "p"));
// 公鑰
pk = g.modPow(sk, p);// g^w mod p -- 注意這兒是正,所以下面驗證的時候是用的負
List<String> toLiPK = KeyPairOperate.transArryToLi(new String[] {user + "=" + pk});
KeyPairOperate.writePublicKeyToFile(PUBLIC_KEY_PATH, toLiPK, true);
System.out.println(user + " 密鑰頒發完成");
}
}
/**
* @Author: mulming
* @Description: 產生簽名
* @param sourcefilePath : 待簽名文件路徑
* @param user:用戶身份
* @Date:下午10:41:37
*/
public static void makeSign(String sourcefilePath, String user) {
System.out.println(user+ "的文件" + KeyPairOperate.getFileName(sourcefilePath) + " 簽名開始");
System.out.println("... ...");
BigInteger q = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "q")); // 素數 q
BigInteger p = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "p")); // 素數 p
BigInteger g = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "g")); // q的原根 a
// 私鑰
BigInteger sk = new BigInteger(KeyPairOperate.getDataFromFile(PERFIX_PATH + user + ".properties", "sk")); // 私鑰
SecureRandom sr = new SecureRandom();
BigInteger r, x, e, y;
r = new BigInteger(q.bitLength(), sr); // 隨機數
x = g.modPow(r, p); // g^r mod p
// e=H(M||x)
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(Files.readAllBytes(Paths.get(sourcefilePath)));
md5.update(x.toString().getBytes());
byte[] digest = md5.digest();
// e 將BigInteger的符號大小表示法轉換成一個BigInteger值
e = new BigInteger(1, digest);
// y s2 = r
y = (r.subtract(sk.multiply(e))).mod(q);
List<String> transArryToLi = KeyPairOperate.transArryToLi(new String[] {"e="+e,"y="+y});
String fileName =PERFIX_PATH + user + "_sign_" + KeyPairOperate.getFileName(sourcefilePath) + ".properties";
KeyPairOperate.writePublicKeyToFile(fileName, transArryToLi, false);
System.out.println(user+ "的文件" + KeyPairOperate.getFileName(sourcefilePath) + "簽名成功 !");
} catch (Exception e1) {
e1.printStackTrace();
}
}
/**
* @Author: mulming
* @Description: 驗證簽名
* @param sourcePath : 原文件路徑
* @param user : 用戶名
* @Return:void
* @Date:上午11:07:04
*/
public static void checkSign(String sourcefilePath, String user) {
System.out.println("驗證簽名");
BigInteger p = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "p")); // 素數 p
BigInteger g = new BigInteger(KeyPairOperate.getDataFromFile(PARAM_PATH, "g")); // q的原根 a
BigInteger pk = new BigInteger(KeyPairOperate.getDataFromFile(PUBLIC_KEY_PATH, user));// 公鑰
String fileName =PERFIX_PATH + user + "_sign_" + KeyPairOperate.getFileName(sourcefilePath) + ".properties";
BigInteger e = new BigInteger(KeyPairOperate.getDataFromFile(fileName, "e")); // e 簽名信息1: 產生的簽名信息
BigInteger y = new BigInteger(KeyPairOperate.getDataFromFile(fileName, "y"));; // y 簽名信息2: 加密後的消息
// 計算的 x'
BigInteger x1 = g.modPow(y, p); // g^y mod p -- y
BigInteger x2 = (pk.modPow(e, p)).mod(p); // pk^e mod p
BigInteger x = x1.multiply(x2).mod(p); // x1*x2 mod p = (g^y)*(pk^e)mod p
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(Files.readAllBytes(Paths.get(sourcefilePath)));
md5.update(x.toString().getBytes());
byte[] digest = md5.digest();
BigInteger h = new BigInteger(1, digest);
System.out.println("... ...");
if(e.equals(h))
System.out.println(user+ "的文件" + KeyPairOperate.getFileName(sourcefilePath) + "驗證通過 !");
else
System.out.println(user+ "的文件" + KeyPairOperate.getFileName(sourcefilePath) + "驗證失敗 !");
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
** 注:如果需要完整一點的代碼,可以在下面留下郵箱,我發送。**