解剖RSA算法原理及數字簽名(附java實現源碼)

1.RSA算法

簡介:       

      1977年,三位數學家Rivest、Shamir 和 Adleman 設計了一種算法,可以實現非對稱加密。這種算法用他們三個人的名字命名,叫做RSA算法。RSA加密算法是一種非對稱加密算法。遵循私鑰加密公鑰解密、公鑰加密私鑰解密的模式。只有短的RSA鑰匙纔可能被強力方式解破。只要其鑰匙的長度足夠長,用RSA加密的信息實際上是不能被解破的。

原理:

   簡單介紹下素數和指數。

素數:素數又稱質數,指在一個大於1的自然數中,除了1和此整數自身外,不能被其他自然數整除的數。

互質數:公因數只有1的兩個數,叫做互質數。

模運算:模運算即求餘運算。“模”是“Mod”的音譯。和模運算緊密相關的一個概念是“同餘”。數學上,當兩個整數除以同一個正整數,若得相同餘數,則二整數同餘。

指數運算:指數運算又稱乘方計算,計算結果稱爲冪。nm指將n自乘m次。把nm看作乘方的結果,叫做”n的m次冪”或”n的m次方”。其中,n稱爲“底數”,m稱爲“指數”。

   RSA算法基於一個簡單的數論事實,將兩個大的素數想成十分容易,然而想要對其乘積進行因式分解則極其困難,因此可以將乘積公開作爲加密祕鑰。

   祕鑰的生成步驟

          <1>隨機選擇兩個不相等的指數p和q

          <2>計算出p和q的乘積N

          <3>計算出p-1和q-1的乘積φN也叫歐拉函數

          <4>隨機選擇整數e,e與m要互質並且0<e<φ(N)

          <5>計算e的模反元素d

          <6>公鑰是(N,e)則私鑰是(N,d)

   加密解密過程

          <1>假設一個明文數m(0<m<N)

          <2>將m加密成密文c,a.c=m^e  mod N

          <3>將密文c解密成m a.m=c^d mod N

     用java封裝RSA工具類

導入maven依賴:

 <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
      </dependency>
	  <dependency>
			  	<groupId>bouncycastle</groupId>
				<artifactId>bcprov-jdk16</artifactId>
				<version>140</version>
	  </dependency>
      <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.3.2</version>
      </dependency>
      <dependency>
			    <groupId>commons-codec</groupId>
			    <artifactId>commons-codec</artifactId>
			    <version>1.11</version>
	  </dependency>

RSAUtil工具類:

public class RSAUtil {

	    //非對稱密鑰算法
	    public static final String KEY_ALGORITHM = "RSA";


	    /**
	     * 密鑰長度,DH算法的默認密鑰長度是1024
	     * 密鑰長度必須是64的倍數,在512到65536位之間
	     */
	    private static final int KEY_SIZE = 512;
	    //公鑰
	    private static final String PUBLIC_KEY = "RSAPublicKey";

	    //私鑰
	    private static final String PRIVATE_KEY = "RSAPrivateKey";

	    /**
	     * 生成公私鑰對
	     *
	     */
	    public static Map<String, Object> initKey() throws Exception {
	        //實例化密鑰生成器
	        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
	        //初始化密鑰生成器
	        keyPairGenerator.initialize(KEY_SIZE);
	        //生成密鑰對
	        KeyPair keyPair = keyPairGenerator.generateKeyPair();
	        //甲方公鑰
	        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
	        //甲方私鑰
	        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
	        //將密鑰存儲在map中
	        Map<String, Object> keyMap = new HashMap<String, Object>();
	        keyMap.put(PUBLIC_KEY, publicKey);
	        keyMap.put(PRIVATE_KEY, privateKey);
	        return keyMap;

	    }


	    /**
	     * 私鑰加密
	     */
	    public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {

	        //取得私鑰
	        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
	        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
	        //生成私鑰
	        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
	        //數據加密
	        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
	        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
	        return cipher.doFinal(data);
	    }

	    /**
	     * 公鑰加密
	     *
	     */
	    public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {

	        //實例化密鑰工廠
	        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
	        //初始化公鑰
	        //密鑰材料轉換
	        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
	        //產生公鑰
	        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);

	        //數據加密
	        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
	        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
	        return cipher.doFinal(data);
	    }

	    /**
	     * 私鑰解密
	     */
	    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
	        //取得私鑰
	        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
	        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
	        //生成私鑰
	        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
	        //數據解密
	        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
	        cipher.init(Cipher.DECRYPT_MODE, privateKey);
	        return cipher.doFinal(data);
	    }

	    /**
	     * 公鑰解密
	     */
	    public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {

	        //實例化密鑰工廠
	        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
	        //初始化公鑰
	        //密鑰材料轉換
	        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
	        //產生公鑰
	        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
	        //數據解密
	        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
	        cipher.init(Cipher.DECRYPT_MODE, pubKey);
	        return cipher.doFinal(data);
	    }

	    /**
	     * 取得私鑰
	     */
	    public static byte[] getPrivateKey(Map<String, Object> keyMap) {
	        Key key = (Key) keyMap.get(PRIVATE_KEY);
	        return key.getEncoded();
	    }

	    /** 
	     * 取得公鑰
	     */
	    public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {
	        Key key = (Key) keyMap.get(PUBLIC_KEY);
	        return key.getEncoded();
	    }
	   
	}

測試類:

public static void main(String[] args) throws Exception {
    //初始化密鑰
    //生成密鑰對
    Map<String, Object> keyMap = RSAUtil.initKey();
    //公鑰
    byte[] publicKey = RSAUtil.getPublicKey(keyMap);

    //私鑰
    byte[] privateKey = RSAUtil.getPrivateKey(keyMap);
    System.out.println("公鑰:" + Base64.encodeBase64String(publicKey));
    System.out.println("私鑰:" + Base64.encodeBase64String(privateKey));
    String str = "MIICeAIB";
    System.out.println("原文:" + str);
    //私鑰加密
    byte[] code1 = RSAUtil.encryptByPrivateKey(str.getBytes(), privateKey);
    System.out.println("加密後的數據:" + Base64.encodeBase64String(code1));
    String a=Base64.encodeBase64String(code1);
    byte[] code3=Base64.decodeBase64(a);
    //公鑰解密
    byte[] decode1 = RSAUtil.decryptByPublicKey(code3, publicKey);
    System.out.println("解密後的數據:" + new String(decode1));

    //公鑰加密
    byte[] code2 = RSAUtil.encryptByPublicKey(str.getBytes(), publicKey);
    System.out.println("加密後的數據:" + Base64.encodeBase64String(code2));
    String code4=Base64.encodeBase64String(code2);

    //私鑰解密
    byte[]  code5=Base64.decodeBase64(code4);
    byte[] decode2 = RSAUtil.decryptByPrivateKey(code5, privateKey);
    System.out.println("解密後的數據:" + new String(decode2));
}

2.數字簽名

簡介

       數字簽名(又稱公鑰數字簽名、電子簽章等)是一種類似寫在紙上的普通的物理簽名,但是使用了公鑰加密領域的技術實現,用於鑑別數字信息的方法。一套數字簽名通常定義兩種互補的運算,一個用於簽名,另一個用於驗證。

        數字簽名,就是隻有信息的發送者才能產生的別人無法僞造的一段數字串,這段數字串同時也是對信息的發送者發送信息真實性的一個有效證明。

        數字簽名是非對稱密鑰加密技術與數字摘要技術的應用。

步驟

簽名過程:

1.獲得密鑰對(RSA,DSA,ECDSA)

2.將需要簽名消息進行hash計算得到消息摘要。(常見的消息摘要有MD5,SHA-256,SHA-512等)

3.將消息摘要進行非對稱加密生成簽名。

4.將原文和簽名及公鑰一起發送

驗證過程:

1.接受者首先對原文進行hash計算

2.使用發送者公鑰對簽名進行解籤

3.驗證兩者是否相同,若相同則證明未被篡

代碼實例:

HashUtil:

package com.core.hash;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.bouncycastle.crypto.digests.RIPEMD160Digest;

public class HashUtil {
	//md5消息摘要
public static String md5(byte[] data) throws NoSuchAlgorithmException{
	MessageDigest md=MessageDigest.getInstance("MD5");
	byte[] d= md.digest(data);
	return byteToString(d);
}

//字節轉化16進製表達式
public static String byteToString(byte[] data){
	StringBuffer sb=new StringBuffer();
	for (byte by:data) {
		sb.append(Integer.toString((by & 0x0ff)+0x100,16).substring(1));
	}
	return sb.toString();
}
//sha1消息摘要
public static String sha1(byte[] data) throws NoSuchAlgorithmException{
	MessageDigest md=MessageDigest.getInstance("SHA");
	byte[] d= md.digest(data);
	return byteToString(d);	
}

//sha256消息摘要
public static String sha256(byte[] data) throws NoSuchAlgorithmException{
	MessageDigest md=MessageDigest.getInstance("SHA-256");
	byte[] d= md.digest(data);
	return byteToString(d);	
}

//sha512消息摘要
public static String sha512(byte[] data) throws NoSuchAlgorithmException{
	MessageDigest md=MessageDigest.getInstance("SHA-512");
	byte[] d= md.digest(data);
	return byteToString(d);	
}

//ripemd160消息摘要
public static String ripemd160(byte[] data) throws NoSuchAlgorithmException{
	RIPEMD160Digest r=new RIPEMD160Digest();
	r.update(data,0,data.length);
	byte[] digest=new byte[r.getDigestSize()];
	r.doFinal(digest, 0);
	return byteToString(digest);	
}
//將16進制字節表達式碼轉成字節數組,兩兩hash
public static byte[] hexStr2hexByte(String data){
	if(null==data||0==data.length()){
		return null;
	}
	data=(data.length()==1)?"0"+data:data;
	byte[] arr=new byte[data.length()/2];
	byte[] tmp=data.getBytes();
	for (int i = 0; i < tmp.length/2; i++) {
		 arr[i]=unitByte(tmp[i*2], tmp[i*2+1]);
	}
	return arr;
}
public static byte unitByte(byte src0,byte src1){
	byte b0=Byte.decode("0x"+new String(new byte[]{src0})).byteValue();
	b0=(byte)(b0<<4);
	byte b1=Byte.decode("0x"+new String(new byte[]{src1})).byteValue();
	byte ret=(byte)(b0^b1);
	return ret;
}
}

digitalSignature:

package com.core.test;

import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import com.core.RSA.RSAUtil;
import com.core.hash.HashUtil;

public class digitalSignature {
public static void main(String[] args) {
	String str="數字簽名的文件的完整性是很容易驗證的(不需要騎縫章,騎縫簽名,也不需要筆跡專家),"
			+ "而且數字簽名具有不可抵賴性(不可否認性)。"
			+ "簡單地說,所謂數字簽名就是附加在數據單元上的一些數據,"
			+ "或是對數據單元所作的密碼變換。"
			+ "這種數據或變換允許數據單元的接收者用以確認數據單元的來源和數據單元的完整性並保護數據,"
			+ "防止被人(例如接收者)進行僞造。它是對電子形式的消息進行簽名的一種方法,一個簽名消息能在一個通信網絡中傳輸。"
			+ "基於公鑰密碼體制和私鑰密碼體制都可以獲得數字簽名,主要是基於公鑰密碼體制的數字簽名。"
			+ "包括普通數字簽名和特殊數字簽名。"
			+ "普通數字簽名算法有RSA、ElGamal、Fiat-Shamir、Guillou- Quisquarter、"
			+ "Schnorr、Ong-Schnorr-Shamir數字簽名算法、Des/DSA,橢圓曲線數字簽名算法和有限自動機數字簽名算法等。"
			+ "特殊數字簽名有盲簽名、代理簽名、羣簽名、不可否認簽名、公平盲簽名、門限簽名、具有消息恢復功能的簽名等,"
			+ "它與具體應用環境密切相關。顯然,數字簽名的應用涉及到法律問題,"
			+ "美國聯邦政府基於有限域上的離散對數問題制定了自己的數字簽名標準(DSS)。";
	try {
		 /*
		  * 1.使用RSA算法獲得公鑰和公鑰
		  */
		Map<String, Object> keyMap = RSAUtil.initKey();
	    byte[] publicKey = RSAUtil.getPublicKey(keyMap);
		byte[] privateKey = RSAUtil.getPrivateKey(keyMap);
		/*
		 * 2.發送方對字符串進行hash,獲得消息摘要
		 */
		String hash=HashUtil.md5(str.getBytes());
		System.out.println("---------------------------發送方開始-------------------------------");
		System.out.println("文本消息摘要"+hash);
		/*
		 * 3.使用私鑰進行簽名
		 */
		byte[] code1 = RSAUtil.encryptByPrivateKey(hash.getBytes(), privateKey);
		System.out.println("簽名爲:" + Base64.encodeBase64String(code1));
		String a=Base64.encodeBase64String(code1);
		
		System.out.println("----------------------------------接收方驗證------------------------------------");
		/*
		 * 4.使用公鑰解籤
		 */
		System.out.println("接收收到的簽名:" +a);
		byte[] code3=Base64.decodeBase64(a);
		byte[] decode1 = RSAUtil.decryptByPublicKey(code3, publicKey);
		String b=new String(decode1);
		System.out.println("接收方解籤:" +b);
		/*
		 * 5.接收方對字符串進行hash,獲得消息摘要
		 */
		String hash1=HashUtil.md5(str.getBytes());
		System.out.println("接收方原文摘要計算:"+hash1);
		if(b.equals(hash1)){
			System.out.println("消息正確是本人發送!");
		}else{
			System.out.println("消息錯誤非本人發送!");
		}
		System.out.println();
	} catch (Exception e) {
		e.printStackTrace();
	}
}
}

RSAUtil往上翻可以找到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章