Java安全之數字簽名
數字簽名是一種安全措施,分爲:消息摘要和消息簽名。
概念:
1、消息摘要:是一種算法,分爲MD5/SHA算法,無論原始數據多長,消息摘要的結果都是固定長度的;原始數據任意bit位的變化,都會導致消息摘要的結果有很大的不同,且根據結果推算出原始數據的概率極低。消息摘要可以看作原始數據的指紋,指紋不同則原始數據不同。主要作用用來防止消息在傳遞途中被“第三者”篡改了。
2、消息簽名:其基礎是公鑰和私鑰的非對稱加密,發送者使用私鑰加密消息摘要,接收者使用公鑰解密消息摘要以驗證簽名是否是某個人的。主要作用是驗證發消息者的身份,確保消息來源的可靠性。
圖解:
1、鮑勃有兩把鑰匙,一把是公鑰,另一把是私鑰。
2、鮑勃把公鑰送給他的朋友們—-帕蒂、道格、蘇珊—-每人一把。
3、鮑勃給蘇珊寫信,決定採用 “數字簽名”。他寫完後先用Hash函數,生成信件的摘要(digest)
這個過程用到的哈希算法主要有MD5算法和SHA算法。
4、然後,鮑勃使用私鑰,對這個摘要加密,生成”消息簽名”(signature)。
5、鮑勃將這個簽名,附在信件下面,一起發給蘇珊。
6、蘇珊收信後,取下消息簽名,用鮑勃的公鑰解密,得到信件的摘要。由此證明,這封信確實是鮑勃發出的。
7、蘇珊再對信件本身使用Hash函數,將得到的結果,與上一步得到的摘要進行對比。如果兩者一致,就證明這封信未被修改過。
這樣一個完整過程就完成了對消息來源者身份的確定和對消息內容安全性的驗證。
但是,這樣並不能保證絕對安全,假如,道格想欺騙蘇珊,他偷偷使用了蘇珊的電腦,用自己的公鑰換走了鮑勃的公鑰。此時,蘇珊實際擁有的是道格的公鑰,但是還以爲這是鮑勃的公鑰。因此,道格就可以冒充鮑勃,用自己的私鑰做成”數字簽名”,寫信給蘇珊,讓蘇珊用假的鮑勃公鑰進行解密。解決這個問題就涉及到認證,這裏我們先不做談論。
示例代碼:
1、生成消息摘要的代碼:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Digest {
private static String[] arg={"D:\\英文\\java\\JavaInterview\\阿房宮賦.txt","SHA-1","MD5"};
public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
// TODO Auto-generated method stub
String sha=arg.length>=2?arg[1]:"SHA-1";
String md5=arg.length>=3?arg[2]:"MD5";
MessageDigest alg1=MessageDigest.getInstance(sha);
MessageDigest alg2=MessageDigest.getInstance(md5);
byte[] input=Files.readAllBytes(Paths.get(arg[0]));
byte[] hash1=alg1.digest(input);
byte[] hash2=alg2.digest(input);
String d1="";
for(int i=0;i<hash1.length;i++){
int v=hash1[i]&0xFF;
if(v<16) d1+="0";
d1+=Integer.toString(v,16).toUpperCase()+"";
}
String d2="";
for(int i=0;i<hash2.length;i++){
int v=hash2[i]&0xFF;
if(v<16) d2+="0";
d2+=Integer.toString(v,16).toUpperCase()+"";
}
System.out.println("SHA-1算法生成的消息摘要: "+d1);
System.out.println("MD5算法生成的消息摘要: "+d2);
}
}
運行結果:
SHA-1算法生成的消息摘要: 42109004CF53C57A5667D94420063D1D5B762DB1
MD5算法生成的消息摘要: FD0B8405534503F587F22FD73038E03B
從結果可以看出,SHA-1算法生成的消息摘要要稍微長一些,安全新也自然高一些,而且目前MD5算法被找到了一些規律,可靠性更差了,一般推薦使用SHA-1算法。
2、消息簽名的代碼:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
public class SignatureTest {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, ClassNotFoundException, IOException {
// 通用處理方式
KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance("RSA");
KeyPair keyPair=keyPairGenerator.generateKeyPair();
PrivateKey privateKey=keyPair.getPrivate();
PublicKey publicKey=keyPair.getPublic();
Signature signature=Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
byte[] src=new byte[]{1,2,3,4,5,6,7};
signature.update(src);
byte[] signed=signature.sign();
signature.initVerify(publicKey);
signature.update(src);
System.out.println(signature.verify(signed));
// 保存原始數據,簽名,公鑰,祕鑰
FileOutputStream foutsrc=new FileOutputStream("src.txt");
FileOutputStream foutsigned=new FileOutputStream("signed.dat");
foutsrc.write(src);
foutsigned.write(signed);
foutsigned.close();
foutsrc.close();
FileOutputStream outPublicKey=new FileOutputStream("public.key");
ObjectOutputStream objOut=new ObjectOutputStream(outPublicKey);
objOut.writeObject(publicKey);
objOut.close();
outPublicKey.close();
FileOutputStream outprivateKey=new FileOutputStream("private.key");
ObjectOutputStream objOut1=new ObjectOutputStream(outprivateKey);
objOut1.writeObject(privateKey);
objOut1.close();
outprivateKey.close();
// 流數據的特殊處理
sign();
verify();
}
private static void verify() throws IOException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
FileInputStream fisPublicKey=new FileInputStream("public.key");
ObjectInputStream oisPublicKey=new ObjectInputStream(fisPublicKey);
PublicKey publicKey=(PublicKey) oisPublicKey.readObject();
oisPublicKey.close();
fisPublicKey.close();
Signature signature=Signature.getInstance("MD5withRSA");
signature.initVerify(publicKey);
FileInputStream fis=new FileInputStream("src.txt");
byte[] src=new byte[1024];
int len=0;
while((len=fis.read(src))!=-1){
signature.update(src,0,len);
}
fis.close();
FileInputStream fisSigned=new FileInputStream("signed.dat");
byte[] signed=new byte[fisSigned.available()];
int readed=0;
int total=0;
System.out.println(signed.length);
while(total<signed.length){
// 此方法的第三個參數 len 長度不能大於 b.length-off,並且 len 等於 0 時 直接返回,所以不能用 while(readed!=-1)進行判斷
readed=fisSigned.read(signed,total,signed.length-total);
total+=readed;
}
fisSigned.close();
System.out.println(signature.verify(signed));
}
private static void sign() throws IOException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
FileInputStream fisPrivateKey=new FileInputStream("private.key");
ObjectInputStream oisPrivateKey=new ObjectInputStream(fisPrivateKey);
PrivateKey privateKey=(PrivateKey) oisPrivateKey.readObject();
oisPrivateKey.close();
fisPrivateKey.close();
Signature signature=Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
FileInputStream fis=new FileInputStream("src.txt");
byte[] src=new byte[1024];
int len=0;
while((len=fis.read(src))!=-1){
signature.update(src,0,len);
}
fis.close();
byte[] signed=signature.sign();
System.out.println(signed.length);
FileOutputStream fos=new FileOutputStream("signed.dat");
fos.write(signed);
fos.close();
}
}
運行結果:
true
128
128
true
本示例代碼中,展現了兩種方式的簽名,第一種是通用方式:直接對已知的內容進行加密;第二種方式是通過數據流的方式,對讀取的文件中的內容進行加密,加密完成之後再存入文件,然後進行文件傳輸,接收端又讀取接收文件,再進行解密驗證。